Source code for tyssue.topology.sheet_topology

import logging
import warnings

import numpy as np
import pandas as pd

from .base_topology import add_vert, close_face, collapse_edge, remove_face
from .base_topology import split_vert as base_split_vert

logger = logging.getLogger(name=__name__)
MAX_ITER = 100


[docs]def split_vert( sheet, vert, face=None, multiplier=1.5, reindex=True, recenter=False, epsilon=None ): """Splits a vertex towards the center of the face. This operation removes the face `face` from the neighborhood of the vertex. Returns a list of the new edge's indices in edge_df. This should be two: the edge with vert as the srce and the newly created vertex as the trgt and the reverse edge with vert as the trgt and the new one as the srce """ # Get the value for the length of the new edge if epsilon is None: epsilon = sheet.settings.get("threshold_length", 0.1) * multiplier else: warnings.warn( "The epsilon argument is deprecated and will be removed" " in a future version. " "The length of the new edge should be set by " "`sheet.settings['threshold_length]*multiplier` " ) if face is None: face = np.random.choice(sheet.edge_df[sheet.edge_df["srce"] == vert]["face"]) face_edges = sheet.edge_df.query(f"face == {face}") (prev_v,) = face_edges[face_edges["trgt"] == vert]["srce"] (next_v,) = face_edges[face_edges["srce"] == vert]["trgt"] connected = sheet.edge_df[ sheet.edge_df["trgt"].isin((next_v, prev_v)) | sheet.edge_df["srce"].isin((next_v, prev_v)) ] base_split_vert(sheet, vert, face, connected, epsilon, recenter) new_edges = [] for face_ in connected["face"]: new_edge = close_face(sheet, face_) if new_edge is not None: new_edges.append(new_edge) if reindex: sheet.reset_index() sheet.reset_topo() return new_edges
[docs]def type1_transition(sheet, edge01, *, remove_tri_faces=True, multiplier=1.5): """Performs a type 1 transition around the edge edge01 See ../../doc/illus/t1_transition.png for a sketch of the definition of the vertices and cells letterings See Finegan et al. for a description of the algotithm https://doi.org/10.1101/704932 Parameters ---------- sheet : a `Sheet` instance edge_01 : int index of the edge around which the transition takes place epsilon : float, optional, deprecated default 0.1, the initial length of the new edge, in case "threshold_length" is not in the sheet.settings remove_tri_faces : bool, optional if True (the default), will remove triangular cells after the T1 transition is performed multiplier : float, optional default 1.5, the multiplier to the threshold length, so that the length of the new edge is set to multiplier * threshold_length """ srce, trgt, face = sheet.edge_df.loc[edge01, ["srce", "trgt", "face"]].astype(int) vert = min(srce, trgt) # find the vertex that won't be reindexed ret_code = collapse_edge(sheet, edge01, reindex=True, allow_two_sided=True) if ret_code < 0: warnings.warn(f"Collapse of edge {edge01} failed") return ret_code split_vert( sheet, vert, face, multiplier=multiplier, reindex=True, recenter=True, ) if not remove_tri_faces: return 0 # Type 1 transitions might create 3 or 2 sided cells, we remove those tri_faces = sheet.face_df[sheet.face_df["num_sides"] < 4].index i = 0 while len(tri_faces): remove_face(sheet, tri_faces[0]) tri_faces = sheet.face_df[sheet.face_df["num_sides"] < 4].index i += 1 if i > MAX_ITER: raise RecursionError return 0
[docs]def cell_division(sheet, mother, geom, angle=None): """Causes a cell to divide Parameters ---------- sheet : a 'Sheet' instance mother : face index of target dividing cell geom : a 2D geometry angle : division angle for newly formed edge Returns ------- daughter: face index of new cell Notes ----- - Function checks for perodic boundaries if there are, it checks if dividing cell rests on an edge of the periodic boundaries if so, it displaces the boundaries by a half a period and moves the target cell in the bulk of the tissue. It then performs cell division normally and reverts the periodic boundaries to the original configuration """ if sheet.settings.get("boundaries") is not None: mother_on_periodic_boundary = False if ( sheet.face_df.loc[mother]["at_x_boundary"] or sheet.face_df.loc[mother]["at_y_boundary"] ): mother_on_periodic_boundary = True saved_boundary = sheet.specs["settings"]["boundaries"].copy() for u, boundary in sheet.settings["boundaries"].items(): if sheet.face_df.loc[mother][f"at_{u}_boundary"]: period = boundary[1] - boundary[0] sheet.specs["settings"]["boundaries"][u] = [ boundary[0] + period / 2.0, boundary[1] + period / 2.0, ] geom.update_all(sheet) if not sheet.face_df.loc[mother, "is_alive"]: logger.warning("Cell %s is not alive and cannot devide", mother) return edge_a, edge_b = get_division_edges(sheet, mother, geom, angle=angle, axis="x") if edge_a is None: return vert_a, *_ = add_vert(sheet, edge_a) vert_b, *_ = add_vert(sheet, edge_b) sheet.vert_df.index.name = "vert" daughter = face_division(sheet, mother, vert_a, vert_b) if sheet.settings.get("boundaries") is not None and mother_on_periodic_boundary: sheet.specs["settings"]["boundaries"] = saved_boundary geom.update_all(sheet) return daughter
[docs]def get_division_edges(sheet, mother, geom, angle=None, axis="x"): if angle is None: angle = np.random.random() * np.pi m_data = sheet.edge_df[sheet.edge_df["face"] == mother] rot_pos = geom.face_projected_pos(sheet, mother, psi=angle) srce_pos = rot_pos.loc[m_data["srce"], axis] srce_pos.index = m_data.index trgt_pos = rot_pos.loc[m_data["trgt"], axis] trgt_pos.index = m_data.index try: edge_a = m_data[(srce_pos < 0) & (trgt_pos >= 0)].index[0] edge_b = m_data[(srce_pos >= 0) & (trgt_pos < 0)].index[0] except IndexError: print("Failed") logger.error("Division of Cell {} failed".format(mother)) return None, None return edge_a, edge_b
[docs]def face_division(sheet, mother, vert_a, vert_b): """ Divides the face associated with edges indexed by `edge_a` and `edge_b`, splitting it in the middle of those edes. """ # mother = sheet.edge_df.loc[edge_a, 'face'] face_cols = sheet.face_df.loc[mother:mother] sheet.face_df = pd.concat([sheet.face_df, face_cols], ignore_index=True) sheet.face_df.index.name = "face" daughter = int(sheet.face_df.index[-1]) edge_cols = sheet.edge_df[sheet.edge_df["face"] == mother].iloc[0:1] sheet.edge_df = pd.concat([sheet.edge_df, edge_cols, edge_cols], ignore_index=True) new_edge_m = sheet.edge_df.index[-2] sheet.edge_df.loc[new_edge_m, "srce"] = vert_b sheet.edge_df.loc[new_edge_m, "trgt"] = vert_a new_edge_d = sheet.edge_df.index[-1] sheet.edge_df.loc[new_edge_d, "srce"] = vert_a sheet.edge_df.loc[new_edge_d, "trgt"] = vert_b # ## Discover daughter edges m_data = sheet.edge_df[sheet.edge_df["face"] == mother] daughter_edges = [new_edge_d] srce, trgt = vert_a, vert_b srces, trgts = m_data[["srce", "trgt"]].values.T spins = 0 while trgt != vert_a: srce, trgt = trgt, trgts[srces == trgt][0] daughter_edges.append( m_data[(m_data["srce"] == srce) & (m_data["trgt"] == trgt)].index[0] ) spins += 1 if spins > m_data.shape[0]: raise ValueError(f"The face {mother} has an invalid topology, \n") sheet.edge_df.loc[daughter_edges, "face"] = daughter sheet.edge_df.index.name = "edge" sheet.reset_topo() return daughter
[docs]def resolve_t1s(sheet, geom, model, solver, max_iter=60): l_th = sheet.settings["threshold_length"] i = 0 while sheet.edge_df.length.min() < l_th: for edge in ( sheet.edge_df[sheet.edge_df.length < l_th].sort_values("length").index ): try: type1_transition(sheet, edge) except KeyError: continue sheet.reset_index() sheet.reset_topo() geom.update_all(sheet) solver.find_energy_min(sheet, geom, model) i += 1 if i > max_iter: break
def _cast_to_int(df_value): if len(df_value) == 1: return int(df_value) elif len(df_value) == 0: return -1 else: raise ValueError("Trying to retrieve an integer from a more than length 1 df ")