squidpy.experimental.im.calculate_image_features
- squidpy.experimental.im.calculate_image_features(sdata, *, image_key=None, labels_key=None, shapes_key=None, scale=None, channels=None, features=None, tile_size=1024, align_mode='strict', key_added=None, invalid_as_zero=False, drop_constant_features=True, n_jobs=1, inplace=True)[source]
Calculate per-cell features from segmentation masks.
Uses cp_measure for CellProfiler-derived features, scikit-image
regionpropsfor morphological/intensity features, and squidpy-specific per-cell metrics (summary statistics, GLCM texture, intensity histograms). Large images are automatically tiled intotile_size x tile_sizechunks with overlap so that every cell is fully contained in exactly one tile.- Parameters:
sdata (
SpatialData) – SpatialData object.image_key (
str|None) – Key insdata.images. Optional: required only for intensity / squidpy / cp_measure features (and forshapes_key). Morphology-only runs need no image.labels_key (
str|None) – Key insdata.labelswith segmentation masks.shapes_key (
str|None) – Key insdata.shapes(rasterized to labels internally).channels (
list[str] |None) – Subset of channel names to use, matching those returned byspatialdata.models.get_channel_names().Noneuses all channels. Integer indices are not accepted – always pass names.features (
list[str] |str|None) –Which features to compute.
None(the default) computes all features across every backend; because that set includes intensity / texture features, an image is required (a clear error is raised ifimage_keyis missing). Otherwise a list of flag strings from three groups:cp_measure –
"cp_measure:intensity","cp_measure:sizeshape","cp_measure:texture","cp_measure:granularity","cp_measure:zernike","cp_measure:feret","cp_measure:radial","cp_measure:correlation"(or a single correlation kind via"cp_measure:correlation_<pearson|costes|manders_fold|rwc>"). Columns keep cp_measure’s native CellProfiler names (e.g.Area,Intensity_MeanIntensity__<channel>). Correlation features need an image with >=2 channels.skimage regionprops –
"skimage:morphology"(all shape props, from the mask alone) or"skimage:morphology:<prop>"for one (e.g.area);"skimage:intensity"(all per-channel intensity props, needs an image) or"skimage:intensity:<prop>"for one (e.g.intensity_mean). Morphology columns use skimage’s native names (area,centroid-0); intensity columns are suffixed with the channel name.squidpy per-cell –
"squidpy:summary"(per-channel mean / std / min / max),"squidpy:texture"(per-channel GLCM contrast, dissimilarity, homogeneity, energy, ASM, correlation), and"squidpy:histogram"(per-channel intensity histogram).
If a request computes both
"cp_measure:sizeshape"and skimage morphology props, the overlapping skimage props (which cp_measure reproduces identically) are dropped to avoid duplicate columns; only the skimage-only props (centroid_local,feret_diameter_max) are kept. cp_measure computes its groups all-or-nothing, so it wins.tile_size (
int) – Side length of the tiling grid (pixels).align_mode (
Literal['strict','rasterize']) –How to handle image/labels whose pixel grids do not match (via their SpatialData transformations).
"strict"(default): require the relative transform between image and labels to be identity or an integer-pixel translation; raise otherwise with a hint pointing tospatialdata.rasterize()."rasterize": resample labels onto the image pixel grid usingspatialdata.rasterize()when not pixel-aligned (logs a warning because this materializes the full label grid). Not supported for multiscale labels under a non-integer transform – pre-align instead.
Cells falling outside the image/labels overlap are dropped (logged at INFO).
key_added (
str|None) – Key under which to store the result insdata.tables. IfNone(default), the key is derived from the region and, when an image is used, the image key:f"morphology_{labels_key or shapes_key}_{image_key}"(orf"morphology_{labels_key or shapes_key}"for a morphology-only run). This keeps per-region / per-image runs from clobbering each other.invalid_as_zero (
bool) – IfTrue, replaceinf/NaNwith zero.False(default) keeps them, so undefined features stay distinguishable from genuine zeros.drop_constant_features (
bool) – IfTrue(default), drop zero-variance feature columns (they break scaling/PCA downstream). Applied per call and skipped for a single cell; when comparing samples, prefer dropping constants on the concatenated table so column sets stay aligned.n_jobs (
int) – Number of worker processes for tile featurization (-1uses all cores).1runs serially in-process. cp_measure is GIL-bound, son_jobs > 1uses adask.distributed.LocalCluster; if an activedask.distributed.Clientis already in scope it is used instead andn_jobsis ignored.inplace (
bool) – IfTrue, store result insdata.tables. Otherwise return it.
Notes
Cells dropped during alignment (fully/partially outside the image) and constant features removed by
drop_constant_featuresare logged at WARNING level.With
n_jobs > 1aLocalClusteris started, which spawns worker processes. On macOS/Windows (spawn start method) the calling code must be guarded byif __name__ == "__main__":(the standard Python multiprocessing requirement), or run from a notebook. For repeated calls or multi-node scale-out, create and reuse your ownClientso the worker pool is started once. Worker BLAS/OpenMP threads are pinned to 1 to avoid oversubscription.Examples
>>> import squidpy as sq >>> sq.experimental.im.calculate_image_features( ... sdata, ... image_key="image", ... labels_key="cells", ... features=["cp_measure:sizeshape", "skimage:morphology", "squidpy:summary"], ... )
All features across every backend (needs an image):
>>> sq.experimental.im.calculate_image_features( ... sdata, image_key="image", labels_key="cells", features=None ... )
Morphology-only needs no image:
>>> sq.experimental.im.calculate_image_features( ... sdata, labels_key="cells", features=["skimage:morphology:area"] ... )
The per-cell table is stored in
sdata.tables["morphology_cells"](the key is derived from the region/image keys; seekey_added).