Skip to content
Snippets Groups Projects
Commit 4fca1c47 authored by pswain's avatar pswain
Browse files

more docs

parent 529b3208
No related branches found
No related tags found
No related merge requests found
......@@ -21,6 +21,7 @@ from extraction.core.functions.loaders import (
)
from extraction.core.functions.utils import depth
# Global parameters used to load functions that either analyse cells or their background. These global parameters both allow the functions to be stored in a dictionary for access only on demand and to be defined simply in extraction/core/functions.
CELL_FUNS, TRAPFUNS, FUNS = load_funs()
CUSTOM_FUNS, CUSTOM_ARGS = load_custom_args()
RED_FUNS = load_redfuns()
......@@ -177,11 +178,11 @@ class Extractor(ProcessABC):
def load_custom_funs(self):
"""
Load any necessary parameters for functions.
These parameters must be loaded within an Extractor instance because they depend on the experiment's metadata.
Define any custom functions to be functions of cell_masks and trap_image only.
Alan: this is challenging to follow!
Any other parameters are taken from the experiment's metadata and automatically applied. These parameters therefore must be loaded within an Extractor instance.
"""
# find functions specified in params.tree
funs = set(
[
fun
......@@ -190,18 +191,21 @@ class Extractor(ProcessABC):
for fun in red
]
)
# consider only those already loaded from CUSTOM_FUNS
funs = funs.intersection(CUSTOM_FUNS.keys())
# find their arguments
ARG_VALS = {
k: {k2: self.get_meta(k2) for k2 in v}
for k, v in CUSTOM_ARGS.items()
}
# self._custom_funs = {trap_apply(CUSTOM_FUNS[fun],])
# define custom functions - those with extra arguments other than cell_masks and trap_image - as functions of two variables
self._custom_funs = {}
for k, f in CUSTOM_FUNS.items():
def tmp(f):
return lambda m, img: trap_apply(
f, m, img, **ARG_VALS.get(k, {})
# pass extra arguments to custom function
return lambda cell_masks, trap_image: trap_apply(
f, cell_masks, trap_image, **ARG_VALS.get(k, {})
)
self._custom_funs[k] = tmp(f)
......@@ -209,6 +213,7 @@ class Extractor(ProcessABC):
def load_funs(self):
self.load_custom_funs()
self._all_cell_funs = set(self._custom_funs.keys()).union(CELL_FUNS)
# merge the two dicts
self._all_funs = {**self._custom_funs, **FUNS}
def load_meta(self):
......@@ -328,16 +333,16 @@ class Extractor(ProcessABC):
Parameters
----------
:param red_metrics: dict in which keys are reduction funcions and
values are strings indicating the metric function
:**kwargs: All other arguments, must include masks and traps.
param red_metrics: dict
dict for which keys are reduction funcions and values are strings indicating the metric function
**kwargs: dict
All other arguments and must include masks and traps.
Returns
------
Dictionary of dataframes with the corresponding reductions and metrics nested.
"""
# create dict of traps with reduction in the z-direction
reduced_traps = {}
if traps is not None:
for red_fun in red_metrics.keys():
......@@ -359,7 +364,7 @@ class Extractor(ProcessABC):
def reduce_dims(self, img: np.array, method=None) -> np.array:
"""
Collapse a z-stack into a single file. It may perform a null operation.
Collapse a z-stack into 2d array. It may perform a null operation.
"""
if method is None:
return img
......@@ -397,12 +402,6 @@ class Extractor(ProcessABC):
Returns
-------
dict
Examples
--------
FIXME: Add docs.
"""
if tree is None:
# use default
......@@ -618,6 +617,7 @@ class Extractor(ProcessABC):
def get_meta(self, flds):
if not hasattr(flds, "__iter__"):
# make flds a list
flds = [flds]
meta_short = {k.split("/")[-1]: v for k, v in self.meta.items()}
return {
......
"""
Base functions to extract information from a single cell
These functions are automatically read, so only add new functions with
the same arguments as the existing ones.
np.where is used to cover for cases where z>1
TODO: Implement membrane functions when needed
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.
"""
# Alan: why aren't we using skimage's regionprops?
import numpy as np
from scipy import ndimage
from sklearn.cluster import KMeans
def area(cell_mask):
# find the area of a cell mask
"""
Find the area of a cell mask
Parameters
----------
cell_mask: 2d array
Segmentation mask for the cell
"""
return np.sum(cell_mask, dtype=int)
def eccentricity(cell_mask):
"""
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
......@@ -108,7 +116,10 @@ def max5px_med(cell_mask, trap_image):
max5px = np.mean(top_vals, dtype=float)
# find the median
med = np.median(sorted_vals)
return max5px / med
if med == 0:
return np.nan
else:
return max5px / med
def max2p5pc_med(cell_mask, trap_image):
......@@ -132,7 +143,10 @@ def max2p5pc_med(cell_mask, trap_image):
# find mean of these highest pixels
max2p5pc = np.mean(top_vals, dtype=float)
med = np.median(sorted_vals)
return max2p5pc / med
if med == 0:
return np.nan
else:
return max2p5pc / med
def std(cell_mask, trap_image):
......@@ -148,11 +162,9 @@ def std(cell_mask, trap_image):
return np.std(trap_image[np.where(cell_mask)], dtype=float)
def k2_top_median(cell_mask, trap_image):
def k2_major_median(cell_mask, trap_image):
"""
Finds the medians of the major and minor clusters after clustering the pixels in the cell into two clusters.
These medians might be useful if the cell has a large vacuole.
Finds the medians of the major cluster after clustering the pixels in the cell into two clusters.
Parameters
----------
......@@ -162,24 +174,47 @@ def k2_top_median(cell_mask, trap_image):
Returns
-------
medians: tuple of floats
The median of the major cluster and the median of the minor cluster.
median: float
The median of the major cluster of two clusters
"""
if np.any(cell_mask):
X = trap_image[np.where(cell_mask)].reshape(-1, 1)
# cluster pixels in cell into two clusters
kmeans = KMeans(n_clusters=2, random_state=0).fit(X)
high_clust_id = kmeans.cluster_centers_.argmax()
low_clust_id = kmeans.cluster_centers_.argmin()
# find the median of pixels in the largest cluster
major_cluster = X[kmeans.predict(X) == high_clust_id]
major_median = np.median(major_cluster, axis=None)
return major_median
else:
return np.nan
def k2_minor_median(cell_mask, trap_image):
"""
Finds the median of the minor 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
Returns
-------
median: float
The median of the minor cluster.
"""
if np.any(cell_mask):
X = trap_image[np.where(cell_mask)].reshape(-1, 1)
# cluster pixels in cell into two clusters
kmeans = KMeans(n_clusters=2, random_state=0).fit(X)
low_clust_id = kmeans.cluster_centers_.argmin()
# find the median of pixels in the smallest cluster
minor_cluster = X[kmeans.predict(X) == low_clust_id]
minor_median = np.median(minor_cluster, axis=None)
return major_median, minor_median
return minor_median
else:
return np.nan, np.nan
return np.nan
def volume(cell_mask):
......@@ -196,6 +231,14 @@ def volume(cell_mask):
def conical_volume(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
......@@ -212,7 +255,6 @@ def spherical_volume(cell_mask):
cell_mask: 2d array
Segmentation mask for the cell
'''
area = cell_mask.sum()
r = np.sqrt(area / np.pi)
return (4 * np.pi * r**3) / 3
......
......@@ -3,22 +3,38 @@ import numpy as np
def trap_apply(cell_fun, cell_masks, *args, **kwargs):
"""
Apply a cell_function to a mask, trap_image pair
Apply a cell_function to a mask and a trap_image.
:param cell_fun: function to apply to a cell (from extraction/cell.py)
:param cell_masks: (numpy 3d array) cells' segmentation mask
:param trap_image: (Optional) the image for the trap in which the cell is (all
channels)
:**kwargs: parameters to pass if needed for custom functions
Parameters
----------
cell_fun: function
Function to apply to the cell (from extraction/cell.py)
cell_masks: 3d array
Segmentation masks for the cells
*args: tuple
Trap_image and any other arguments to pass if needed to custom functions.
**kwargs: dict
Keyword arguments to pass if needed to custom functions.
"""
# find an index for each cell in the trap
cells_iter = (*range(cell_masks.shape[2]),)
# apply cell_fun to each cell and return the results as a list
return [cell_fun(cell_masks[..., i], *args, **kwargs) for i in cells_iter]
def reduce_z(trap_image, fun):
# Optimise the reduction function if possible
"""
Reduce the trap_image to 2d.
Parameters
----------
trap_image: array
Images for all the channels associated with a trap
fun: function
Function to execute the reduction
"""
if isinstance(fun, np.ufunc):
# optimise the reduction function if possible
return fun.reduce(trap_image, axis=2)
else:
return np.apply_along_axis(fun, 2, trap_image)
......@@ -7,9 +7,15 @@ from extraction.core.functions.custom import localisation
from extraction.core.functions.distributors import trap_apply
from extraction.core.functions.math_utils import div0
"""
Load functions for analysing cells and their background.
Note that inspect.getmembers returns a list of function names and functions, and inspect.getfullargspec returns a function's arguments.
"""
def load_cellfuns_core():
# Generate str -> trap_function dict from functions in core.cell
"""
Load functions from the cell module and return as a dict.
"""
return {
f[0]: f[1]
for f in getmembers(cell)
......@@ -20,15 +26,16 @@ def load_cellfuns_core():
def load_custom_args():
"""
Load custom functions. If they have extra arguments (starting at index 2,
after mask and image) also load these.
Load custom functions from the localisation module and return the functions and any additional arguments, other than cell_mask and trap_image, as dictionaries.
"""
# load functions from module
funs = {
f[0]: f[1]
for f in getmembers(localisation)
if isfunction(f[1])
and f[1].__module__.startswith("extraction.core.functions")
}
# load additional arguments if cell_mask and trap_image are arguments
args = {
k: getfullargspec(v).args[2:]
for k, v in funs.items()
......@@ -36,7 +43,7 @@ def load_custom_args():
getfullargspec(v).args
)
}
# return dictionaries of functions and of arguments
return (
{k: funs[k] for k in args.keys()},
{k: v for k, v in args.items() if v},
......@@ -45,31 +52,32 @@ def load_custom_args():
def load_cellfuns():
"""
Input:
Returns:
Dict(str, function)
Creates a dict of core functions that can be used on an array of cell_masks.
The core functions only work on a single mask.
"""
# Generate str -> trap_function dict from core.cell and core.trap functions
# create dict of the core functions from cell.py - these functions apply to a single mask
cell_funs = load_cellfuns_core()
# create a dict of functions that apply the core functions to an array of cell_masks
CELLFUNS = {}
for k, f in cell_funs.items():
for f_name, f in cell_funs.items():
if isfunction(f):
def tmp(f):
args = getfullargspec(f).args
if len(args) == 1:
# function that applies f to m, an array of masks
return lambda m, _: trap_apply(f, m)
else:
# function that applies f to m and img, the trap_image
return lambda m, img: trap_apply(f, m, img)
CELLFUNS[k] = tmp(f)
CELLFUNS[f_name] = tmp(f)
return CELLFUNS
def load_trapfuns():
"""
Load functions that are applied to an entire trap (or tile or subsection of a given image) rather than to single cells.
Load functions that are applied to an entire trap or tile or subsection of an image rather than to single cells.
"""
TRAPFUNS = {
f[0]: f[1]
......@@ -86,12 +94,13 @@ def load_funs():
"""
CELLFUNS = load_cellfuns()
TRAPFUNS = load_trapfuns()
# return dict of cell funs, dict of trap funs, and dict of both
return CELLFUNS, TRAPFUNS, {**TRAPFUNS, **CELLFUNS}
def load_redfuns(): # TODO make defining reduction functions more flexible
"""
Load z-stack reduction functions.
Load functions to reduce the z-stack to two dimensions.
"""
RED_FUNS = {
"np_max": np.maximum,
......
......@@ -2,9 +2,17 @@ import numpy as np
def div0(a, b, fill=0):
"""a / b, divide by 0 -> `fill`
div0( [-1, 0, 1], 0, fill=np.nan) -> [nan nan nan]
div0( 1, 0, fill=np.inf ) -> inf
"""
Divide array a by array b.
If the result is a scalar and infinite, return fill.
If the result contain elements that are infinite, replace these elements with fill.
Parameters
----------
a: array
b: array
"""
with np.errstate(divide="ignore", invalid="ignore"):
c = np.true_divide(a, b)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment