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 ( ...@@ -21,6 +21,7 @@ from extraction.core.functions.loaders import (
) )
from extraction.core.functions.utils import depth 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() CELL_FUNS, TRAPFUNS, FUNS = load_funs()
CUSTOM_FUNS, CUSTOM_ARGS = load_custom_args() CUSTOM_FUNS, CUSTOM_ARGS = load_custom_args()
RED_FUNS = load_redfuns() RED_FUNS = load_redfuns()
...@@ -177,11 +178,11 @@ class Extractor(ProcessABC): ...@@ -177,11 +178,11 @@ class Extractor(ProcessABC):
def load_custom_funs(self): def load_custom_funs(self):
""" """
Load any necessary parameters for functions. Define any custom functions to be functions of cell_masks and trap_image only.
These parameters must be loaded within an Extractor instance because they depend on the experiment's metadata.
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( funs = set(
[ [
fun fun
...@@ -190,18 +191,21 @@ class Extractor(ProcessABC): ...@@ -190,18 +191,21 @@ class Extractor(ProcessABC):
for fun in red for fun in red
] ]
) )
# consider only those already loaded from CUSTOM_FUNS
funs = funs.intersection(CUSTOM_FUNS.keys()) funs = funs.intersection(CUSTOM_FUNS.keys())
# find their arguments
ARG_VALS = { ARG_VALS = {
k: {k2: self.get_meta(k2) for k2 in v} k: {k2: self.get_meta(k2) for k2 in v}
for k, v in CUSTOM_ARGS.items() 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 = {} self._custom_funs = {}
for k, f in CUSTOM_FUNS.items(): for k, f in CUSTOM_FUNS.items():
def tmp(f): def tmp(f):
return lambda m, img: trap_apply( # pass extra arguments to custom function
f, m, img, **ARG_VALS.get(k, {}) return lambda cell_masks, trap_image: trap_apply(
f, cell_masks, trap_image, **ARG_VALS.get(k, {})
) )
self._custom_funs[k] = tmp(f) self._custom_funs[k] = tmp(f)
...@@ -209,6 +213,7 @@ class Extractor(ProcessABC): ...@@ -209,6 +213,7 @@ class Extractor(ProcessABC):
def load_funs(self): def load_funs(self):
self.load_custom_funs() self.load_custom_funs()
self._all_cell_funs = set(self._custom_funs.keys()).union(CELL_FUNS) self._all_cell_funs = set(self._custom_funs.keys()).union(CELL_FUNS)
# merge the two dicts
self._all_funs = {**self._custom_funs, **FUNS} self._all_funs = {**self._custom_funs, **FUNS}
def load_meta(self): def load_meta(self):
...@@ -328,16 +333,16 @@ class Extractor(ProcessABC): ...@@ -328,16 +333,16 @@ class Extractor(ProcessABC):
Parameters Parameters
---------- ----------
:param red_metrics: dict in which keys are reduction funcions and param red_metrics: dict
values are strings indicating the metric function dict for which keys are reduction funcions and values are strings indicating the metric function
:**kwargs: All other arguments, must include masks and traps. **kwargs: dict
All other arguments and must include masks and traps.
Returns Returns
------ ------
Dictionary of dataframes with the corresponding reductions and metrics nested. Dictionary of dataframes with the corresponding reductions and metrics nested.
""" """
# create dict of traps with reduction in the z-direction
reduced_traps = {} reduced_traps = {}
if traps is not None: if traps is not None:
for red_fun in red_metrics.keys(): for red_fun in red_metrics.keys():
...@@ -359,7 +364,7 @@ class Extractor(ProcessABC): ...@@ -359,7 +364,7 @@ class Extractor(ProcessABC):
def reduce_dims(self, img: np.array, method=None) -> np.array: 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: if method is None:
return img return img
...@@ -397,12 +402,6 @@ class Extractor(ProcessABC): ...@@ -397,12 +402,6 @@ class Extractor(ProcessABC):
Returns Returns
------- -------
dict dict
Examples
--------
FIXME: Add docs.
""" """
if tree is None: if tree is None:
# use default # use default
...@@ -618,6 +617,7 @@ class Extractor(ProcessABC): ...@@ -618,6 +617,7 @@ class Extractor(ProcessABC):
def get_meta(self, flds): def get_meta(self, flds):
if not hasattr(flds, "__iter__"): if not hasattr(flds, "__iter__"):
# make flds a list
flds = [flds] flds = [flds]
meta_short = {k.split("/")[-1]: v for k, v in self.meta.items()} meta_short = {k.split("/")[-1]: v for k, v in self.meta.items()}
return { return {
......
""" """
Base functions to extract information from a single cell Base functions to extract information from a single cell
These functions are automatically read, so only add new functions with 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.
the same arguments as the existing ones.
np.where is used to cover for cases where z>1
TODO: Implement membrane functions when needed
""" """
# Alan: why aren't we using skimage's regionprops?
import numpy as np import numpy as np
from scipy import ndimage from scipy import ndimage
from sklearn.cluster import KMeans from sklearn.cluster import KMeans
def area(cell_mask): 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) return np.sum(cell_mask, dtype=int)
def eccentricity(cell_mask): 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) min_ax, maj_ax = min_maj_approximation(cell_mask)
return np.sqrt(maj_ax**2 - min_ax**2) / maj_ax return np.sqrt(maj_ax**2 - min_ax**2) / maj_ax
...@@ -108,7 +116,10 @@ def max5px_med(cell_mask, trap_image): ...@@ -108,7 +116,10 @@ def max5px_med(cell_mask, trap_image):
max5px = np.mean(top_vals, dtype=float) max5px = np.mean(top_vals, dtype=float)
# find the median # find the median
med = np.median(sorted_vals) 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): def max2p5pc_med(cell_mask, trap_image):
...@@ -132,7 +143,10 @@ def max2p5pc_med(cell_mask, trap_image): ...@@ -132,7 +143,10 @@ def max2p5pc_med(cell_mask, trap_image):
# find mean of these highest pixels # find mean of these highest pixels
max2p5pc = np.mean(top_vals, dtype=float) max2p5pc = np.mean(top_vals, dtype=float)
med = np.median(sorted_vals) med = np.median(sorted_vals)
return max2p5pc / med if med == 0:
return np.nan
else:
return max2p5pc / med
def std(cell_mask, trap_image): def std(cell_mask, trap_image):
...@@ -148,11 +162,9 @@ 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) 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. Finds the medians of the major cluster after clustering the pixels in the cell into two clusters.
These medians might be useful if the cell has a large vacuole.
Parameters Parameters
---------- ----------
...@@ -162,24 +174,47 @@ def k2_top_median(cell_mask, trap_image): ...@@ -162,24 +174,47 @@ def k2_top_median(cell_mask, trap_image):
Returns Returns
------- -------
medians: tuple of floats median: float
The median of the major cluster and the median of the minor cluster. The median of the major cluster of two clusters
""" """
if np.any(cell_mask): if np.any(cell_mask):
X = trap_image[np.where(cell_mask)].reshape(-1, 1) X = trap_image[np.where(cell_mask)].reshape(-1, 1)
# cluster pixels in cell into two clusters # cluster pixels in cell into two clusters
kmeans = KMeans(n_clusters=2, random_state=0).fit(X) kmeans = KMeans(n_clusters=2, random_state=0).fit(X)
high_clust_id = kmeans.cluster_centers_.argmax() high_clust_id = kmeans.cluster_centers_.argmax()
low_clust_id = kmeans.cluster_centers_.argmin()
# find the median of pixels in the largest cluster # find the median of pixels in the largest cluster
major_cluster = X[kmeans.predict(X) == high_clust_id] major_cluster = X[kmeans.predict(X) == high_clust_id]
major_median = np.median(major_cluster, axis=None) 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 # find the median of pixels in the smallest cluster
minor_cluster = X[kmeans.predict(X) == low_clust_id] minor_cluster = X[kmeans.predict(X) == low_clust_id]
minor_median = np.median(minor_cluster, axis=None) minor_median = np.median(minor_cluster, axis=None)
return major_median, minor_median return minor_median
else: else:
return np.nan, np.nan return np.nan
def volume(cell_mask): def volume(cell_mask):
...@@ -196,6 +231,14 @@ def volume(cell_mask): ...@@ -196,6 +231,14 @@ def volume(cell_mask):
def conical_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) padded = np.pad(cell_mask, 1, mode="constant", constant_values=0)
nearest_neighbor = ( nearest_neighbor = (
ndimage.morphology.distance_transform_edt(padded == 1) * padded ndimage.morphology.distance_transform_edt(padded == 1) * padded
...@@ -212,7 +255,6 @@ def spherical_volume(cell_mask): ...@@ -212,7 +255,6 @@ def spherical_volume(cell_mask):
cell_mask: 2d array cell_mask: 2d array
Segmentation mask for the cell Segmentation mask for the cell
''' '''
area = cell_mask.sum() area = cell_mask.sum()
r = np.sqrt(area / np.pi) r = np.sqrt(area / np.pi)
return (4 * np.pi * r**3) / 3 return (4 * np.pi * r**3) / 3
......
...@@ -3,22 +3,38 @@ import numpy as np ...@@ -3,22 +3,38 @@ import numpy as np
def trap_apply(cell_fun, cell_masks, *args, **kwargs): 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) Parameters
:param cell_masks: (numpy 3d array) cells' segmentation mask ----------
:param trap_image: (Optional) the image for the trap in which the cell is (all cell_fun: function
channels) Function to apply to the cell (from extraction/cell.py)
:**kwargs: parameters to pass if needed for custom functions 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]),) 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] return [cell_fun(cell_masks[..., i], *args, **kwargs) for i in cells_iter]
def reduce_z(trap_image, fun): 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): if isinstance(fun, np.ufunc):
# optimise the reduction function if possible
return fun.reduce(trap_image, axis=2) return fun.reduce(trap_image, axis=2)
else: else:
return np.apply_along_axis(fun, 2, trap_image) return np.apply_along_axis(fun, 2, trap_image)
...@@ -7,9 +7,15 @@ from extraction.core.functions.custom import localisation ...@@ -7,9 +7,15 @@ from extraction.core.functions.custom import localisation
from extraction.core.functions.distributors import trap_apply from extraction.core.functions.distributors import trap_apply
from extraction.core.functions.math_utils import div0 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(): 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 { return {
f[0]: f[1] f[0]: f[1]
for f in getmembers(cell) for f in getmembers(cell)
...@@ -20,15 +26,16 @@ def load_cellfuns_core(): ...@@ -20,15 +26,16 @@ def load_cellfuns_core():
def load_custom_args(): def load_custom_args():
""" """
Load custom functions. If they have extra arguments (starting at index 2, Load custom functions from the localisation module and return the functions and any additional arguments, other than cell_mask and trap_image, as dictionaries.
after mask and image) also load these.
""" """
# load functions from module
funs = { funs = {
f[0]: f[1] f[0]: f[1]
for f in getmembers(localisation) for f in getmembers(localisation)
if isfunction(f[1]) if isfunction(f[1])
and f[1].__module__.startswith("extraction.core.functions") and f[1].__module__.startswith("extraction.core.functions")
} }
# load additional arguments if cell_mask and trap_image are arguments
args = { args = {
k: getfullargspec(v).args[2:] k: getfullargspec(v).args[2:]
for k, v in funs.items() for k, v in funs.items()
...@@ -36,7 +43,7 @@ def load_custom_args(): ...@@ -36,7 +43,7 @@ def load_custom_args():
getfullargspec(v).args getfullargspec(v).args
) )
} }
# return dictionaries of functions and of arguments
return ( return (
{k: funs[k] for k in args.keys()}, {k: funs[k] for k in args.keys()},
{k: v for k, v in args.items() if v}, {k: v for k, v in args.items() if v},
...@@ -45,31 +52,32 @@ def load_custom_args(): ...@@ -45,31 +52,32 @@ def load_custom_args():
def load_cellfuns(): def load_cellfuns():
""" """
Input: 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.
Returns:
Dict(str, function)
""" """
# 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() cell_funs = load_cellfuns_core()
# create a dict of functions that apply the core functions to an array of cell_masks
CELLFUNS = {} CELLFUNS = {}
for k, f in cell_funs.items(): for f_name, f in cell_funs.items():
if isfunction(f): if isfunction(f):
def tmp(f): def tmp(f):
args = getfullargspec(f).args args = getfullargspec(f).args
if len(args) == 1: if len(args) == 1:
# function that applies f to m, an array of masks
return lambda m, _: trap_apply(f, m) return lambda m, _: trap_apply(f, m)
else: else:
# function that applies f to m and img, the trap_image
return lambda m, img: trap_apply(f, m, img) return lambda m, img: trap_apply(f, m, img)
CELLFUNS[k] = tmp(f) CELLFUNS[f_name] = tmp(f)
return CELLFUNS return CELLFUNS
def load_trapfuns(): 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 = { TRAPFUNS = {
f[0]: f[1] f[0]: f[1]
...@@ -86,12 +94,13 @@ def load_funs(): ...@@ -86,12 +94,13 @@ def load_funs():
""" """
CELLFUNS = load_cellfuns() CELLFUNS = load_cellfuns()
TRAPFUNS = load_trapfuns() TRAPFUNS = load_trapfuns()
# return dict of cell funs, dict of trap funs, and dict of both
return CELLFUNS, TRAPFUNS, {**TRAPFUNS, **CELLFUNS} return CELLFUNS, TRAPFUNS, {**TRAPFUNS, **CELLFUNS}
def load_redfuns(): # TODO make defining reduction functions more flexible 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 = { RED_FUNS = {
"np_max": np.maximum, "np_max": np.maximum,
......
...@@ -2,9 +2,17 @@ import numpy as np ...@@ -2,9 +2,17 @@ import numpy as np
def div0(a, b, fill=0): def div0(a, b, fill=0):
"""a / b, divide by 0 -> `fill` """
div0( [-1, 0, 1], 0, fill=np.nan) -> [nan nan nan] Divide array a by array b.
div0( 1, 0, fill=np.inf ) -> inf
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"): with np.errstate(divide="ignore", invalid="ignore"):
c = np.true_divide(a, b) 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