Esempio n. 1
0
    def grow_hyphae(self, iron_min_grow, grow_time, p_branch, spacing):
        """Grow fungal hyphae."""
        cells = self.cell_data

        conidia_indices = self.alive(
            (cells['form'] == FungusCellData.Form.CONIDIA)
            & (cells['status'] == FungusCellData.Status.GERMINATED)
            & (np.invert(cells['internalized'])))

        hyphae_indices = self.alive(
            (cells['form'] == FungusCellData.Form.HYPHAE)
            & (cells['status'] == FungusCellData.Status.GROWABLE)
            & (np.invert(cells['internalized']))
            & (cells['iron'] > iron_min_grow)
            & (cells['iteration'] > grow_time))

        # grow conidia
        if len(conidia_indices) != 0:
            cells['status'][conidia_indices] = FungusCellData.Status.GROWN
            cells['form'][conidia_indices] = FungusCellData.Form.HYPHAE
            children = FungusCellData(len(conidia_indices), initialize=True)
            children['iron'] = cells['iron'][conidia_indices]
            growth = spacing * (rg.random((len(conidia_indices), 3)) * 2 - 1)
            children['point'] = cells['point'][conidia_indices] + growth
            self.spawn_hypahael_cell(children)

        # grow hyphae
        if len(hyphae_indices) != 0:
            cells['status'][hyphae_indices] = FungusCellData.Status.GROWN
            branch_mask = rg.random(len(hyphae_indices)) < p_branch
            not_branch_indices = (np.invert(branch_mask)).nonzero()[0]
            branch_indices = branch_mask.nonzero()[0]

            elongate_children = FungusCellData(len(hyphae_indices),
                                               initialize=True)
            branch_children = FungusCellData(len(branch_indices),
                                             initialize=True)

            elongate_children['iron'] = cells['iron'][hyphae_indices] / 2
            growth = spacing * (rg.random((len(hyphae_indices), 3)) * 2 - 1)
            elongate_children[
                'point'] = cells['point'][hyphae_indices] + growth

            if len(branch_indices) != 0:
                elongate_children['iron'][branch_indices] = (
                    cells['iron'][hyphae_indices[branch_indices]] / 3)

                branch_children['iron'] = cells['iron'][
                    hyphae_indices[branch_indices]] / 3
                growth = spacing * (rg.random(
                    (len(hyphae_indices[branch_indices]), 3)) * 2 - 1)
                branch_children['point'] = cells['point'][
                    hyphae_indices[branch_indices]] + growth

            # update iron in orignal cells
            cells['iron'][hyphae_indices[not_branch_indices]] /= 2
            cells['iron'][hyphae_indices[branch_indices]] /= 3

            self.spawn_hypahael_cell(elongate_children)
            self.spawn_hypahael_cell(branch_children)
Esempio n. 2
0
def random_sphere_point() -> np.ndarray:
    """Generate a random point on the unit 2-sphere in R^3 using Marsaglia's method"""
    # generate vector in unit disc
    u: np.ndarray = 2 * rg.random(size=2) - 1
    while np.linalg.norm(u) > 1.0:
        u = 2 * rg.random(size=2) - 1

    norm_squared_u = float(np.dot(u, u))
    return np.array(
        [
            2 * u[0] * np.sqrt(1 - norm_squared_u),
            2 * u[1] * np.sqrt(1 - norm_squared_u),
            1 - 2 * norm_squared_u,
        ],
        dtype=np.float64,
    )
    def recruit_new(self, rec_rate_ph, rec_r, p_rec_r, tissue, grid, cyto):
        num_reps = rec_rate_ph  # maximum number of macrophages recruited per time step

        cyto_index = np.argwhere(
            np.logical_and(tissue == TissueType.BLOOD.value, cyto >= rec_r))
        if len(cyto_index) == 0:
            # nowhere to place cells
            return

        for _ in range(num_reps):
            if p_rec_r > rg.random():
                ii = rg.integers(cyto_index.shape[0])
                point = Point(
                    x=grid.x[cyto_index[ii, 2]],
                    y=grid.y[cyto_index[ii, 1]],
                    z=grid.z[cyto_index[ii, 0]],
                )
                # Do we really want these things to always be in the exact center of the voxel?
                # No we do not. Should not have any effect on model, but maybe some on
                # visualization.
                perturbation = rg.multivariate_normal(mean=[0.0, 0.0, 0.0],
                                                      cov=[[0.25, 0.0, 0.0],
                                                           [0.0, 0.25, 0.0],
                                                           [0.0, 0.0, 0.25]])
                perturbation_magnitude = np.linalg.norm(perturbation)
                perturbation /= max(1.0, perturbation_magnitude)
                point += perturbation
                self.append(MacrophageCellData.create_cell(point=point))
    def internalize_conidia(self, m_det, max_spores, p_in, grid,
                            fungus: FungusCellList):
        for i in self.alive():
            cell = self[i]
            vox = grid.get_voxel(cell['point'])

            # Moore neighborhood, but order partially randomized. Closest to furthest order, but
            # the order of any set of points of equal distance is random
            neighborhood = list(
                itertools.product(tuple(range(-1 * m_det, m_det + 1)),
                                  repeat=3))
            shuffle(neighborhood)
            neighborhood = sorted(neighborhood,
                                  key=lambda v: v[0]**2 + v[1]**2 + v[2]**2)

            for dx, dy, dz in neighborhood:
                zi = vox.z + dz
                yj = vox.y + dy
                xk = vox.x + dx
                if grid.is_valid_voxel(Voxel(x=xk, y=yj, z=zi)):
                    index_arr = fungus.get_cells_in_voxel(
                        Voxel(x=xk, y=yj, z=zi))
                    for index in index_arr:
                        if (fungus[index]['form']
                                == FungusCellData.Form.CONIDIA
                                and not fungus[index]['internalized']
                                and p_in > rg.random()):
                            fungus[index]['internalized'] = True
                            self.append_to_phagosome(i, index, max_spores)
Esempio n. 5
0
    def advance(self, state: State, previous_time: float) -> State:
        """Advance the state by a single time step."""
        from nlisim.modules.macrophage import MacrophageState

        hepcidin: HepcidinState = state.hepcidin
        macrophage: MacrophageState = state.macrophage
        voxel_volume: float = state.voxel_volume

        # interaction with macrophages
        activated_voxels = zip(*np.where(
            activation_function(
                x=hepcidin.grid,
                k_d=hepcidin.k_d,
                h=self.time_step / 60,  # units: (min/step) / (min/hour)
                volume=voxel_volume,
                b=1,
            ) > rg.random(size=hepcidin.grid.shape)))
        for z, y, x in activated_voxels:
            for macrophage_cell_index in macrophage.cells.get_cells_in_voxel(
                    Voxel(x=x, y=y, z=z)):
                macrophage_cell = macrophage.cells[macrophage_cell_index]
                macrophage_cell['fpn'] = False
                macrophage_cell['fpn_iteration'] = 0

        # Degrading Hepcidin is done by the "liver"

        # hepcidin does not diffuse

        return state
Esempio n. 6
0
    def change_status(self, p_internal_swell: float, rest_time: int,
                      swell_time: int):
        cells = self.cell_data

        indices = self.alive(cells['form'] != FungusCellData.Form.HYPHAE, )

        internalized_indices = (cells['internalized'][indices]).nonzero()[0]
        not_internalized_indices = (np.invert(
            cells['internalized'][indices])).nonzero()[0]

        internalized_rest_indices = np.logical_and(
            cells['status'][internalized_indices] ==
            FungusCellData.Status.RESTING,
            cells['iteration'][internalized_indices] >= rest_time,
        ).nonzero()[0]

        internalized_swollen_indices = np.logical_and(
            cells['status'][internalized_indices] ==
            FungusCellData.Status.SWOLLEN,
            cells['iteration'][internalized_indices] >= swell_time,
        ).nonzero()[0]

        # internal fungus with REST status
        swall_mask = rg.random(
            len(internalized_rest_indices)) < p_internal_swell
        internalized_rest_indices = swall_mask.nonzero()[0]

        cells['status'][
            internalized_rest_indices] = FungusCellData.Status.SWOLLEN
        cells['iteration'][internalized_rest_indices] = 0

        # internal fungus with SWOLLEN status
        cells['status'][
            internalized_swollen_indices] = FungusCellData.Status.GERMINATED
        cells['iteration'][internalized_swollen_indices] = 0

        rest_indices = np.logical_and(
            cells['status'][not_internalized_indices] ==
            FungusCellData.Status.RESTING,
            cells['iteration'][not_internalized_indices] >= rest_time,
        ).nonzero()[0]
        swollen_indices = np.logical_and(
            cells['status'][not_internalized_indices] ==
            FungusCellData.Status.SWOLLEN,
            cells['iteration'][not_internalized_indices] >= swell_time,
        ).nonzero()[0]

        # free fungus with REST status
        cells['status'][rest_indices] = FungusCellData.Status.SWOLLEN
        cells['iteration'][rest_indices] = 0

        # free fungus with SWOLLEN status
        cells['status'][swollen_indices] = FungusCellData.Status.GERMINATED
        cells['iteration'][swollen_indices] = 0
Esempio n. 7
0
def generate_branch_direction(cell_vec: np.ndarray) -> np.ndarray:
    """
    Generate a direction vector for branches.

    Parameters
    ----------
    cell_vec : np.ndarray
        a unit 3-vector

    Returns
    -------
    np.ndarray
        a random unit 3-vector at a 45 degree angle to `cell_vec`, sampled from the
        uniform distribution
    """
    # norm should be approx 1, can delete for performance
    cell_vec /= np.linalg.norm(cell_vec)

    # create orthogonal basis adapted to cell's direction
    # get first orthogonal vector
    u: np.ndarray
    epsilon = 0.1
    e1 = np.array([1.0, 0.0, 0.0], dtype=np.float64)
    e2 = np.array([0.0, 1.0, 0.0], dtype=np.float64)
    # if the cell vector isn't too close to +/- e1, generate the orthogonal vector using the cross
    # product with e1. otherwise use e2. (we can't be too close to both)
    u = (
        np.cross(cell_vec, e1)
        if (np.linalg.norm(cell_vec - e1) > epsilon and np.linalg.norm(cell_vec + e1) > epsilon)
        else np.cross(cell_vec, e2)
    )
    u /= np.linalg.norm(u)  # unlike the other normalizations, this is non-optional

    # get second orthogonal vector, orthogonal to both the cell vec and the first orthogonal vector
    v = np.cross(cell_vec, u)
    # norm should be approx 1, can delete for performance
    v /= np.linalg.norm(v)

    # change of coordinates matrix
    p_matrix = np.array([cell_vec, u, v]).T

    # form a random unit vector on a 45 degree cone
    theta = rg.random() * 2 * np.pi
    branch_direction = p_matrix @ np.array([1.0, np.cos(theta), np.sin(theta)]) / np.sqrt(2)
    # norm should be approx 1, can delete for performance
    branch_direction /= np.linalg.norm(branch_direction)

    return branch_direction
Esempio n. 8
0
    def fungus_macrophage_interaction(
        afumigatus: AfumigatusState,
        afumigatus_cell: AfumigatusCellData,
        afumigatus_cell_index: int,
        macrophage: 'MacrophageState',
        macrophage_cell: 'MacrophageCellData',
        macrophage_cell_index: int,
        iron: IronState,
        grid: RectangularGrid,
    ):
        from nlisim.modules.macrophage import PhagocyteStatus

        probability_of_interaction = (
            afumigatus.pr_ma_hyphae
            if afumigatus_cell['status'] == AfumigatusCellStatus.HYPHAE
            else afumigatus.pr_ma_phag
        )

        # return if they do not interact
        if rg.random() >= probability_of_interaction:
            return

        # now they interact

        interact_with_aspergillus(
            phagocyte_cell=macrophage_cell,
            phagocyte_cell_index=macrophage_cell_index,
            phagocyte_cells=macrophage.cells,
            aspergillus_cell=afumigatus_cell,
            aspergillus_cell_index=afumigatus_cell_index,
            phagocyte=macrophage,
            phagocytize=afumigatus_cell['status'] != AfumigatusCellStatus.HYPHAE,
        )

        # unlink the fungal cell from its tree
        if (
            afumigatus_cell['status'] == AfumigatusCellStatus.HYPHAE
            and macrophage_cell['status'] == PhagocyteStatus.ACTIVE
        ):
            Afumigatus.kill_fungal_cell(
                afumigatus, afumigatus_cell, afumigatus_cell_index, iron, grid
            )
Esempio n. 9
0
def branch(
    afumigatus_cell: AfumigatusCellData,
    afumigatus_cell_index: int,
    pr_branch: float,
    afumigatus: AfumigatusState,
):
    if (
        afumigatus_cell['next_branch'] != -1  # if it already has a branch
        or afumigatus_cell['status'] != AfumigatusCellStatus.HYPHAE
        or not afumigatus_cell['boolean_network'][NetworkSpecies.LIP]
    ):
        return

    hyphal_length: float = afumigatus.hyphal_length
    if rg.random() < pr_branch:
        # now we branch
        branch_vector = generate_branch_direction(cell_vec=afumigatus_cell['vec'])
        branch_center_point = (
            afumigatus_cell['point']
            + (hyphal_length / 2) * afumigatus_cell['vec']
            + (hyphal_length / 2) * branch_vector
        )  # center of new branch is offset by rest (half) of this septa and half of the new septa

        # create the new septa
        next_branch: CellData = AfumigatusCellData.create_cell(
            point=Point(
                x=branch_center_point[2], y=branch_center_point[1], z=branch_center_point[0]
            ),
            vec=branch_vector,
            growth_iteration=-1,
            iron_pool=0,
            status=AfumigatusCellStatus.HYPHAE,
            state=afumigatus_cell['state'],
            is_root=False,
        )
        next_branch_id: int = afumigatus.cells.append(next_branch)

        # link them together
        afumigatus_cell['next_branch'] = next_branch_id
        next_branch['previous_septa'] = afumigatus_cell_index
Esempio n. 10
0
def cell_self_update(
    afumigatus: AfumigatusState,
    afumigatus_cell: AfumigatusCellData,
    afumigatus_cell_index: int,
) -> None:
    afumigatus_cell['activation_iteration'] += 1

    process_boolean_network(
        afumigatus_cell=afumigatus_cell,
        steps_to_eval=afumigatus.steps_to_bn_eval,
        afumigatus=afumigatus,
    )

    # resting conidia become swelling conidia after a number of iterations
    # (with some probability)
    if (
        afumigatus_cell['status'] == AfumigatusCellStatus.RESTING_CONIDIA
        and afumigatus_cell['activation_iteration'] >= afumigatus.iter_to_swelling
        and rg.random() < afumigatus.pr_aspergillus_change
    ):
        afumigatus_cell['status'] = AfumigatusCellStatus.SWELLING_CONIDIA
        afumigatus_cell['activation_iteration'] = 0

    elif (
        afumigatus_cell['status'] == AfumigatusCellStatus.SWELLING_CONIDIA
        and afumigatus_cell['activation_iteration'] >= afumigatus.iter_to_germinate
    ):
        afumigatus_cell['status'] = AfumigatusCellStatus.GERM_TUBE
        afumigatus_cell['activation_iteration'] = 0

    # TODO: verify this, 1 turn on internalizing then free?
    if afumigatus_cell['state'] in {
        AfumigatusCellState.INTERNALIZING,
        AfumigatusCellState.RELEASING,
    }:
        afumigatus_cell['state'] = AfumigatusCellState.FREE

    # Distribute iron evenly within fungal tree.
    # Note: called for every cell, but a no-op on non-root cells.
    diffuse_iron(afumigatus_cell_index, afumigatus)
Esempio n. 11
0
def lip_activation(afumigatus: AfumigatusState, iron_pool: float) -> bool:
    molar_concentration = iron_pool / afumigatus.hyphae_volume
    activation = 1 - np.exp(-molar_concentration / afumigatus.kd_lip)
    return bool(rg.random() < activation)
Esempio n. 12
0
    def chemotaxis(
        self,
        molecule,
        drift_lambda,
        drift_bias,
        tissue,
        grid: RectangularGrid,
    ):
        # 'molecule' = state.'molecule'.concentration
        # prob = 0-1 random number to determine which voxel is chosen to move

        # 1. Get cells that are alive
        for index in self.alive():
            prob = rg.random()

            # 2. Get voxel for each cell to get molecule in that voxel
            cell = self[index]
            vox = grid.get_voxel(cell['point'])

            # 3. Set prob for neighboring voxels
            p = []
            vox_list = []
            p_tot = 0.0
            i = -1

            # calculate individual probability
            for x in [0, 1, -1]:
                for y in [0, 1, -1]:
                    for z in [0, 1, -1]:
                        p.append(0.0)
                        vox_list.append([x, y, z])
                        i += 1
                        zk = vox.z + z
                        yj = vox.y + y
                        xi = vox.x + x
                        if grid.is_valid_voxel(Voxel(x=xi, y=yj, z=zk)):
                            if tissue[zk, yj, xi] in [
                                    TissueType.SURFACTANT.value,
                                    TissueType.BLOOD.value,
                                    TissueType.EPITHELIUM.value,
                                    TissueType.PORE.value,
                            ]:
                                p[i] = logistic(molecule[zk, yj, xi],
                                                drift_lambda, drift_bias)
                                p_tot += p[i]

            # scale to sum of probabilities
            if p_tot:
                for i in range(len(p)):
                    p[i] = p[i] / p_tot

            # chose vox from neighbors
            cum_p = 0.0
            for i in range(len(p)):
                cum_p += p[i]
                if prob <= cum_p:
                    cell['point'] = Point(
                        x=random.uniform(grid.xv[vox.x + vox_list[i][0]],
                                         grid.xv[vox.x + vox_list[i][0] + 1]),
                        y=random.uniform(grid.yv[vox.y + vox_list[i][1]],
                                         grid.yv[vox.y + vox_list[i][1] + 1]),
                        z=random.uniform(grid.zv[vox.z + vox_list[i][2]],
                                         grid.zv[vox.z + vox_list[i][2] + 1]),
                    )
                    self.update_voxel_index([index])
                    break