Source code for tyssue.utils.utils

import logging
import warnings

import numpy as np
import pandas as pd

logger = logging.getLogger(name=__name__)


def _to_2d(df):
    df_2d = to_nd(df, 2)
    return df_2d


def _to_3d(df):
    df_3d = to_nd(df, 3)
    return df_3d


[docs]def to_nd(df, ndim): """ Give a new shape to an input data by duplicating its column. Parameters ---------- df : input data that will be reshape ndim : dimension of the new reshape data. Returns ------- df_nd : return array reshaped in ndim. """ df_nd = np.asarray(df).reshape((df.size, 1)) return df_nd
[docs]def combine_specs(*specs): combined = {} for spec in specs: for key in spec: if key in combined: combined[key].update(spec[key]) else: combined[key] = spec[key] return combined
[docs]def spec_updater(specs, new): """ Add element to the new dictionary to the specs dictionary. Update value if the key already exist. Parameters ---------- specs: specification that will be modified new: dictionary of new specification """ for key in new.keys(): if specs.get(key) is not None: specs[key].update(new[key]) else: specs[key] = new[key]
[docs]def set_data_columns(datasets, specs, reset=False): """Sets the columns of the dataframes in the datasets dictionnary to the uniform values in the specs sub-dictionnaries. Parameters ---------- datasets : dict of dataframes specs : dict of dicts reset : bool, default False For each key in specs, the value is a dictionnary whose keys are column names for the corresponding dataframe in datasets. If there is no such column in the dataframe, it is created. If the columns allready exists and reset is `True`, the new value is used. """ for name, spec in specs.items(): if not len(spec): continue if "setting" in name: continue df = datasets.get(name) if df is None: warnings.warn( f"There is no {name} dataset, so the {name}" " spec have no effect." ) continue for col, default in spec.items(): if col in df.columns and reset: logger.info( "Reseting column %s of the %s dataset with new specs", col, name ) if col not in df.columns or reset: df[col] = default
[docs]def data_at_opposite(sheet, edge_data, free_value=None): """ Returns a pd.DataFrame with the values of the input edge_data at the opposite edges. For free edges, optionaly replaces Nan values with free_value Parameters ---------- sheet: a :class:`Sheet` instance edge_data: dataframe contain value of edge Returns ------- opposite: pandas series contain value of opposite edge """ if isinstance(edge_data, pd.Series): opposite = pd.Series( edge_data.reindex(sheet.edge_df["opposite"]).to_numpy(), index=edge_data.index, ) elif isinstance(edge_data, pd.DataFrame): opposite = pd.DataFrame( edge_data.reindex(sheet.edge_df["opposite"]).to_numpy(), index=edge_data.index, columns=edge_data.columns, ) else: opposite = pd.DataFrame( np.asarray(edge_data).take(sheet.edge_df["opposite"].to_numpy(), axis=0), index=sheet.edge_df.index, ) if free_value is not None: opposite = opposite.replace(np.nan, free_value) return opposite
[docs]def get_sub_eptm(eptm, edges, copy=False): """ Define sub-epithelium corresponding to the edges. Parameters ---------- eptm: a :class:`Epithelium` instance edges: list of edges includes in the sub-epithelium Returns ------- sub_eptm: a :class:`Epithelium` instance """ from ..core.objects import Epithelium datasets = {} edge_df = eptm.edge_df.loc[edges] if edge_df.empty: warnings.warn("Sub epithelium appears to be empty") return None datasets["edge"] = edge_df datasets["vert"] = eptm.vert_df.loc[np.unique(edge_df["srce"])] datasets["face"] = eptm.face_df.loc[np.unique(edge_df["face"])] if "cell" in eptm.datasets: datasets["cell"] = eptm.cell_df.loc[np.unique(edge_df["cell"])] if copy: for elem, df in datasets.items(): datasets[elem] = df.copy() sub_eptm = Epithelium("sub", datasets, eptm.specs) sub_eptm.datasets["edge"]["edge_o"] = edges sub_eptm.datasets["edge"]["srce_o"] = edge_df["srce"] sub_eptm.datasets["edge"]["trgt_o"] = edge_df["trgt"] sub_eptm.datasets["edge"]["face_o"] = edge_df["face"] if "cell" in eptm.datasets: sub_eptm.datasets["edge"]["cell_o"] = edge_df["cell"] sub_eptm.datasets["vert"]["srce_o"] = np.unique(edge_df["srce"]) sub_eptm.datasets["face"]["face_o"] = np.unique(edge_df["face"]) if "cell" in eptm.datasets: sub_eptm.datasets["cell"]["cell_o"] = np.unique(edge_df["cell"]) sub_eptm.reset_index() sub_eptm.reset_topo() return sub_eptm
[docs]def single_cell(eptm, cell, copy=False): """ Define epithelium instance for all element to a define cell. Parameters ---------- eptm : a :class:`Epithelium` instance cell : identifier of a cell copy : bool, default `False` Returns ------- sub_etpm: class:'Epithelium' instance corresponding to the cell """ edges = eptm.edge_df[eptm.edge_df["cell"] == cell].index return get_sub_eptm(eptm, edges, copy)
[docs]def scaled_unscaled(func, scale, eptm, geom, args=(), kwargs={}, coords=None): """Scales the epithelium by an homotetic factor `scale`, applies the function `func`, and scales back to original size. Parameters ---------- func: the function to apply to the scaled epithelium scale: float, the scale to apply eptm: a :class:`Epithelium` instance geom: a :class:`Geometry` class args: sequence, the arguments to pass to func kwargs: dictionary, the keywords arguments to pass to func coords: the coordinates on which the scaling applies If the execution of function fails, the scaling is still reverted Returns ------- res: the result of the function func """ if coords is None: coords = eptm.coords geom.scale(eptm, scale, coords) geom.update_all(eptm) try: res = func(*args, **kwargs) finally: geom.scale(eptm, 1 / scale, coords) geom.update_all(eptm) return res
[docs]def modify_segments(eptm, modifiers): """Modifies the datasets of a segmented epithelium according to the passed modifiers. Parameters ---------- eptm : :class:`tyssue.Epithelium` modifiers : nested dictionnary Note ---- This functions assumes that the epithelium has a `segment_index` method as implemented in the :class:`tyssue.Monolayer`. Example ------- >>> modifiers = { >>> 'apical' : { >>> 'edge': {'line_tension': 1.}, >>> 'face': {'prefered_area': 0.2}, >>> }, >>> 'basal' : { >>> 'edge': {'line_tension': 3.}, >>> 'face': {'prefered_area': 0.1}, >>> } >>> modify_segments(monolayer, modifiers) >>> monolayer.ver_df.loc[monolayer.apical_edges, >>> 'line_tension'].unique()[0] == 1. True """ for segment, spec in modifiers.items(): for element, parameters in spec.items(): idx = eptm.segment_index(segment, element) for param_name, param_value in parameters.items(): eptm.datasets[element].loc[idx, param_name] = param_value
def _compute_ar(df, coords): u, v = coords major = np.ptp(df[u].values) minor = np.ptp(df[v].values) if major < minor: minor, major = major, minor return 0 if minor == 0 else major / minor
[docs]def ar_calculation(sheet, coords=["x", "y"]): """Calculates the aspect ratio of each face of the sheet Parameters ---------- eptm : a :class:`Sheet` object coords : list of str, optional, default ['x', 'y'] the coordinates on which to compute the aspect ratio Returns ------- AR: pandas series of aspect ratio for all faces. Note ---- As is the case in ImageJ, the returned aspect ratio is always higher than 1 """ srce_pos = sheet.upcast_srce(sheet.vert_df[sheet.coords]) srce_pos["face"] = sheet.edge_df["face"] return srce_pos.groupby("face").apply(_compute_ar, coords)
[docs]def get_next(eptm): """ Returns the indices of the next edge for each edge """ fs_indexed = ( eptm.edge_df[["face", "srce"]] .reset_index() .set_index(["face", "srce"], drop=False) ) ft_index = pd.MultiIndex.from_frame( eptm.edge_df[["face", "trgt"]], names=["face", "srce"] ) next_ = fs_indexed.loc[ft_index, "edge"].values return next_
# small utlity to swap apical and basal segments
[docs]def swap_apico_basal(organo): """Swap apical and basal segments of an organoid.""" for elem in ["vert", "face", "edge"]: swaped = organo.datasets[elem]["segment"].copy() swaped.loc[organo.segment_index("apical", elem)] = "basal" swaped.loc[organo.segment_index("basal", elem)] = "apical" organo.datasets[elem]["segment"] = swaped
[docs]def elem_centered_patch(eptm, elem_idx, neighbour_order, elem): """ Return subeptm centered on the element (cell or face) with index elem_idx with neighbour_order neighbours around it. Parameters ---------- eptm : a :class:`Epithelim` instance index : int, id of the center element neighbour_order: int, neighbourhood 'degree' around the center element Returns ------- patch: an object with the same class as eptm """ if elem not in ("face", "cell"): raise ValueError elems = pd.concat( [ pd.Series(elem_idx), eptm.get_neighborhood(elem_idx, neighbour_order, elem)[elem], ] ) print(elems, elem) edges = eptm.edge_df[eptm.edge_df[elem].isin(elems)].copy() vertices = eptm.vert_df.loc[np.unique(edges["srce"])].copy() if elem == "cell": faces = eptm.face_df.loc[np.unique(edges["face"])].copy() cells = eptm.cell_df.loc[elems].copy() elif "cell" in edges.columns: faces = eptm.face_df.loc[elems].copy() cells = eptm.cell_df.loc[np.unique(edges["cell"])].copy() else: faces = eptm.face_df.loc[elems].copy() cells = None pos = ( vertices[eptm.coords].values - vertices[eptm.coords].mean(axis=0).values[None, :] ) u, v, rotation = np.linalg.svd(pos, full_matrices=False) vertices[eptm.coords] = np.dot(pos, rotation.T) patch_dset = {"vert": vertices, "face": faces, "edge": edges} if cells is not None: patch_dset["cell"] = cells patch = eptm.__class__("patch", patch_dset, eptm.specs) patch.reset_index() return patch
[docs]def face_centered_patch(sheet, face, neighbour_order): """ Return subsheet centered on face with a distance of neighbour order around the face Parameters ---------- sheet : a :class:`Sheet` object face : int, id of the center face neighbour_order: int, number of neighbour around the center face Returns ------- patch: an object of the same class as the input object """ return elem_centered_patch(sheet, face, neighbour_order, "face")
[docs]def cell_centered_patch(eptm, cell, neighbour_order): """ Return subsheet centered on cell with a distance of neighbour order around the cell Parameters ---------- eptm : a :class:`Epithelium` instance face : int, id of the center face neighbour_order: int, number of neighbour around the center face Returns ------- patch: an object of the same class as the input object """ return elem_centered_patch(eptm, cell, neighbour_order, "cell")