Source code for tyssue.geometry.bulk_geometry
import numpy as np
from ..utils import _to_3d
from .sheet_geometry import SheetGeometry
from .utils import rotation_matrix
[docs]class BulkGeometry(SheetGeometry):
"""Geometry functions for 3D cell arangements"""
[docs] @classmethod
def update_all(cls, eptm):
"""
Updates the eptm geometry by updating:
* the edge vector coordinates
* the edge lengths
* the face centroids
* the normals to each edge associated face
* the face areas
* the cell areas
* the vertices heights (depends on geometry)
* the face volumes (depends on geometry)
"""
cls.update_dcoords(eptm)
cls.update_ucoords(eptm)
cls.update_length(eptm)
cls.update_perimeters(eptm)
cls.update_centroid(eptm)
cls.update_normals(eptm)
cls.update_vol(eptm)
cls.update_areas(eptm)
[docs] @staticmethod
def update_dcoords(eptm):
SheetGeometry.update_dcoords(eptm)
[docs] @staticmethod
def update_ucoords(eptm):
SheetGeometry.update_ucoords(eptm)
[docs] @staticmethod
def update_vol(eptm):
""" """
face_pos = eptm.edge_df[["f" + c for c in eptm.coords]].values
cell_pos = eptm.edge_df[["c" + c for c in eptm.coords]].values
eptm.edge_df["sub_vol"] = (
np.sum((face_pos - cell_pos) * eptm.edge_df[eptm.ncoords].values, axis=1)
/ 6
)
eptm.cell_df["vol"] = eptm.sum_cell(eptm.edge_df["sub_vol"])
[docs] @staticmethod
def update_areas(eptm):
# SheetGeometry.update_areas(eptm)
eptm.edge_df["sub_area"] = (
np.linalg.norm(eptm.edge_df[eptm.ncoords], axis=1) / 2
)
eptm.face_df["area"] = eptm.sum_face(eptm.edge_df["sub_area"])
eptm.cell_df["area"] = eptm.sum_cell(eptm.edge_df["sub_area"])
[docs] @staticmethod
def update_centroid(eptm):
scoords = ["s" + c for c in eptm.coords]
eptm.face_df[eptm.coords] = eptm.edge_df.groupby("face")[scoords].mean()
face_pos = eptm.upcast_face(eptm.face_df[eptm.coords])
for c in eptm.coords:
eptm.edge_df["f" + c] = face_pos[c]
eptm.edge_df["r" + c] = eptm.edge_df["s" + c] - eptm.edge_df["f" + c]
eptm.cell_df[eptm.coords] = eptm.edge_df.groupby("cell")[scoords].mean()
cell_pos = eptm.upcast_cell(eptm.cell_df[eptm.coords]).values
eptm.edge_df[["c" + c for c in eptm.coords]] = cell_pos
[docs] @staticmethod
def validate_face_norms(eptm):
fcoords = ["f" + c for c in eptm.coords]
ccoords = ["c" + c for c in eptm.coords]
face_pos = eptm.edge_df[fcoords].values
cell_pos = eptm.edge_df[ccoords].values
r_cf = face_pos - cell_pos
r_cf["face"] = eptm.edge_df["face"]
r_cf = r_cf.groupby("face").mean()
face_norm = eptm.edge_df.groupby("face")[eptm.ncoords].mean()
proj = (face_norm * r_cf.values).sum(axis=1)
is_outward = proj >= 0
return is_outward
[docs]class RNRGeometry(BulkGeometry):
[docs] @staticmethod
def update_centroid(eptm):
scoords = ["s" + c for c in eptm.coords]
tcoords = ["t" + c for c in eptm.coords]
srce_pos = eptm.edge_df[scoords].values
trgt_pos = eptm.edge_df[tcoords].values
mid_pos = (srce_pos + trgt_pos) / 2
weighted_pos = eptm.sum_face(mid_pos * _to_3d(eptm.edge_df["length"]))
eptm.face_df[eptm.coords] = (
weighted_pos.values / eptm.face_df["perimeter"].values[:, np.newaxis]
)
face_pos = eptm.upcast_face(eptm.face_df[eptm.coords])
for c in eptm.coords:
eptm.edge_df["f" + c] = face_pos[c]
eptm.edge_df["r" + c] = eptm.edge_df["s" + c] - eptm.edge_df["f" + c]
eptm.cell_df[eptm.coords] = eptm.edge_df.groupby("cell")[scoords].mean()
cell_pos = eptm.upcast_cell(eptm.cell_df[eptm.coords]).values
eptm.edge_df[["c" + c for c in eptm.coords]] = cell_pos
[docs]class MonolayerGeometry(RNRGeometry):
[docs] @staticmethod
def basal_apical_axis(eptm, cell):
"""
Returns a unit vector allong the apical-basal axis of the cell
"""
edges = eptm.edge_df[eptm.edge_df["cell"] == cell]
srce_segments = eptm.vert_df.loc[edges["srce"], "segment"]
srce_segments.index = edges.index
trgt_segments = eptm.vert_df.loc[edges["trgt"], "segment"]
trgt_segments.index = edges.index
ba_edges = edges[(srce_segments == "apical") & (trgt_segments == "basal")]
return ba_edges[eptm.dcoords].mean()
[docs] @classmethod
def cell_projected_pos(cls, eptm, cell, psi=0):
"""Returns the positions of the cell vertices
transformed such that the cell center sits at the
coordinate system's origin and the basal-apical axis
is the new `z` axis.
"""
ab_axis = cls.basal_apical_axis(eptm, cell)
n_xy = np.linalg.norm(ab_axis[["dx", "dy"]])
theta = -np.arctan2(n_xy, ab_axis.dz)
direction = [ab_axis.dy, -ab_axis.dx, 0]
rot = rotation_matrix(theta, direction)
cell_verts = set(eptm.edge_df[eptm.edge_df["cell"] == cell]["srce"])
vert_pos = eptm.vert_df.loc[cell_verts, eptm.coords]
for c in eptm.coords:
vert_pos[c] -= eptm.cell_df.loc[cell, c]
r1 = np.dot(vert_pos, rot)
if abs(psi) < 1e-6:
vert_pos[:] = r1
else:
vert_pos[:] = np.dot(rotation_matrix(psi, [0, 0, 1]), r1)
return vert_pos
[docs]class MonoLayerGeometry(MonolayerGeometry):
pass
[docs]class ClosedMonolayerGeometry(MonolayerGeometry):
[docs] @classmethod
def update_all(cls, eptm):
"""
Updates the eptm geometry by updating:
* the edge vector coordinates
* the edge lengths
* the face centroids
* the normals to each edge associated face
* the face areas
* the cell areas
* the vertices heights (depends on geometry)
* the face volumes (depends on geometry)
"""
super().update_all(eptm)
cls.update_lumen_vol(eptm)
[docs] @staticmethod
def update_lumen_vol(eptm):
""" """
lumen_edges = eptm.edge_df[
eptm.edge_df.segment == eptm.settings.get("lumen_side", "basal")
].copy()
lumen_pos_faces = lumen_edges[["f" + c for c in eptm.coords]].values
lumen_sub_vol = (
np.sum((lumen_pos_faces) * lumen_edges[eptm.ncoords].values, axis=1) / 6
)
eptm.settings["lumen_vol"] = -lumen_sub_vol.sum()