Source code for sphero_vem.measure.voxel

"""Voxel-based shape descriptors and cell assignment."""

import numpy as np
import pandas as pd
from sphero_vem.utils import bbox_expand
from sphero_vem.utils.accelerator import gpu_dispatch, ski, ArrayLike


[docs] @gpu_dispatch() def props_voxel( labels: ArrayLike, spacing: tuple[float, float, float], bbox_margin: int = 15, calc_volume: bool = False, ) -> list[dict]: """Calculate voxel-based properties using GPU acceleration. This function uses skimage.measure.regionprops (or its CuCIM equivalent if CUDA is available) calculate per-label properties from a voxel image. Parameters ---------- labels : ArrayLike Array with the labeled image. spacing : tuple[float, float, float] Physical spacing of the voxel grid. This will only be used when calc_volume is True. bbox_margin : int Constant margin for bounding box expansion. The returned bounding box will be expanded by this value in all directions. calc_volume : bool Flag that controls whether to calculate volume and related quantities directly from the voxel map. This is useful when SDF and mesh-based approaches are not feasible. Default is False. Returns ------- list[dict] List of dictionaries containing the calculated properties for each label: - label: label ID - bbox: bounding box of the object - bbox_exp: bounding box expanded by bbox_margin - eigvals: eigenvalues of the gyration tensor (sorted ascending) - centroid: coordinates of the image centroid - volume: label volume in µm^3 (if calc_volume=True) - diam_equiv: equivalent diameter in µm^2 (if calc_volume=True) """ props = ski.measure.regionprops(labels) results = [ { "label": prop.label, "bbox": prop.bbox, "bbox_exp": bbox_expand( prop.bbox, margin=bbox_margin, im_shape=labels.shape ), "eigvals": tuple(float(i) for i in sorted(prop.inertia_tensor_eigvals)), "centroid": prop.centroid, } for prop in props ] if calc_volume: props_spacing = ski.measure.regionprops(labels, spacing=spacing) for i, prop in enumerate(props_spacing): results[i]["volume"] = float(prop.area) results[i]["diam_equiv"] = float(prop.equivalent_diameter_area) return results
[docs] def assign_cell(props: pd.DataFrame, cells: np.ndarray) -> pd.DataFrame: """Assign parent cell and return dataframe by looking up centroid Parameters ---------- props : pd.DataFrame The dataframe containing the region properties. It should have a `"centroid"` column containing the tuple indexing the object centroid. cells : np.ndarray An array containing the cells masks labeled by instance. Returns ------- pd.DataFrame The updated region properties dataframe with a new column `"parent_cell"`. """ indices = np.array(props["centroid"].tolist()).astype(int) props["parent_cell"] = cells[tuple(indices.T)] return props