spacr.object_distances

Every distance worth measuring, within and between segmented objects.

What existed before this module: within ONE object type, centroid-to- centroid neighbour distances over a KD-tree; and between types, a single family measuring from a CHANNEL’S intensity centre of mass to the nearest nucleus or pathogen surface. So there was no centre-to-centre between types, no centre-to-perimeter in either direction, no perimeter-to- perimeter, nothing about local maxima, and nothing about where an object sits inside its parent.

A Euclidean distance transform is computed once for each object-type mask. At every pixel it records the distance to the nearest pixel belonging to that object type, with zero inside an object. Centre-to-surface, surface-to-surface, and local-maximum distances can therefore be obtained by sampling or reducing the precomputed field rather than evaluating every pair of objects. The principal operations are:

centre -> nearest surface      dt_b[centroid_a]
surface -> surface             min(dt_b[a_boundary])
local maximum -> surface       dt_b[peak]

The transform costs O(image pixels) per object type.

WHAT “DISTANCE” MEANS HERE, because three different numbers get called it:

centre_to_centre between the two centroids. Big for two large

objects that are touching.

centre_to_surface from a’s centroid to the nearest point on ANY b.

Asymmetric: a’s centre to b’s edge is not b’s centre to a’s edge, so both are emitted.

surface_to_surface closest point to closest point. ZERO when they

touch, and it is the number a biologist means by “how far apart are they”.

NO OBJECT-TYPE PREFIX ON THE COLUMNS. measure prefixes every measurement family with the object it belongs to, so a column called distance_to_own_boundary here reaches the database as cell_distance_to_own_boundary. Naming it cell_... here produced cell_cell_..., which is the shape of every doubled-prefix bug.

NEVER NaN WHERE A NUMBER IS MEANINGFUL. An object with no partner of the other type is inf – genuinely infinitely far, which is a fact – and NaN is reserved for “not measured”.

Functions

between_object_types(→ pandas.DataFrame)

Distances from every primary object to every other object type.

intensity_centre_offset([spacing])

How far each channel's intensity centre sits from the geometric one.

interior_distance_transform(mask[, spacing])

Distance from every point INSIDE an object to that object's boundary.

local_maxima(→ numpy.ndarray)

Coordinates of the intensity peaks inside one object.

maxima_distances([spacing])

Where each object's intensity peaks are, and what they are near.

object_distances([spacing])

Every distance this module measures, for one object type.

surface_distance_transform(mask[, spacing])

Distance from every point to the nearest object surface in mask.

Module Contents

spacr.object_distances.between_object_types(masks: Dict[str, numpy.ndarray], *, primary: str, spacing=None) pandas.DataFrame[source]

Distances from every primary object to every other object type.

Parameters:
  • masks – object type -> label image, all the same shape.

  • primary – the type whose objects are the rows.

  • spacing – voxel size, so the numbers carry physical units.

Returns:

one row per primary object, keyed on label.

THREE NUMBERS PER PAIR OF TYPES, because they answer three different questions – see the module docstring. Plus where the object sits inside itself and how close it is to the edge of the field, which is what says an object is clipped.

spacr.object_distances.intensity_centre_offset(mask, images, *, primary: str, channels: Sequence[int] = (), spacing=None) pandas.DataFrame[source]

How far each channel’s intensity centre sits from the geometric one.

Parameters:
  • mask – label image whose instances define rows and geometric centroids.

  • images – aligned intensity field with channels on its final axis, or a single intensity plane.

  • primary – name of the object type represented by mask.

POLARISATION IN ONE NUMBER. A uniformly stained object has an offset of about zero; one whose signal is all at one end does not, and no intensity summary says so.

spacr.object_distances.interior_distance_transform(mask, spacing=None)[source]

Distance from every point INSIDE an object to that object’s boundary.

Parameters:

mask – label or binary image containing the target objects.

The complement of surface_distance_transform(): run on the mask itself, so it is 0 outside and peaks at each object’s deepest point. Read at a centroid it says how far the centre is from its own rim, which is what makes a relative radial position possible.

spacr.object_distances.local_maxima(image, mask, label: int) numpy.ndarray[source]

Coordinates of the intensity peaks inside one object.

Parameters:
  • image – intensity image aligned with mask.

  • mask – labelled object image that limits the peak search.

  • label – object label whose interior is searched.

Returns:

an (n, ndim) array, possibly empty.

spacr.object_distances.maxima_distances(masks: Dict[str, numpy.ndarray], images, *, primary: str, channels: Sequence[int] = (), spacing=None) pandas.DataFrame[source]

Where each object’s intensity peaks are, and what they are near.

Parameters:
  • masks – object-type label images sharing the field geometry.

  • images – the field as (..., channel).

  • primary – object type whose labelled instances define output rows.

  • channels – which channels to find maxima in. Empty means all.

Returns:

one row per primary object, keyed on label.

spacr.object_distances.object_distances(masks: Dict[str, numpy.ndarray], images=None, *, primary: str, channels: Sequence[int] = (), spacing=None, maxima: bool = True) pandas.DataFrame[source]

Every distance this module measures, for one object type.

The one call the measure pipeline makes. Joined on label so it widens the object’s row like any other measurement family.

Parameters:
  • masks – object type -> label image.

  • images – the field, for the intensity-derived families. None skips them.

  • primary – object type whose instances define the returned rows.

  • maxima – whether to find local maxima. The most expensive part.

spacr.object_distances.surface_distance_transform(mask, spacing=None)[source]

Distance from every point to the nearest object surface in mask.

Parameters:

mask – label or binary image containing the target objects.

ZERO INSIDE AN OBJECT. distance_transform_edt measures distance to the nearest ZERO, so it is run on the INVERTED mask: the result is 0 on any labelled pixel and grows outward. That is what makes a lookup at another object’s centroid mean “distance to the nearest surface of this type”, and what makes two touching objects come out at 0.