Source code for pyclm.core.patterns.fbc_cell_movement

import numpy as np
import tifffile
from scipy.ndimage import distance_transform_edt
from scipy.spatial import KDTree
from scipy.stats import gaussian_kde
from skimage.measure import label, regionprops, regionprops_table

from .pattern import AcquiredImageRequest, DataDock, PatternMethod
from .zoo import ZooMeta


[docs] class PerCellPatternMethod(PatternMethod): """ Base class for the per-cell pattern methods. This class provides the basic structure for generating patterns based on the properties of segmented cells. Subclasses should implement the `process_prop` method to define how each cell's properties are used to generate the pattern, and include any additional parameters needed for the specific pattern in their `__init__` method. """ name = "per_cell_base" def __init__( self, channel=None, voronoi=False, gradient=False, direction=1, **kwargs ): super().__init__(**kwargs) if channel is None: raise AttributeError( "PerCellPatternModels must be provided a " 'segmentation channel in the .toml: e.g. channel = "638"' ) self.gradient = gradient self.voronoi = voronoi self.direction = direction self.channel = channel # request the segmentation of the provided channel, to be used by self.generate. # This will ensure that the data is available when generate is called. self.add_requirement(channel_name=channel, seg=True) def prop_vector(self, prop, vec): """ :param prop: regionprops prop :param vec: (y, x) vector """ bin_img = prop.image y = np.arange(bin_img.shape[0]) x = np.arange(bin_img.shape[1]) yy, xx = np.meshgrid(y, x, indexing="ij") y_center, x_center = prop.centroid_local dot_prod = (yy - y_center) * vec[0] + (xx - x_center) * vec[1] if self.gradient: framed = dot_prod * bin_img * (dot_prod > 0) out = framed / np.max(framed) else: out = (dot_prod > 0) * bin_img return out def process_prop(self, prop) -> np.ndarray: return prop.image def voronoi_rebuild(self, img): props_table = regionprops_table(img, properties=["centroid"]) pts = np.stack([props_table["centroid-0"], props_table["centroid-1"]], axis=-1) kdtree = KDTree(pts) h, w = self.pattern_shape y = np.arange(h) x = np.arange(w) yy, xx = np.meshgrid(y, x, indexing="ij") query_pts = np.stack([yy.flatten(), xx.flatten()], axis=-1) _, nn = kdtree.query(query_pts, 1) out = np.reshape(nn, (int(h), int(w))) return out def generate(self, context) -> np.ndarray: seg = context.segmentation(self.channel) if self.voronoi: px_dis = distance_transform_edt(seg == 0) seg = self.voronoi_rebuild(seg) seg = seg * (px_dis < 50) h, w = self.pattern_shape new_img = np.zeros((int(h), int(w)), dtype=np.float16) for prop in regionprops(seg): cell_stim = self.process_prop(prop) new_img[prop.bbox[0] : prop.bbox[2], prop.bbox[1] : prop.bbox[3]] += ( cell_stim ) new_img_clamped = np.clip(new_img, 0, 1).astype(np.float16) return new_img_clamped
[docs] class RotateCcwModel(PerCellPatternMethod): name = "rotate_ccw" zoo_meta = ZooMeta( source="mdck", kwargs={"channel": "seg_channel"}, title="Rotate CCW", description="Illuminates the half of each cell pointing counter-clockwise around the field center.", ) def __init__(self, channel=None, **kwargs): super().__init__(channel=channel, **kwargs) def process_prop(self, prop) -> np.ndarray: center_y, center_x = self.pattern_shape[0] / 2, self.pattern_shape[1] / 2 prop_centroid = prop.centroid vec = -(prop_centroid[1] - center_x), prop_centroid[0] - center_y return self.prop_vector(prop, vec)
[docs] class MoveOutModel(PerCellPatternMethod): name = "move_out" zoo_meta = ZooMeta( source="mdck", kwargs={"channel": "seg_channel"}, title="Move Out", description="Illuminates the outward-facing half of each cell.", ) def __init__(self, channel=None, **kwargs): super().__init__(channel=channel, **kwargs) def process_prop(self, prop) -> np.ndarray: center_y, center_x = self.pattern_shape[0] / 2, self.pattern_shape[1] / 2 prop_centroid = prop.centroid vec = prop_centroid[0] - center_y, (prop_centroid[1] - center_x) return self.prop_vector(prop, vec)
[docs] class MoveInModel(PerCellPatternMethod): name = "move_in" zoo_meta = ZooMeta( source="mdck", kwargs={"channel": "seg_channel"}, title="Move In", description="Illuminates the inward-facing half of each cell.", ) def __init__(self, channel=None, **kwargs): super().__init__(channel=channel, **kwargs) def process_prop(self, prop) -> np.ndarray: center_y, center_x = self.pattern_shape[0] / 2, self.pattern_shape[1] / 2 prop_centroid = prop.centroid vec = -(prop_centroid[0] - center_y), -(prop_centroid[1] - center_x) return self.prop_vector(prop, vec)
[docs] class MoveDownModel(PerCellPatternMethod): name = "move_down" def __init__(self, channel=None, **kwargs): super().__init__(channel=channel, **kwargs) def process_prop(self, prop) -> np.ndarray: center_y, center_x = self.pattern_shape[0] / 2, self.pattern_shape[1] / 2 prop_centroid = prop.centroid vec = (1, 0) return self.prop_vector(prop, vec)
[docs] class BounceModel(PerCellPatternMethod): name = "fb_bounce" def __init__(self, channel=None, t_loop=60, **kwargs): self.t_loop_s = t_loop * 60 self.down = True super().__init__(channel=channel, **kwargs) def process_prop(self, prop) -> np.ndarray: center_y, center_x = self.pattern_shape[0] / 2, self.pattern_shape[1] / 2 prop_centroid = prop.centroid vec = (1, 0) if self.down else (-1, 0) return self.prop_vector(prop, vec) def generate(self, context) -> np.ndarray: t = context.time t = t % self.t_loop_s halfway = self.t_loop_s / 2 if t > halfway: self.down = False return super().generate(context)