import math, numpy as np, random
from .morphologies import Compartment
def depth_first_branches(adjacency_list, node=0, return_visited=False):
branches = []
visited = set([])
def dfs_branch(branch, node):
# Grow the branch with current node.
branch.append(node)
# Mark this node as visited
visited.add(node)
# Get the child nodes and filter out visited nodes (for networks with loops)
next_nodes = list(adjacency_list[node] - visited)
# Continue/create branches if this node has any unvisited children.
if len(next_nodes) > 0:
# Continue the branch with the first child.
branch = dfs_branch(branch, next_nodes[0])
# Create a new branch for each other child node.
for new_branch_next_node in next_nodes[1:]:
# Start a branch from this node, continue it from the child node and pass it
branches.append(dfs_branch([node], new_branch_next_node))
return branch
# Start the first branch at the root node.
branches.append(dfs_branch([], node))
if return_visited:
return branches, visited
else:
return branches
def all_depth_first_branches(adjacency_list):
all_branches = []
all_nodes = list(range(len(adjacency_list)))
visited = set()
first_unvisited_node = 0
while set(all_nodes) - visited:
new_branches, new_visits = depth_first_branches(
adjacency_list, node=first_unvisited_node, return_visited=True
)
all_branches.extend(new_branches)
visited.update(new_visits)
for node in all_nodes:
if not node in visited:
first_unvisited_node = node
break
return all_branches
def get_branch_points(branch_list):
return list(map(lambda b: b[0], branch_list))
[docs]def reduce_branch(branch, branch_points):
"""
Reduce a branch (list of points) to only its start and end point and the
intersection with a list of known branch points.
"""
reduced_branch = [branch[0]]
for bp in branch:
if bp in branch_points:
reduced_branch.append(bp)
reduced_branch.append(branch[-1])
return reduced_branch
class Branch:
def __init__(self, compartments, orientation, parent=None, ordered=True):
self.origin = compartments[0].start
self._compartments = compartments
self.is_root = parent is None
self._parent_branch = parent
self.child_branches = []
if parent is None:
self.orientation = orientation
else:
self.orientation = parent.orientation
# Are the compartments provided in the order they are connected in?
if ordered:
# Iterate over the compartments to set the previous/next as parent/child.
for c in range(len(compartments) - 1):
compartments[c]._child = compartments[c + 1]
for c in range(1, len(compartments)):
compartments[c]._parent = compartments[c - 1]
# Polish the start and end of the branch
self._root = _get_root_compartment(compartments[0], compartments)
self._terminus = _get_terminal_compartment(compartments[-1], compartments)
self._root._parent = None
self._terminus._child = None
def add_branch(self, branch):
branch._parent_branch = self
self.child_branches.append(branch)
def __iter__(self):
return iter(self.walk())
def walk(self, start=None):
if start is None:
start = self._root
while start._child is not None:
yield start
start = start._child
yield start
def append(self, compartment):
self._compartments.append(compartment)
self._terminus._child = compartment
compartment._parent = self._terminus
self._terminus = compartment
def interpolate(self, resolution):
for comp in self._compartments:
length_comp = np.linalg.norm(comp.end - comp.start)
if length_comp > resolution + 1e-3:
num_to_add = math.ceil(length_comp / resolution)
added_points = self.split(comp, num_to_add)
def split(self, compartment, n):
"""
Split the compartment in n pieces and make those a part of the branch.
This function stores a link to the original compartment in the partial
compartments in the attribute `_original`.
:param compartment: The compartment to split.
:type compartment: :class:`.morphologies.Compartment`
:param n: The amount of pieces to split the compartment into.
:type n: int
"""
if n < 2:
raise ValueError("Compartments must be split in at least 2 parts.")
new_compartments = []
# For each dimension calculate n breakpoints between the start and end of the
# compartment. The arrays for each dimensions are then stacked as columns of a
# matrix with all the breakpoints. This matrix includes the start and end points
# themselves.
points = np.column_stack(
[
np.linspace(start, end, n + 1)
for start, end in zip(compartment.start, compartment.end)
]
)
# Inside of the loop `last_compartment` is used as a parent for the new
# compartments. By providing the original compartment's parent as an initial
# condition, we will connect the first new compartment to the original parent,
# thus continuing the branch from the first new compartment.
last_compartment = compartment._parent
# Loop over the points and use every point except the last as the starting point
# for a new compartment. Then connect each new compartment to the previous
# compartment.
for i in range(len(points) - 1):
# Copy the compartment information but change start and end.
c = Compartment.from_template(compartment, start=points[i], end=points[i + 1])
new_compartments.append(c)
# Store a reference to the original compartment for back referencing.
c._original = compartment
# Connect the new child to its parent
c._parent = last_compartment
if last_compartment:
# Connect to the parent to its child
last_compartment._child = c
# Move up the pointer of the loop so that the next compartment is connected to
# the current one.
last_compartment = c
# Finally we should connect the last compartment to the child of the original
# compartment to continue the branch
last_compartment._child = compartment._child
# Remove the original compartment as a part of the branch
self._compartments.remove(compartment)
# Add the new compartments into the internal array of compartments
self._compartments.extend(new_compartments)
# Is the original compartment was the root of the branch?
if compartment is self._root:
# Then we need to replace the root with the first new compartment so that
# branch iteration starts from the new compartment.
self._root = new_compartments[0]
def detach(self, compartment):
if compartment is self._root or compartment._parent is None:
# We have been asked to detach ourselves from ourselves: that just yields our whole branch again
return self
# Detach our parent from the cutoff point.
compartment._parent._child = None
self._terminus = compartment._parent
# Detach ourself from our parent.
compartment._parent = None
detached_branch = Branch(
list(self.walk(start=compartment)), self.orientation, ordered=False
)
# Clean up all detached compartments that now belong to the detached branch
for detached_comp in detached_branch.walk():
self._compartments.remove(detached_comp)
return detached_branch
def voxelize(self, position, bounding_box, voxel_tree, map, voxel_list):
v_id = len(voxel_list)
for v, comp in enumerate(self._compartments):
# Check if the current bounding_box needs to be extended
bounding_box[0] = np.minimum(bounding_box[0], comp.end + position)
bounding_box[1] = np.maximum(bounding_box[1], comp.end + position)
# Find the external points of the voxel surrounding the compartment in absolute coordinates
voxel_bottom_left = np.minimum(comp.start, comp.end)
voxel_top_right = np.maximum(comp.start, comp.end)
voxel = tuple(
np.concatenate((voxel_bottom_left + position, voxel_top_right + position))
)
# Add the voxel to the tree
voxel_tree.insert(
v_id,
voxel,
)
map.append(comp)
voxel_list.append(voxel)
v_id += 1
return bounding_box, voxel_tree, map, voxel_list
def _init_child_compartments(compartments):
# Reset/init child compartments
for c in compartments:
c._children = []
# Add child nodes to their parent's children
for node in compartments:
if node.parent not in compartments:
node.parent = None
if node.parent is not None:
node.parent._children.append(node)
def _get_root_compartment(compartment, compartments):
while compartment.parent is not None and compartment.parent in compartments:
compartment = compartment.parent
return compartment
def _get_single_child(compartment, compartments):
# Get the child either by the _child attribute, or the only element of _children
child = (
hasattr(compartment, "_child")
and compartment._child
or hasattr(compartment, "_children")
and len(compartment._children) == 1
and compartment._children[0]
)
# If a child was found this way, check if it is in the compartments array and return
# it, otherwise this function returns False
return child and child in compartments and child
def _get_terminal_compartment(compartment, compartments):
result = compartment
child = _get_single_child(compartment, compartments)
while child:
result = child
child = _get_single_child(compartment, compartments)
return result
def _consume_branch(unvisited, root_compartment, orientation, parent=None):
branch = Branch([root_compartment], orientation, parent=parent, ordered=False)
unvisited.remove(root_compartment)
root_compartment._parent = None
if len(root_compartment._children) > 0:
root_compartment._child = root_compartment._children[0]
compartment = root_compartment
while len(compartment._children) == 1:
next_compartment = compartment._children[0]
branch.append(next_compartment)
unvisited.remove(next_compartment)
compartment = next_compartment
if len(compartment._children) > 0:
for child in compartment._children:
child_branch = _consume_branch(unvisited, child, parent=branch)
branch.add_branch(child_branch)
branch._terminus._child = None
return branch
def _copy_linked_compartments(compartments):
copy_map = {}
new_compartments = []
# Create a copy of each compartment
for c in compartments:
new_c = Compartment.from_template(c)
copy_map[new_c.id] = new_c
new_compartments.append(new_c)
# Overwrite each parent with a reference to the copied parent, or strip it
for c in new_compartments:
if c.parent is not None:
parent_id = c.parent.id
if parent_id in copy_map:
c.parent = copy_map[parent_id]
else:
c.parent = None
return new_compartments
def create_root_branched_network(compartments, orientation):
root_branches = []
_init_child_compartments(compartments)
unvisited = set(compartments)
while len(unvisited) > 0:
starting_compartment = next(iter(unvisited))
root_compartment = _get_root_compartment(starting_compartment, compartments)
root_branch = _consume_branch(unvisited, root_compartment, orientation)
root_branches.append(root_branch)
return root_branches
class FiberMorphology:
def __init__(self, compartments, rotation):
compartments = _copy_linked_compartments(compartments)
if rotation is None:
orientation = None
else:
orientation = np.array(
[np.cos(rotation[0]), np.sin(rotation[0]), np.sin(rotation[1])]
)
self.root_branches = create_root_branched_network(compartments, orientation)
def flatten(self, branches=None):
if branches is None:
branches = self.root_branches
compartments = []
for branch in branches:
compartments.extend(list(branch._compartments))
compartments.extend(self.flatten(branch.child_branches))
return compartments