spacr.bystanders¶
Label uninfected cells as bystanders or distal by distance to infection.
spaCR measures each cell on its own, but an infected cell can change the phenotype of its neighbours. Without a neighbourhood column, an uninfected cell touching an infected one and an uninfected cell on the far side of the well are indistinguishable rows.
This has two consequences. A bystander phenotype cannot be detected, because no column separates a bystander from a distant uninfected cell. In addition, if bystanders are phenotypically shifted and pooled into “uninfected”, the control population is a mixture: its variance is inflated and the effect size of every infection comparison is reduced.
Distances are not recomputed here. spacr.object_distances computes
a Euclidean distance transform per object type once, at O(image pixels)
rather than O(pairs), and this module reads that field and applies a
threshold and a label.
The threshold is a length, not a pixel count. It is expressed as a multiple of the measured cell diameter, so it has the same meaning on a 20x and a 63x acquisition and on a plate whose cells are larger. A fixed micron value would suit one dataset and not the next.
Functions¶
|
Label every cell infected, bystander or distal. |
|
Summarise one measured feature per neighbourhood state. |
|
Count cells in each neighbourhood state, including empty states. |
|
Distance from each cell to the nearest infected cell's surface. |
|
Fraction of each cell's |
|
The bystander reach in the units |
Module Contents¶
- spacr.bystanders.classify(cell_mask, infected_labels: Iterable[int], *, reach: float, spacing=None) pandas.DataFrame[source]¶
Label every cell infected, bystander or distal.
- Parameters:
cell_mask – the cell label image.
infected_labels – labels of the cells carrying a pathogen.
reach – how close an uninfected cell must be to count as a bystander, in the same units as
spacing. Seereach_from_diameter().spacing – voxel size.
- Returns:
label,distance_to_infectedandneighbourhood.
A non-positive reach makes every uninfected cell distal. This turns the bystander split off without a second setting, and a reach that is not a finite number is treated the same way.
A well with no parasites reports no bystanders. This follows from
distance_to_infectedreturning infinity there, not from a special case.
- spacr.bystanders.compare(frame: pandas.DataFrame, feature: str, *, statuses: Sequence[str] = STATUSES) pandas.DataFrame[source]¶
Summarise one measured feature per neighbourhood state.
- Parameters:
frame – measurements carrying a
neighbourhoodcolumn, asclassify()produces it, joined to the feature table.feature – the column to summarise.
statuses – which neighbourhoods to report, in order.
- Returns:
neighbourhood,n,mean,stdandmedian.
Splitting “uninfected” into bystander and distal rows lets a bystander effect appear as a difference between groups instead of only as a wider control distribution.
Every requested status is a row, with
n = 0andNaNstatistics where the frame has none, so results from different plates have the same rows. A missingfeatureorneighbourhoodcolumn also returns these empty rows.No test statistic is computed. The choice of test depends on the design (pairing, nesting of wells, the null hypothesis) and is left to the caller; this function returns the per-group summaries.
- spacr.bystanders.counts(frame: pandas.DataFrame) Dict[str, int][source]¶
Count cells in each neighbourhood state, including empty states.
- Parameters:
frame – the output of
classify().- Returns:
every name in
STATUSES, zero where absent.
Every key is always present, so a caller comparing wells receives 0 for an absent state rather than a
KeyError.
- spacr.bystanders.distance_to_infected(cell_mask, infected_labels: Iterable[int], *, spacing=None) pandas.DataFrame[source]¶
Distance from each cell to the nearest infected cell’s surface.
- Parameters:
cell_mask – the cell label image.
infected_labels – labels of the cells carrying a pathogen.
spacing – voxel size, so the distances carry physical units.
- Returns:
labelanddistance_to_infected, one row per cell.
An infected cell reads 0 and its immediate neighbour reads one pixel step, not 0. The field is zero on every infected pixel and grows outward, so a cell that shares a border with an infected one has its nearest pixel one step away: the minimum over that cell is 1. Only a cell whose own pixels are infected reads 0.
surface_distance_transform’s docstring says “two touching objects come out at 0”, and that is true of the case it describes – reading the field at a point INSIDE the other object, which is what an overlapping or contained object gives. Two disjoint labels that merely abut do not overlap, so they do not. A threshold expressed in cell diameters is far larger than one pixel either way, so a value of 1 for an adjacent cell is expected.Every distance is
infwhen the image has no infected cell, rather than 0 or NaN: infinity keepsdistance <= reachFalse for every finite reach.
- spacr.bystanders.neighbourhood(cell_mask, infected_labels: Iterable[int], *, k: int = 6, spacing=None) pandas.DataFrame[source]¶
Fraction of each cell’s
knearest neighbours that are infected.- Parameters:
cell_mask – the cell label image.
infected_labels – labels of the cells carrying a pathogen.
k – how many neighbours to count, excluding the cell itself.
spacing – voxel size, so “nearest” means nearest in real space rather than in pixels on an anisotropic stack.
- Returns:
label,neighbours_counted,neighbours_infectedandlocal_infected_fraction.
classify()applies a threshold to one distance. This function measures the infected share of the neighbourhood, which separates a bystander at the edge of an infection focus from one in its middle. Neighbours are found among cell centroids.A cell is never counted as its own neighbour, and infected cells also receive a fraction, so an infected cell among uninfected ones can be distinguished from one inside a cluster.
neighbours_countedis reported rather than assumed to bek. A field with fewer thank + 1cells supplies fewer thankneighbours, and the column gives the denominator actually used. Fields with fewer than two cells returnNaNfractions.
- spacr.bystanders.reach_from_diameter(cell_diameter: float, diameters: float = DEFAULT_REACH_IN_DIAMETERS) float[source]¶
The bystander reach in the units
cell_diameteris measured in.- Parameters:
cell_diameter – the plate’s measured cell diameter, from
spacr.diameter.diameters – how many diameters count as neighbouring.
- Returns:
a distance in the same units, never negative.
Kept as its own function so the conversion is visible in one place and a caller can see that the reach is derived rather than chosen.