Neighborhood Module API Reference
Module for performing neighborhood analysis.
NeighborhoodCollection
Bases: CelldegaCollection
Neighborhood-level or spatial-region MuData collection.
Observations are neighborhoods or spatial regions. Feature modalities live in
mod and global observation relations live in relations/mdata.obsp.
Geometry is kept as a live GeoDataFrame in memory; durable geometry
storage can be layered on later with WKB columns or GeoParquet sidecars.
geometry
property
Neighborhood geometry. Alias of :attr:gdf (single source of truth).
__init__(obs=None, mod=None, mdata=None, gdf=None, nbhd_type=None, data_dir=None, source=None, name=None, meta=None, nbhd_col='name', geometry=None, relations=None, provenance=None, uns=None, memberships=None, transformation_matrix=None)
Build a neighborhood / spatial-region collection.
The observation axis (one row per neighborhood) is established from a
neighborhood GeoDataFrame (gdf — the usual path, produced by
alpha_shape / generate_hextile / etc.), from an explicit obs
table paired with geometry, or from a pre-built mdata. When built
from gdf, per-neighborhood area/area_um2 and centroid columns
are derived and the neighborhood-id column is normalized.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obs
|
DataFrame | None
|
Pre-built neighborhood observation table (use with |
None
|
mod
|
dict[str, AnnData] | None
|
Feature-space modalities to attach up front. |
None
|
mdata
|
MuData | None
|
Pre-built |
None
|
gdf
|
GeoDataFrame | None
|
Neighborhood geometry; each row becomes an observation. Mutually
exclusive with |
None
|
nbhd_type
|
str | None
|
Label for how the neighborhoods were made (e.g.
|
None
|
data_dir
|
str | None
|
DegaFiles/instrument directory used as the default source for the transcript- and transform-loading methods. |
None
|
source
|
str | dict[str, Any] | None
|
Source descriptor recorded in provenance. |
None
|
name
|
str | None
|
Optional collection name. |
None
|
meta
|
dict[str, Any] | None
|
Extra metadata merged into |
None
|
nbhd_col
|
str
|
Column in |
'name'
|
geometry
|
GeoDataFrame | None
|
Neighborhood geometry paired with an explicit |
None
|
relations
|
dict[str, spmatrix] | None
|
Square neighborhood-by-neighborhood matrices for
|
None
|
provenance
|
dict[str, Any] | None
|
Free-form provenance metadata. |
None
|
uns
|
dict[str, Any] | None
|
Extra Celldega metadata. |
None
|
memberships
|
dict[str, spmatrix] | None
|
Membership matrices (e.g. cell-to-neighborhood); kept in
memory only (not persisted by |
None
|
transformation_matrix
|
Any | None
|
Optional micron-to-pixel affine (see
:meth: |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
calc_bordering(metric='border_ratio', key='bordering', category='leiden')
Calculate a neighborhood-by-neighborhood bordering relation.
Computes pairwise border relationships between neighborhoods and stores
the square matrix in relations[key] (mdata.obsp).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
metric
|
str
|
Border metric (e.g. |
'border_ratio'
|
key
|
str
|
Name for the relation in |
'bordering'
|
category
|
str
|
Neighborhood category recorded on the computed result. |
'leiden'
|
Returns:
| Type | Description |
|---|---|
spmatrix
|
The stored sparse relation matrix. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If geometry is not set. |
calc_gradient(obs_name, direction='both', bin_width=10, max_dist=50, nbhd_type='gradient', *, technology=None, scale_um_per_pixel=None, is_pixel_space=False, clip_boundary=None, clip_reference=None, clip_alpha=100, **kwargs)
Calculate a gradient collection around one neighborhood in this collection.
Picks the neighborhood identified by obs_name and grows fixed-width
bands outward from and/or inward into it (see
the gradient engine), returning a new
gradient NeighborhoodCollection — one neighborhood (observation) per
ring, ordered inner-most to outer-most. From there the usual
calc_nbhd_by_* methods summarize cell composition or expression per
ring, profiling how the tissue changes with distance from that
neighborhood's boundary. A gradient is therefore always anchored to a
concrete neighborhood (e.g. a per-cluster alpha shape) rather than a
loose geometry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obs_name
|
str
|
Identifier of the source neighborhood to anchor the gradient
on — matched against the collection's observation index (and, as a
fallback, the |
required |
direction
|
str
|
|
'both'
|
bin_width
|
float
|
Width of each ring in microns (default |
10
|
max_dist
|
float
|
Maximum distance from the neighborhood boundary in microns
(default |
50
|
nbhd_type
|
str
|
Label recorded on the new collection (default
|
'gradient'
|
technology
|
str | None
|
Imaging platform used to look up |
None
|
scale_um_per_pixel
|
float | None
|
Microns per pixel; required (directly or via
|
None
|
is_pixel_space
|
bool
|
|
False
|
clip_boundary
|
Any | None
|
Optional precomputed tissue boundary to clip outward
rings to (takes precedence over |
None
|
clip_reference
|
Any | None
|
Optional source of cell positions (an |
None
|
clip_alpha
|
float
|
Inverse-alpha for the on-the-fly alpha shape (default
|
100
|
**kwargs
|
Any
|
Forwarded to the new :class: |
{}
|
Returns:
| Type | Description |
|---|---|
NeighborhoodCollection
|
A new |
NeighborhoodCollection
|
rings around |
Raises:
| Type | Description |
|---|---|
ValueError
|
If this collection has no geometry. |
KeyError
|
If |
Examples:
>>> # nbhd holds one alpha-shape neighborhood per cell-type cluster
>>> grad_nbhd = nbhd.calc_gradient(obs_name="9", direction="both",
... bin_width=50, max_dist=200)
>>> grad_nbhd.calc_signature(adata, by="cell")
>>> grad_nbhd.obs[["direction", "dist_start_um"]].head(3)
calc_overlap(metric='iou', key='overlap', category='leiden')
Calculate a neighborhood-by-neighborhood overlap relation.
Computes pairwise geometric overlap between neighborhoods and stores the
square matrix in relations[key] (mdata.obsp).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
metric
|
str
|
Overlap metric — |
'iou'
|
key
|
str
|
Name for the relation in |
'overlap'
|
category
|
str
|
Neighborhood category recorded on the computed result. |
'leiden'
|
Returns:
| Type | Description |
|---|---|
spmatrix
|
The stored sparse relation matrix. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If geometry is not set. |
calc_population(adata, category='leiden', modality_name='population', output='proportion', min_cells=5, drop_missing=True)
Calculate a neighborhood-by-population modality and attach it to self.mod.
Spatially assigns cells to neighborhoods and, per neighborhood, counts
cells per category value to form a neighborhood (rows) by population
(columns) feature matrix.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
adata
|
AnnData
|
Cell-level |
required |
category
|
str
|
|
'leiden'
|
modality_name
|
str
|
Key for the modality in |
'population'
|
output
|
str
|
|
'proportion'
|
min_cells
|
int
|
Minimum cells for a neighborhood to be included. |
5
|
drop_missing
|
bool
|
When |
True
|
Returns:
| Type | Description |
|---|---|
None
|
|
calc_signature(adata=None, by='cell', modality_name=None, min_cells=1, data_dir=None, drop_missing=True, include_variance=False)
Calculate a neighborhood-by-gene modality and attach it to self.mod.
Builds per-neighborhood gene expression — mean expression of contained
cells (by="cell") or transcript counts (by="cell-free").
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
adata
|
AnnData | None
|
Cell-level |
None
|
by
|
str
|
|
'cell'
|
modality_name
|
str | None
|
Key for the modality; defaults to |
None
|
min_cells
|
int
|
Minimum cells/transcripts for a neighborhood to be kept. |
1
|
data_dir
|
str | None
|
Transcript directory for |
None
|
drop_missing
|
bool
|
When |
True
|
include_variance
|
bool
|
When |
False
|
Returns:
| Type | Description |
|---|---|
None
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
calc_transcript_assignment(data_dir=None)
Add per-neighborhood transcript-assignment columns to obs.
From transcripts.parquet in data_dir, adds three obs columns
(on the underlying MuData) for each neighborhood:
total_transcripts— transcripts falling inside the neighborhood.unassigned_transcripts— those withcell_id == "UNASSIGNED".transcript_assignment_proportion— assigned / total (0.0when the neighborhood has no transcripts).
Assumption: the transcript-to-cell assignment is not computed here —
it must already be present in the instrument data, with unassigned
transcripts marked by the "UNASSIGNED" sentinel (Xenium convention).
Only transcripts are needed — no adata or cell polygons.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_dir
|
str | None
|
Directory containing |
None
|
Returns:
| Type | Description |
|---|---|
None
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If geometry or a usable |
from_gdf(gdf, nbhd_type='neighborhood', **kwargs)
classmethod
Create a NeighborhoodCollection from a neighborhood GeoDataFrame.
Convenience wrapper for NeighborhoodCollection(gdf=gdf,
nbhd_type=nbhd_type, **kwargs).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gdf
|
GeoDataFrame
|
Neighborhood geometry; each row becomes an observation. |
required |
nbhd_type
|
str
|
Label for how the neighborhoods were made. |
'neighborhood'
|
**kwargs
|
Any
|
Forwarded to the constructor. |
{}
|
Returns:
| Type | Description |
|---|---|
NeighborhoodCollection
|
A new |
load_transformation_matrix(data_dir=None)
Load the micron-to-pixel transformation matrix from DegaFiles.
Reads micron_to_image_transform.csv and stores it via
:meth:set_transformation_matrix. Later this matrix can instead be
supplied directly (e.g. from SpatialData).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_dir
|
str | None
|
Directory containing the transform CSV; defaults to
|
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The loaded matrix as a float |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no |
set_transformation_matrix(matrix)
Set the micron-to-pixel affine transformation matrix.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
matrix
|
Any
|
Affine mapping micron coordinates (the geometry's native
space) to image/pixel space, as a |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
The stored matrix as a float |
ndarray
|
|
to_pixel_gdf()
Return the neighborhood geometry ready for pixel-space visualization.
Adds a geometry_pixel column (micron geometry transformed to image
space via the stored transformation matrix) and leaves the original
micron geometry intact. The result can be passed straight to
Landscape(nbhd=...), which renders geometry_pixel directly when
present rather than applying its own transform.
Returns:
| Type | Description |
|---|---|
GeoDataFrame
|
A copy of |
Raises:
| Type | Description |
|---|---|
ValueError
|
If geometry or the transformation matrix is not set. |
alpha_shape_cell_clusters(adata, cat='cluster', alphas=(100, 150, 200, 250, 300, 350), meta_cluster=None)
Compute alpha shapes for each cluster in the cell metadata.
Parameters
adata : AnnData AnnData object with cell metadata in obs and spatial coordinates in obsm["spatial"]. cat : str Column name in adata.obs containing cluster/category labels. alphas : Sequence[float] List of inverse alpha values to compute shapes for. meta_cluster : pd.DataFrame | None Optional DataFrame with cluster metadata including 'color' column. If not provided, colors will be extracted from adata.uns[f'{cat}_colors'] if available, otherwise defaults to black.
Returns
gpd.GeoDataFrame GeoDataFrame with alpha shapes for each cluster at each alpha value.
alpha_shape_cell_clusters_by_slice(adata, cluster_attr='cluster', slice_attr='slice_id', z_attr=None, alphas=(150,), meta_cluster=None, z_jitter=0.1, progress_every=0)
Compute one alpha shape per (slice, cluster) pair, each stamped with its slice's Z.
Mirrors the approach validated in
notebooks/Serial_Slice_Alpha_Shapes_3D_Demo.ipynb: for each slice, builds a
throwaway per-slice AnnData and reuses alpha_shape_cell_clusters /
filter_alpha_shapes at a single alpha resolution, then stamps every
resulting polygon with that slice's Z (from z_attr, or 0.0 if not given)
plus a small per-cluster jitter (via _stamp_z) so overlapping, coplanar
cluster polygons within a slice don't z-fight.
Parameters
adata : AnnData
Cell-level AnnData with spatial coordinates in obsm["spatial"] and
cluster_attr / slice_attr (and, if given, z_attr) columns in obs.
cluster_attr : str
Column in adata.obs with cluster/category labels.
slice_attr : str
Column in adata.obs identifying each slice.
z_attr : str | None
Column in adata.obs with each cell's Z coordinate (expected to be one
constant value per slice, e.g. from celldega.align.serial_slices). If
None, every slice is stamped at Z=0 (e.g. for a future 2D
"neighborhood-scape" use where "slice" means "dataset").
alphas : Sequence[float]
Must contain exactly one inverse-alpha resolution (see alpha_shape) —
this function computes a single resolution per (slice, cluster), unlike
alpha_shape_cell_clusters which can sweep several.
meta_cluster : pd.DataFrame | None
Optional cluster color/metadata lookup, forwarded to
alpha_shape_cell_clusters.
z_jitter : float
Per-cluster Z offset within a slice, to avoid z-fighting between
coplanar cluster polygons that would otherwise sit at the exact same Z.
progress_every : int
Print a progress line every this many slices processed (0, the
default, disables it). Each real alpha shape involves a Delaunay
triangulation plus verification, so this is the slow part of a
neighborhood-cloud pre-processing run on a large aligned dataset with
many slices -- worth visibility into, unlike a quick synthetic test.
Returns
gpd.GeoDataFrame
One row per (slice, cluster) neighborhood, with columns name
(f"{slice_id}__{cluster_id}"), cluster_id, slice_id, geometry
(3D), color, area, inv_alpha, cell_count.
alpha_shape_gene_expression_by_slice(adata, gene_list, slice_attr='slice_id', z_attr=None, alphas=(150,), min_expression=2.0, min_cells=4, z_jitter=0.1, shape_max_cells=50000, random_state=0)
Compute one alpha shape per (slice, gene) pair, built from expressing cells.
Generalizes alpha_shape_cell_clusters_by_slice — instead of grouping
cells by a cluster label, each shape is built from whichever cells have
adata[:, gene].X >= min_expression within that slice. A thin eager
wrapper around iter_gene_alpha_shapes_by_slice that collects every
gene's shapes into one GeoDataFrame before returning — convenient for
interactive/analysis use on a bounded gene list (a curated marker
panel), but holds every gene's shapes for every slice in memory at once.
For a whole-transcriptome gene list (~40k genes), use
iter_gene_alpha_shapes_by_slice directly, or
celldega.pre.nbhd_cloud.write_gene_shapes_streaming to write results to
DegaFiles as they're produced instead of materializing all of them.
Parameters
adata : AnnData
Cell-level AnnData with spatial coordinates in obsm["spatial"],
slice_attr (and, if given, z_attr) columns in obs, and gene_list
present in adata.var_names.
gene_list : Sequence[str]
Genes to compute shapes for. Must all be present in adata.var_names.
slice_attr : str
Column in adata.obs identifying each slice.
z_attr : str | None
Column in adata.obs with each cell's Z coordinate. If None, every
slice is stamped at Z=0.
alphas : Sequence[float]
Must contain exactly one inverse-alpha resolution — one resolution
per (slice, gene), same restriction as alpha_shape_cell_clusters_by_slice.
min_expression : float
A cell counts as "expressing" a gene when its value is at least this
(default: 2 counts).
min_cells : int
Minimum number of expressing cells required to compute a shape for a
(slice, gene) pair; pairs with fewer are silently skipped.
z_jitter : float
Small per-gene Z offset, to avoid z-fighting between coplanar gene
shapes that would otherwise sit at the exact same Z (see
_gene_z_offset).
shape_max_cells : int | None
Cap (via uniform random subsample) on the expressing cells that feed
the alpha shape's own geometry computation, within one (slice, gene)
pair -- see iter_gene_alpha_shapes. None disables it. Even a
curated marker panel of 50-100 genes can include broadly-expressed
genes with far more expressing cells than needed to describe their
spatial footprint, so this caps the actual cost driver regardless of
how many genes are in gene_list.
random_state : int
Seed for the subsampling RNG.
Returns
gpd.GeoDataFrame
One row per (slice, gene) shape, with columns name
(f"{slice_id}__{gene}"), gene, slice_id, geometry (3D),
area, inv_alpha, cell_count, mean_expression, and
max_expression (whole-tissue single-cell max for that gene,
repeated across every row of that gene — see write_gene_shapes).
filter_alpha_shapes(gdf_alpha, alpha, min_area=0, clean_names=True)
Filter alpha shapes by a specific alpha value and optionally clean up names.
Alpha shapes computed by alpha_shape_cell_clusters have names in the format
{category}_{alpha} (e.g., "cluster_0_150"). This function filters to a specific
alpha value and removes the trailing _{alpha} suffix from names.
Parameters
gdf_alpha : gpd.GeoDataFrame
GeoDataFrame of alpha shapes with 'inv_alpha', 'area', 'name', and 'cat' columns.
Typically the output of alpha_shape_cell_clusters.
alpha : float
The inverse alpha value to filter for (must match values in 'inv_alpha' column).
min_area : float, default 0
Minimum area threshold. Shapes with area <= min_area are excluded.
clean_names : bool, default True
If True, removes the trailing _{alpha} suffix from the 'name' column,
leaving just the category name (e.g., "cluster_0" instead of "cluster_0_150").
Returns
gpd.GeoDataFrame Filtered GeoDataFrame with optionally cleaned names.
Examples
gdf_alpha = dega.nbhd.alpha_shape_cell_clusters(adata, cat="leiden") gdf_filtered = dega.nbhd.filter_alpha_shapes(gdf_alpha, alpha=150)
Names are now just category names without the alpha suffix
print(gdf_filtered["name"].tolist()[:3]) ['0', '1', '2']
generate_hextile(adata, diameter=100)
Generate a hexagonal grid over the bounding box of cell spatial coordinates.
Parameters
adata : AnnData
AnnData object with spatial coordinates in obsm["spatial"].
diameter : float, default 100
Diameter of each hexagon in the same units as the spatial coordinates
(typically microns).
Returns
gpd.GeoDataFrame GeoDataFrame with hexagon geometries covering the spatial extent. Columns: "name" (hex_0, hex_1, ...), "geometry" (Polygon).
Examples
gdf_hex = dega.nbhd.generate_hextile(adata, diameter=100) gdf_hex.shape (1234, 2)
hextile_niche(gdf_hex, adata_hex, category='leiden', dissolve=True)
Create niche polygons from hextiles based on clustering results.
Takes hexagon geometries and assigns them to niches based on clustering (e.g., Leiden clustering of hexagon population distributions). Optionally dissolves adjacent hexagons of the same niche into unified polygons.
Parameters
gdf_hex : gpd.GeoDataFrame
GeoDataFrame of hexagon geometries. Must be indexed by hexagon name
(matching adata_hex.obs.index).
adata_hex : AnnData
AnnData object containing clustering results in obs[category].
The index must match the hexagon names in gdf_hex.
Colors can be provided in uns[f"{category}_colors"].
category : str, default "leiden"
Column name in adata_hex.obs containing the niche/cluster assignment.
dissolve : bool, default True
If True, dissolve adjacent hexagons of the same niche into unified
MultiPolygon geometries. If False, return individual hexagons with
their niche assignment.
Returns
gpd.GeoDataFrame If dissolve=True: GeoDataFrame with dissolved niche polygons. Columns: "name", "cat", "geometry", "color", "area". If dissolve=False: GeoDataFrame with individual hexagons and niche assignment. Columns: "name", "cat", "geometry", "color".
Examples
Generate hexagons and compute population distribution
gdf_hex = dega.nbhd.generate_hextile(adata, diameter=100) nbhd = dega.nbhd.NeighborhoodCollection(gdf=gdf_hex) nbhd.calc_population(adata, category="leiden")
Cluster hexagons by population similarity (e.g., using scanpy)
import scanpy as sc sc.pp.pca(adata_hex) sc.pp.neighbors(adata_hex) sc.tl.leiden(adata_hex)
Create dissolved niche polygons
gdf_niche = dega.nbhd.hextile_niche(gdf_hex, adata_hex, category="leiden")
Or keep individual hexagons with niche assignment
gdf_hex_niche = dega.nbhd.hextile_niche(gdf_hex, adata_hex, dissolve=False)
iter_gene_alpha_shapes(coords, slice_ids, z_values, gene_expression, cell_ids=None, alphas=(150,), min_expression=2.0, min_cells=4, z_jitter=0.1, max_cells=50000, shape_max_cells=50000, random_state=0)
Yield (gene, gdf_gene, df_cells) one gene at a time, from a
caller-supplied stream of (gene, expression_array) pairs — no
AnnData involved.
This is the AnnData-free core iter_gene_alpha_shapes_by_slice wraps:
that function exists for the interactive-analysis case where you
already have (or are willing to build) an AnnData with every gene in
gene_list loaded into .X. This function instead takes a plain
per-cell coordinate/slice/Z array plus an iterable of
(gene, expression_array) — the caller decides how each gene's
expression array is produced, which matters when the source isn't
already a combined multi-gene matrix: a plain generator can produce one
gene's array at a time and let the caller's reference to it drop before
the next gene is read, without ever assembling a combined multi-gene
matrix (dense or sparse) or an AnnData wrapper around it — pure
overhead when nothing downstream ever needs more than one gene's
expression at a time.
Alongside each gene's shape, also selects up to max_cells of that
gene's own highest-expressing cells (same min_expression threshold as
the shape) — real cell centroids to "pepper" the alpha shape with in
the frontend, grounding the coarse polygon in actual single-cell
positions. Capped rather than exhaustive because an unbounded per-gene
cell file would scale with how many cells express the gene, not with
dataset size in general — a broadly-expressed gene could otherwise mean
a cell file sized like the whole dataset.
Parameters
coords : np.ndarray
Per-cell spatial coordinates, shape (n_cells, >=2) — only the
first two columns are used for the alpha shape itself.
slice_ids : np.ndarray
Per-cell slice identifier, shape (n_cells,).
z_values : np.ndarray
Per-cell Z coordinate, shape (n_cells,) (all zeros for a 2D run).
Expected constant within a slice, like elsewhere in this module.
gene_expression : Iterable[tuple[str, np.ndarray]]
Yields (gene, expr) pairs, expr a per-cell array aligned to
coords/slice_ids/z_values (same order, same length). Genes are
processed in the order this iterable yields them (and that order is
what _gene_z_offset derives each gene's Z bucket from).
cell_ids : np.ndarray | None
Per-cell identifier, shape (n_cells,), aligned like coords. If
None, df_cells is always empty (no cell selection performed) --
e.g. when a caller only wants shapes and doesn't have cell ids handy.
alphas, min_expression, min_cells, z_jitter :
Same meaning as alpha_shape_gene_expression_by_slice.
max_cells : int
Cap on the number of top-expressing cells selected per gene (across
all slices combined). 0 (or cell_ids=None) skips cell selection
entirely, yielding an always-empty df_cells.
shape_max_cells : int | None
Cap on the number of expressing cells (within one slice) that feed
the alpha shape's own geometry computation -- a real alpha shape's
cost scales with this count, and a broadly-expressed gene can have
far more expressing cells than are needed to describe its spatial
footprint. Above this many, a uniform random subsample is used
instead (see _subsample_mask_for_shape for why random rather than
top-expressing). None disables the cap (use every expressing
cell). Does not affect mean_expression/cell_count, which are
always computed from the full expressing population, or df_cells
(governed by max_cells instead).
random_state : int
Seed for the subsampling RNG, so results are reproducible run to
run for the same inputs.
Yields
tuple[str, gpd.GeoDataFrame, pd.DataFrame]
(gene, gdf_gene, df_cells) — gdf_gene as documented on
iter_gene_alpha_shapes_by_slice. df_cells has columns
cell_id, gene, slice_id, x, y, z, expression — one row
per selected cell, empty (zero rows, same columns) if no cells were
selected (no cell_ids, max_cells <= 0, or no expressing cells).
iter_gene_alpha_shapes_by_slice(adata, gene_list, slice_attr='slice_id', z_attr=None, alphas=(150,), min_expression=2.0, min_cells=4, z_jitter=0.1, max_cells=50000, shape_max_cells=50000, random_state=0)
Yield (gene, gdf_gene, df_cells) one gene at a time, instead of
building the whole (slice, gene) result set in memory before returning
anything.
An AnnData-sourcing wrapper around iter_gene_alpha_shapes: for each
gene in gene_list, densifies that one column from adata.X and hands
it to iter_gene_alpha_shapes (along with adata.obs_names as cell
ids). Convenient when you already have (or are willing to build) an
AnnData with every gene in gene_list loaded —
alpha_shape_gene_expression_by_slice (a thin eager wrapper around this
generator) is the interactive/analysis entry point for that case.
Parameters
adata, slice_attr, z_attr, alphas, min_expression, min_cells, z_jitter :
Same meaning as alpha_shape_gene_expression_by_slice.
gene_list : Sequence[str]
Genes to compute shapes for, in the order they'll be processed (and
the order _gene_z_offset derives each gene's Z bucket from).
max_cells : int
Forwarded to iter_gene_alpha_shapes — cap on top-expressing cells
selected per gene (0 skips cell selection).
shape_max_cells, random_state :
Forwarded to iter_gene_alpha_shapes — cap (via uniform random
subsample) on the expressing cells that feed the alpha shape's own
geometry computation, and the seed for that subsampling.
Yields
tuple[str, gpd.GeoDataFrame, pd.DataFrame]
(gene, gdf_gene, df_cells) — gdf_gene has columns name
(f"{slice_id}__{gene}"), gene, slice_id, geometry (3D),
area, inv_alpha, cell_count, mean_expression,
max_expression (whole-tissue single-cell max for that gene). Empty
(zero rows, same columns) if the gene produced no usable shape in
any slice -- below min_cells everywhere, or every candidate shape
failed verification / GEOS choked (see alpha_shape). df_cells
has columns cell_id, gene, slice_id, x, y, z,
expression — see iter_gene_alpha_shapes.