Newer
Older
"""
Base functions to extract information from a single cell
These functions are automatically read by extractor.py, and so can only have the cell_mask and trap_image as inputs and must return only one value.
They assume that there are no NaNs in the image.
We use bottleneck when it performs faster than numpy:
- Median
- values containing NaNs (We make sure this does not happen)
import math
import typing as t
import bottleneck as bn
import faiss
import numpy as np
from scipy import ndimage
def area(cell_mask) -> int:
"""
Find the area of a cell mask
Parameters
----------
cell_mask: 2d array
Segmentation mask for the cell
"""
def eccentricity(cell_mask) -> float:
"""
Find the eccentricity using the approximate major and minor axes
Parameters
----------
cell_mask: 2d array
Segmentation mask for the cell
"""
min_ax, maj_ax = min_maj_approximation(cell_mask)
return np.sqrt(maj_ax**2 - min_ax**2) / maj_ax
def mean(cell_mask, trap_image) -> float:
"""
Finds the mean of the pixels in the cell.
Parameters
----------
cell_mask: 2d array
Segmentation mask for the cell
trap_image: 2d array
"""
return np.mean(trap_image[cell_mask])
def median(cell_mask, trap_image) -> int:
"""
Finds the median of the pixels in the cell.
Parameters
----------
cell_mask: 2d array
Segmentation mask for the cell
trap_image: 2d array
"""
return bn.median(trap_image[cell_mask])
def max2p5pc(cell_mask, trap_image) -> float:
"""
Finds the mean of the brightest 2.5% of pixels in the cell.
Parameters
----------
cell_mask: 2d array
Segmentation mask for the cell
trap_image: 2d array
"""
# number of pixels in mask
npixels = np.sum(cell_mask)
n_top = int(np.ceil(npixels * 0.025))
# sort pixels in cell and find highest 2.5%
top_values = bn.partition(pixels, len(pixels) - n_top)[-n_top:]
def max5px(cell_mask, trap_image) -> float:
"""
Finds the mean of the five brightest pixels in the cell.
Parameters
----------
cell_mask: 2d array
Segmentation mask for the cell
trap_image: 2d array
"""
# sort pixels in cell
pixels = trap_image[cell_mask]
top_values = bn.partition(pixels, len(pixels) - 5)[-5:]
max5px = np.mean(top_values)
"""
Finds the standard deviation of the values of the pixels in the cell.
Parameters
----------
cell_mask: 2d array
Segmentation mask for the cell
trap_image: 2d array
"""
return np.std(trap_image[cell_mask])
Finds the medians of the major cluster after clustering the pixels in the cell into two clusters.
Parameters
----------
cell_mask: 2d array
Segmentation mask for the cell
trap_image: 2d array
if bn.anynan(trap_image):
cell_mask[np.isnan(trap_image)] = False
X = trap_image[cell_mask].reshape(-1, 1).astype(np.float32)
# cluster pixels in cell into two clusters
indices = faiss.IndexFlatL2(X.shape[1])
# (n_clusters=2, random_state=0).fit(X)
_, indices = indices.search(X, k=2)
high_indices = np.argmax(indices, axis=1).astype(bool)
# find the median of pixels in the largest cluster
# high_masks = np.logical_xor( # Use casting to obtain masks
# high_indices.reshape(-1, 1), np.tile((0, 1), X.shape[0]).reshape(-1, 2)
# )
major_median = bn.median(X[high_indices])
def volume(cell_mask) -> float:
Estimates the volume of the cell assuming it is an ellipsoid with the mask providing a cross-section through the median plane of the ellipsoid.
Parameters
----------
cell_mask: 2d array
Segmentation mask for the cell
"""
min_ax, maj_ax = min_maj_approximation(cell_mask)
"""
Estimates the volume of the cell
Parameters
----------
cell_mask: 2D array
Segmentation mask for the cell
"""
padded = np.pad(cell_mask, 1, mode="constant", constant_values=0)
nearest_neighbor = (
ndimage.morphology.distance_transform_edt(padded == 1) * padded
)
return 4 * np.sum(nearest_neighbor)
Estimates the volume of the cell assuming it is a sphere with the mask providing a cross-section through the median plane of the sphere.
Parameters
----------
cell_mask: 2d array
Segmentation mask for the cell
"""
total_area = area(cell_mask)
r = math.sqrt(total_area / np.pi)
return (4 * np.pi * r**3) / 3
def min_maj_approximation(cell_mask) -> t.Tuple[int]:
Finds the lengths of the minor and major axes of an ellipse from a cell mask.
Parameters
----------
cell_mask: 3d array
Segmentation masks for cells
"""
# pad outside with zeros so that the distance transforms have no edge artifacts
padded = np.pad(cell_mask, 1, mode="constant", constant_values=0)
# get the distance from the edge, masked
nn = ndimage.morphology.distance_transform_edt(padded == 1) * padded
# get the distance from the top of the cone, masked
dn = ndimage.morphology.distance_transform_edt(nn - nn.max()) * padded
# get the size of the top of the cone (points that are equally maximal)
cone_top = ndimage.morphology.distance_transform_edt(dn == 0) * padded
# minor axis = largest distance from the edge of the ellipse
min_ax = np.round(np.max(nn))
# major axis = largest distance from the cone top
# + distance from the center of cone top to edge of cone top
maj_ax = np.round(np.max(dn) + np.sum(cone_top) / 2)