Source code for neurorosettes.grid

from typing import List, Tuple, Union
from dataclasses import dataclass, field
from abc import ABC, abstractmethod

import numpy as np

from neurorosettes.subcellular import CellBody, Neurite


[docs]@dataclass class UniformGrid: """ Class to store the objects inside a simulation container and their coordinates. Grids are defined as uniform objects with the same size and spacing for all dimensions (2D or 3D). """ min: float """The bottom limit of the grid domain size.""" max: float """The top limit of the grid domain size.""" step: float """The size of the grid cells.""" use_2d: bool = True """If a grid has a third dimension or is 2D.""" grid_values: np.ndarray = field(init=False) """The coordinates of the grid cells.""" idx_values: np.ndarray = field(init=False) """The indices of the grid cells.""" grid: np.ndarray = field(init=False) """The grid, with the objects stored inside each cell."""
[docs] def __post_init__(self) -> None: """Creates the empty grid object based on the initial size and spacing.""" self.grid_values = np.arange(self.min, self.max, self.step) self.idx_values = np.arange(self.grid_values.shape[0]) z_shape = 1 if self.use_2d else self.grid_values.shape[0] self.grid = np.empty( shape=[z_shape, self.grid_values.shape[0], self.grid_values.shape[0]], dtype=object, ) self.grid[...] = [ [[[] for _ in range(self.grid.shape[2])] for _ in range(self.grid.shape[1])] for _ in range(self.grid.shape[0]) ]
@property def representation_grid_values(self) -> np.ndarray: """Returns the grid cell coordinates, inluding the top limit.""" return np.arange(self.min, self.max + 1, self.step)
[docs] def interpolate_idx(self, position: np.ndarray) -> Tuple[int, int, int]: """ Returns the indices of the cell where a given position should be stored. Parameters ---------- position The position to be located inside the grid. """ idxx = np.floor( np.interp(position[0], self.grid_values, self.idx_values) ).astype(int) idxy = np.floor( np.interp(position[1], self.grid_values, self.idx_values) ).astype(int) if self.use_2d: return idxx, idxy, 0 idxz = np.floor( np.interp(position[2], self.grid_values, self.idx_values) ).astype(int) return idxx, idxy, idxz
[docs] def register_cell(self, cell: CellBody) -> None: """ Stores a cell object inside the grid and evaluates where to register it. Parameters ---------- cell The cell object to be stored inside the grid. """ idxx, idxy, idxz = self.interpolate_idx(cell.position) self.grid[idxz][idxy][idxx].append(cell)
[docs] def register_neurite(self, neurite: Neurite) -> None: """ Stores a neurite object inside the grid and evaluates where to register it. Parameters ---------- neurite The neurite object to be stored inside the grid. """ idxx, idxy, idxz = self.interpolate_idx(neurite.distal_point) self.grid[idxz][idxy][idxx].append(neurite)
[docs] def remove_cell(self, cell: CellBody) -> None: """ Removes a cell object from the grid. Parameters ---------- cell The cell object to be removed from the grid. """ idxx, idxy, idxz = self.interpolate_idx(cell.position) self.grid[idxz][idxy][idxx].remove(cell)
[docs] def remove_neurite(self, neurite: Neurite) -> None: """ Removes a neurite object from the grid. Parameters ---------- neurite The neurite object to be removed from the grid. """ idxx, idxy, idxz = self.interpolate_idx(neurite.distal_point) self.grid[idxz][idxy][idxx].remove(neurite)
[docs] def get_objects_in_voxel( self, idxx: int, idxy: int, idxz: int = 0 ) -> List[Union[CellBody, Neurite]]: """ Returns the objects inside a cell of the grid. Parameters ---------- idxx The index of the cell in the x component. idxy The index of the cell in the y component. idxz The index of the cell in the z component. Returns ------- A list of the cell bodies and neurites registered in the given cell of the grid. """ return self.grid[idxz][idxy][idxx]
[docs] def get_close_objects(self, position: np.ndarray) -> List[Union[CellBody, Neurite]]: """ Returns the objects inside a cell and its neighbors. Parameters ---------- position The position to be evaluated. Returns ------- A list of the cell bodies and objects inside a cell, and the cells that surround it in 2 or 3 dimensions. """ # Find the index corresponding to the position to be evaluated idxx, idxy, idxz = self.interpolate_idx(position) neighbors = list() # Get the indices of the neighbors, avoiding domain boundaries x_neighbors = [ idxx + value for value in [-1, 0, 1] if 0 < idxx + value < len(self.idx_values) ] y_neighbors = [ idxy + value for value in [-1, 0, 1] if 0 < idxy + value < len(self.idx_values) ] # Disregard the z component if the grid is 2D if self.use_2d: for y in y_neighbors: for x in x_neighbors: # Go through the cells of interest and get the objects inside neighbors.extend(self.get_objects_in_voxel(x, y, 0)) return neighbors z_neighbors = [ idxz + value for value in [-1, 0, 1] if 0 < idxz + value < len(self.idx_values) ] # Go through the cells of interest in 3D and get the objects inside for z in z_neighbors: for y in y_neighbors: for x in x_neighbors: neighbors.extend(self.get_objects_in_voxel(x, y, z)) return neighbors
[docs] def get_close_cells(self, position: np.ndarray) -> List[CellBody]: """ Returns only the cell bodies inside a cell and its neighbors. Parameters ---------- position The position to be evaluated. Returns ------- A list of the cell bodies inside a cell and its neighbors. """ return [ neighbor for neighbor in self.get_close_objects(position) if isinstance(neighbor, CellBody) ]
[docs] def get_cells_in_radius( self, position: np.ndarray, radius: float ) -> List[CellBody]: """ Returns the objects inside a radius of interest. Parameters ---------- position The position to be evaluated. radius The radius of interest. Returns ------- A list of the cell bodies and neurites that are inside a radius of interest with the given position as the centre. """ return [ neighbor for neighbor in self.get_close_cells(position) if np.linalg.norm(neighbor.position - position) <= radius ]
[docs]class CellDensityCheck(ABC): """ Class to evaluate the density of a tissue around a neuron object. Multiple types of checks may be defined, such as grid-based or radius-based evaluations. """
[docs] @abstractmethod def check_max_density(self, cell: CellBody, grid: UniformGrid) -> bool: """ Returns a true boolean value if the density around a neuron surpasses the limit. Parameters ---------- cell The cell to be evaluated. grid The Grid object where the cells and its neighbors are registered. """ pass
[docs]class OneLevelDensityCheck(CellDensityCheck): """ Class for a cell density check based on the neighboring grid cells. Parameters ---------- max_neighbors The maximum numbers of neighbors inside the cell grid and its neighbors. """ def __init__(self, max_neighbors: int = 15): self.max_neighbors = max_neighbors
[docs] def check_max_density(self, cell: CellBody, grid: UniformGrid) -> bool: """ Checks if the number of neighbors is higher than the maximum limit. Parameters ---------- cell The cell to be evaluated. grid The Grid object where the cells and its neighbors are registered. """ return len(grid.get_close_cells(cell.position)) >= self.max_neighbors
[docs]class TwoLevelsDensityCheck(CellDensityCheck): """ Class for a cell density check based on a radius of interest. Starts by checking if the number of neighbors is larger than the maximum, as done in the OneLevelDensityCheck. If this value is surpassed, the neighbors inside a radius of interest are selected and quantified. It then checks if there are more neighbors inside this radius than the expected value. Parameters ---------- max_outer_neighbors The maximum numbers of neighbors inside the cell grid and its neighbors. max_inner_neighbors The maximum number of neighbors inside of the radius of interest. radius The radius of interest. """ def __init__( self, max_outer_neighbors: int = 15, max_inner_neighbors: int = 6, radius: float = 18.0, ) -> None: self.max_outer_neighbors = max_outer_neighbors self.max_inner_neighbors = max_inner_neighbors self.radius = radius
[docs] def check_max_density(self, cell: CellBody, grid: UniformGrid) -> bool: """ Checks if the number of neighbors is higher than the maximum limit. Parameters ---------- cell The cell to be evaluated. grid The Grid object where the cells and its neighbors are registered. """ neighbors = grid.get_close_cells(cell.position) if len(neighbors) >= self.max_outer_neighbors: return True cells_in_radius = grid.get_cells_in_radius(cell.position, self.radius) return len(cells_in_radius) >= self.max_inner_neighbors