# Copyright 2015-2016 Allen Institute for Brain Science
# This file is part of Allen SDK.
#
# Allen SDK is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# Allen SDK is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Allen SDK. If not, see <http://www.gnu.org/licenses/>.
import copy
import math
import numpy as np
from allensdk.internal.morphology.node import Node
from allensdk.internal.morphology.compartment import Compartment
[docs]class Morphology( object ):
"""
Keep track of the list of nodes in a morphology and provide
a few helper methods (soma, tree information, pruning, etc).
"""
SOMA = 1
AXON = 2
BASAL_DENDRITE = 3
APICAL_DENDRITE = 4
NODE_TYPES = [ SOMA, AXON, BASAL_DENDRITE, APICAL_DENDRITE ]
def __init__(self, node_list=None):
"""
Try to initialize from a list of nodes first, then from
a dictionary indexed by node id if that fails, and finally just
leave everything empty.
Parameters
----------
node_list: list
list of Node objects
"""
self._node_list = [] # list of morphology node IDs
self._compartment_list = [] # list of morphology compartment IDs
##############################################
# define tree list here for clarity, even though it's reset below
# when nodes are assigned
self._tree_list = []
##############################################
# dimensions of morphology, including min and max values on xyz
# this is cached value
# NOTE: if morphology is manually manipulated, this value can
# become incorrect
self.dims = None
##############################################
# construct the node list
# first try to do so using the node list, then try using
# the node index and if that fails then complain
if node_list:
self.node_list = node_list
##############################################
# verify morphology is consistent with morphology rules (e.g.,
# no dendrite branching from an axon)
num_errors = self._check_consistency()
if num_errors > 0:
raise ValueError("Morphology appears to be inconsistent")
##############################################
# restructure morphology as necessary (eg, renumber nodes)
# and construct internal associations
self._reconstruct()
####################################################################
####################################################################
# class properties, and helper functions for them
@property
def node_list(self):
""" Return the node list. This is a property to ensure that the
node list and node index are in sync. """
return self._node_list
@node_list.setter
def node_list(self, node_list):
""" Update the node list. """
self._set_nodes(node_list)
@property
def compartment_list(self):
return self._compartment_list
@property
def num_trees(self):
""" Return the number of trees in the morphology. A tree is
defined as everything following from a single root node. """
return len(self._tree_list)
@property
def num_nodes(self):
"""
Return the number of nodes in the morphology.
"""
return len(self.node_list)
# internal function
def _set_nodes(self, node_list):
"""
take a list of SWC node objects and turn those into morphology
nodes need to be able to initialize from a list supplied by an SWC
file while also being able to initialize from the node list
of an existing Morphology object. As nodes in a morphology object
contain reference to nodes in that object, make a shallow copy
of input nodes and overwrite known references (ie, the
'children' array)
"""
self._node_list = []
for obj in node_list:
seg = copy.copy(obj)
seg.tree_id = -1
seg.children = []
self._node_list.append(seg)
# list data now set. remove holes in sequence and re-index
self._reconstruct()
# removed old 'soma' and 'root' calls as these were ambiguous
# a soma can consist of multiple compartments, and there can
# be multiple roots
# replaced those calls with new soma_root(), which returns the
# same thing as the old soma() when only a single soma node is
# present
[docs] def soma_root(self):
""" Returns root node of soma, if present"""
if len(self._tree_list) > 0 and self._tree_list[0][0].t == 1:
return self._tree_list[0][0]
return None
####################################################################
####################################################################
# tree and node access
[docs] def tree(self, n):
"""
Returns a list of all Morphology nodes within the specified
tree. A tree is defined as a fully connected graph of nodes.
Each tree has exactly one root.
Parameters
----------
n: integer
ID of desired tree
Returns
-------
A list of all morphology objects in the specified tree, or None
if the tree doesn't exist
"""
if n < 0 or n >= len(self._tree_list):
return None
return self._tree_list[n]
[docs] def node(self, n):
"""
Returns the morphology node having the specified ID.
Parameters
----------
n: integer
ID of desired node
Returns
-------
A morphology node having the specified ID, or None if such a
node doesn't exist
"""
# undocumented feature -- if a node is supplied instead of a
# node ID, the node is returned and no error is
# triggered
return self._resolve_node_type(n)
[docs] def compartment(self, n):
"""
Returns the morphology Compartment having the specified ID.
Parameters
----------
n: integer
ID of desired compartment
Returns
-------
A morphology object having the specified ID, or None if such a
node doesn't exist
"""
if n < 0 or n >= len(self.compartment_list):
return None
return self._compartment_list[n]
[docs] def parent_of(self, seg):
""" Returns parent of the specified node.
Parameters
----------
seg: integer or Morphology Object
The ID of the child node, or the child node itself
Returns
-------
A morphology object, or None if no parent exists or if the
specified node ID doesn't exist
"""
# if ID passed in, make sure it's converted to a node
# don't trap for exception here -- if supplied segment is
# incorrect, make sure the user knows about it
seg = self._resolve_node_type(seg)
# return parent of specified node
if seg is not None and seg.parent >= 0:
return self._node_list[seg.parent]
return None
[docs] def children_of(self, seg):
""" Returns a list of the children of the specified node
Parameters
----------
seg: integer or Morphology Object
The ID of the parent node, or the parent node itself
Returns
-------
A list of the child morphology objects. If the ID of the parent
node is invalid, None is returned.
"""
seg = self._resolve_node_type(seg)
return [ self._node_list[c] for c in seg.children ]
[docs] def to_dict(self):
"""
Returns a dictionary of Node objects. These Nodes are a copy
of the Morphology. Modifying them will not modify anything in
the Morphology itself.
"""
return { c.n: c.to_dict() for c in self._node_list }
###################################################################
###################################################################
# Information querying and data manipulation
# internal function. takes an integer and returns the node having
# that ID. IF a node is passed in instead, it is returned
def _resolve_node_type(self, seg):
# if node passed then we don't need to convert anything
# if node not passed, try converting value to int
# and using that as an index
if not isinstance(seg, Node):
try:
seg = int(seg)
if seg < 0 or seg >= len(self._node_list):
return None
seg = self._node_list[seg]
except ValueError:
raise TypeError("Object not recognized as morphology Node or index")
return seg
[docs] def change_parent(self, child, parent):
""" Change the parent of a node. The child node is adjusted to
point to the new parent, the child is taken off of the previous
parent's child list, and it is added to the new parent's child list.
Parameters
----------
child: integer or Morphology Object
The ID of the child node, or the child node itself
parent: integer or Morphology Object
The ID of the parent node, or the parent node itself
Returns
-------
Nothing
"""
child_seg = self._resolve_node_type(child)
parent_seg = self._resolve_node_type(parent)
# if child has former parent, remove it from parent's child list
if child_seg.parent >= 0:
old_par = self.node(child_seg.parent)
old_par.children.remove(child_seg.n)
parent_seg.children.append(child_seg.n)
child_seg.parent = parent_seg.n
[docs] def get_dimensions(self):
""" Returns tuple of overall width, height and depth of
morphology.
WARNING: if locations of nodes in morphology are manipulated
then this value can become incorrect. It can be reset and
recalculated by programmitcally setting self.dims to None.
Returns
-------
3 real arrays: [width, height, depth], [min_x, min_y, min_z],
[max_x, max_y, max_z]
"""
if self.dims is None:
min_x = self.node_list[0].x
max_x = self.node_list[0].x
min_y = self.node_list[0].y
max_y = self.node_list[0].y
min_z = self.node_list[0].z
max_z = self.node_list[0].z
for node in self.node_list:
max_x = max(node.x, max_x)
max_y = max(node.y, max_y)
max_z = max(node.z, max_z)
#
min_x = min(node.x, min_x)
min_y = min(node.y, min_y)
min_z = min(node.z, min_z)
self.dims = [(max_x-min_x), (max_y-min_y), (max_z-min_z)], [min_x, min_y, min_z], [max_x, max_y, max_z]
return self.dims
# returns a list of node located within dist of x,y,z
[docs] def find(self, x, y, z, dist, node_type=None):
""" Returns a list of Morphology Objects located within 'dist'
of coordinate (x,y,z). If node_type is specified, the search
will be constrained to return only nodes of that type.
Parameters
----------
x, y, z: float
The x,y,z coordinates from which to search around
dist: float
The search radius
node_type: enum (optional)
One of the following constants: SOMA, AXON,
BASAL_DENDRITE or APICAL_DENDRITE
Returns
-------
A list of all Morphology Objects matching the search criteria
"""
found = []
for seg in self.node_list:
dx = seg.x - x
dy = seg.y - y
dz = seg.z - z
if math.sqrt(dx*dx + dy*dy + dz*dz) <= dist:
if node_type is None or seg.t == node_type:
found.append(seg)
return found
[docs] def node_list_by_type(self, node_type):
""" Return an list of all nodes having the specified
node type.
Parameters
----------
node_type: int
Desired node type
Returns
-------
A list of of Morphology Objects
"""
return [x for x in self._node_list if x.t == node_type]
[docs] def save(self, file_name):
""" Write this morphology out to an SWC file
Parameters
----------
file_name: string
desired name of your SWC file
"""
f = open(file_name, "w")
f.write("#n,type,x,y,z,radius,parent\n")
for seg in self.node_list:
f.write("%d %d " % (seg.n, seg.t))
f.write("%0.4f " % seg.x)
f.write("%0.4f " % seg.y)
f.write("%0.4f " % seg.z)
f.write("%0.4f " % seg.radius)
f.write("%d\n" % seg.parent)
f.close()
# keep for backward compatibility, but don't publish in docs
[docs] def write(self, file_name):
self.save(file_name)
[docs] def sparsify(self, modulo):
""" Return a new Morphology object that has a given number of non-leaf,
non-root nodes removed.
Parameters
----------
modulo: int
keep 1 out of every modulo nodes.
Returns
-------
Morphology
A new morphology instance
"""
# create and return a new morphology instance. make a copy of
# this morphology's node list and manipulate that
nodes = copy.deepcopy(self.node_list)
# figure out which nodes to toss
keep = {}
ctr = 0 # mod counter -- keep every modulo element (starting w/ 1st)
for seg in nodes:
nid = seg.n
if (seg.parent < 0 or
len(seg.children) != 1 or
nodes[seg.parent].t == Morphology.SOMA or
seg.t == Morphology.SOMA):
keep[nid] = True
else:
if ctr % modulo == 0:
keep[nid] = True
else:
keep[nid] = False
ctr += 1
# hook children up to their new parents
for seg in nodes:
if keep[seg.n] is False:
parent_id = seg.parent
while keep[parent_id] is False:
parent_id = nodes[parent_id].parent
for child_id in seg.children:
nodes[child_id].parent = parent_id
# filter out orphans
sparse = []
for seg in nodes:
if keep[seg.n] is True:
sparse.append(seg)
return Morphology(sparse)
####################################################################
####################################################################
def _reconstruct(self):
"""
Internal function.
Restructures data and establishes appropriate internal linking.
Data is re-order, removing 'holes' in the ID sequence so that
each object ID corresponds to its position in node list.
Dictionaries mapping IDs to objects are no longer necessary.
Trees are (re)calculated
Parent-child indices are recalculated
A new compartment list is created
"""
remap = {}
# everything defaults to root. this way if a parent was deleted
# the child will become a new root
for i in range(len(self.node_list)):
remap[i] = -1
# map old old node numbers to new ones. reset n to the new ID
# and put node in new list
new_id = 0
tmp_list = []
for node in self.node_list:
if node is not None:
remap[node.n] = new_id
node.n = new_id
tmp_list.append(node)
new_id += 1
# use map to reset parent values. copy objs to new list
for node in tmp_list:
if node.parent >= 0:
node.parent = remap[node.parent]
# replace node list with newly created node list
self._node_list = tmp_list
# reconstruct parent/child relationship links
############################
# node list is complete and sequential so don't need index
# to resolve relationships
# for each node, reset children array
# for each node, add self to parent's child list
for node in self._node_list:
node.children = []
node.compartment = -1
for node in self._node_list:
if node.parent >= 0:
self._node_list[node.parent].children.append(node.n)
# update tree lists
self._separate_trees()
# verify that each node ID is the same as its position in the
# node list
for i in range(len(self.node_list)):
if i != self.node(i).n:
raise RuntimeError("Internal error detected -- node list not properly formed")
# construct compartment list
# (a compartment spans the distance between two nodes)
self._compartment_list = []
for node in self.node_list:
node.compartment_id = -1
for node in self.node_list:
for child_id in node.children:
endpoint = self.node(child_id)
compartment = Compartment(node, endpoint)
endpoint.compartment_id = len(self._compartment_list)
self._compartment_list.append(compartment)
[docs] def append(self, nodes):
""" Add additional nodes to this Morphology. Those nodes must
originate from another morphology object.
Parameters
----------
nodes: list of Morphology nodes
"""
# construct a map between new and old IDs of added nodes
remap = {}
for i in range(len(nodes)):
remap[i] = -1
# map old old node numbers to new ones. reset n to the new ID
# append new nodes to existing node list
old_count = len(self.node_list)
new_id = old_count
for node in nodes:
if node is not None:
remap[node.n] = new_id
node.n = new_id
self._node_list.append(node)
new_id += 1
# use map to reset parent values. copy objs to new list
for i in range(old_count, len(self.node_list)):
node = self.node_list[i]
if node.parent >= 0:
node.parent = remap[node.parent]
self._reconstruct()
[docs] def convert_type(self, from_type, to_type):
""" Convert all nodes in morphology from one type to another
Parameters
----------
from_type: enum
The node type that will be eliminated and replaced.
Use one of the following constants: SOMA, AXON,
BASAL_DENDRITE, or APICAL_DENDRITE
to_type: enum
The new type that will replace it.
Use one of the following constants: SOMA, AXON,
BASAL_DENDRITE, or APICAL_DENDRITE
"""
for node in self.node_list:
if node.t == from_type:
node.t = to_type
[docs] def stumpify_axon(self, count=10):
""" Remove all axon nodes except the first 'count'
nodes, as counted from the connected axon root.
Parameters
----------
count: Integer
The length of the axon 'stump', in number of nodes
"""
# find connected axon root
axon_root = None
for seg in self.node_list:
if seg.t == Morphology.AXON:
par_id = seg.parent
if par_id >= 0:
par = self.node_list[par_id]
if par.t != Morphology.AXON:
axon_root = seg
break
if axon_root is None:
return
# flag the first 'count' nodes from the axon root
ax = axon_root
for node in self.node_list:
node.flag = None
for i in range(count):
# ignore bifurcations -- go 'count' deep on one line only
ax.flag = i
#ax["flag"] = i
children = ax.children
if len(children) > 0:
ax = self.node(children[0])
# strip out all axons that aren't flagged
for i in range(len(self.node_list)):
seg = self.node_list[i]
if seg.t == Morphology.AXON:
#if "flag" not in seg:
if seg.flag is None:
self.node_list[i] = None
self._reconstruct()
def _strip(self, flagged_for_removal):
""" Internal function with code common between
strip_all_other_types() and strip_type()
"""
# if parent will be stripped and node will remain, convert
# parent to this type so it becomes root (otherwise root
# will move)
root = self.soma_root()
for node_id in self._node_list:
node = self.node(node_id)
if node.parent >= 0:
parent = self.node(node.parent)
parent_flag = flagged_for_removal[parent.n]
node_flag = flagged_for_removal[node.n]
if parent_flag and not node_flag:
# don't do this for soma root
if parent.n != root.n:
parent.t = node.t
flagged_for_removal[parent.n] = False
# removed flagged items
for i in range(len(self.node_list)):
seg = self.node_list[i]
if flagged_for_removal[seg.n]:
# eliminate node
self.node_list[i] = None
elif seg.parent >= 0 and flagged_for_removal[seg.parent]:
# parent was eliminated. make this a new root
seg.parent = -1
self._reconstruct()
# strip out everything but the soma and the specified SWC type
[docs] def strip_all_other_types(self, node_type, keep_soma=True):
""" Strips everything from the morphology except for the
specified type.
Parent and child relationships are updated accordingly, creating
new roots when necessary.
Parameters
----------
node_type: enum
The node type to keep in the morphology.
Use one of the following constants: SOMA, AXON,
BASAL_DENDRITE, or APICAL_DENDRITE
keep_soma: Boolean (optional)
True (default) if soma nodes should remain in the
morpyhology, and False if the soma should also be stripped
"""
flagged_for_removal = {}
# scan nodes and see which ones should be removed. keep a record
# of them
for seg in self.node_list:
if seg.t == node_type:
remove = False
elif seg.t == 1 and keep_soma:
remove = False
else:
remove = True
if remove:
flagged_for_removal[seg.n] = True
else:
flagged_for_removal[seg.n] = False
self._strip(flagged_for_removal)
# strip out the specified SWC type
[docs] def strip_type(self, node_type):
""" Strips all nodes of the specified type from the
morphology.
Parent and child relationships are updated accordingly, creating
new roots when necessary.
Parameters
----------
node_type: enum
The node type to strip from the morphology.
Use one of the following constants: SOMA, AXON,
BASAL_DENDRITE, or APICAL_DENDRITE
"""
flagged_for_removal = {}
for seg in self.node_list:
if seg.t == node_type:
remove = True
else:
remove = False
if remove:
flagged_for_removal[seg.n] = True
else:
flagged_for_removal[seg.n] = False
self._strip(flagged_for_removal)
[docs] def clone(self):
""" Create a clone (deep copy) of this morphology
"""
return copy.deepcopy(self)
[docs] def apply_affine_only_rotation(self, aff):
""" Apply an affine transform to all nodes in this
morphology. Only the rotation element of the transform is
performed (i.e., although the entire transformation and
translation matrix is supplied, only the rotation element
is used). The morphology is translated to the point where
the soma root is at 0,0,0.
Format of the affine matrix is:
[x0 y0 z0] [tx]
[x1 y1 z1] [ty]
[x2 y2 z2] [tz]
where the left 3x3 the matrix defines the affine rotation
and scaling, and the right column is the translation
vector.
The matrix must be collapsed and stored in a list as follows:
[x0 y0, z0, x1, y1, z1, x2, y2, z2, tx, ty, tz]
Parameters
----------
aff: 3x4 array of floats (python 2D list, or numpy 2D array)
the transformation matrix
"""
affine = np.copy(aff)
# remove scale on each axis
scale_x = abs(affine[0] + affine[3] + affine[6])
if scale_x != 0.0:
affine[0] /= scale_x
affine[3] /= scale_x
affine[6] /= scale_x
scale_y = abs(affine[1] + affine[4] + affine[7])
if scale_y != 0.0:
affine[1] /= scale_y
affine[4] /= scale_y
affine[7] /= scale_y
scale_z = abs(affine[2] + affine[5] + affine[8])
if scale_z != 0.0:
affine[2] /= scale_z
affine[5] /= scale_z
affine[8] /= scale_z
# apply rotation
for seg in self.node_list:
x = seg.x*affine[0] + seg.y*affine[1] + seg.z*affine[2]
y = seg.x*affine[3] + seg.y*affine[4] + seg.z*affine[5]
z = seg.x*affine[6] + seg.y*affine[7] + seg.z*affine[8]
seg.x = x
seg.y = y
seg.z = z
# # relocate back to zero
# soma = self.soma_root()
# if soma is not None:
# for seg in self.node_list:
# seg.x -= soma.x
# seg.y -= soma.y
# seg.z -= soma.z
[docs] def apply_affine(self, aff, scale=None):
""" Apply an affine transform to all nodes in this
morphology. Compartment radius is adjusted as well.
Format of the affine matrix is:
[x0 y0 z0] [tx]
[x1 y1 z1] [ty]
[x2 y2 z2] [tz]
where the left 3x3 the matrix defines the affine rotation
and scaling, and the right column is the translation
vector.
The matrix must be collapsed and stored in a list as follows:
[x0 y0, z0, x1, y1, z1, x2, y2, z2, tx, ty, tz]
Parameters
----------
aff: 3x4 array of floats (python 2D list, or numpy 2D array)
the transformation matrix
"""
# In addition to transforming the locations of the morphology
# nodes, the radius of each node must be adjusted.
# There are 2 ways to measure scale from a transform. Assuming
# an isotropic transform, the scale is the cube root of the
# matrix determinant. The other ways is to measure scale
# independently along each axis.
# For now, the node radius is only updated based on the average
# scale along all 3 axes (eg, isotropic assumption), so calculate
# scale using the determinant
#
if scale is None:
# calculate the determinant
determinant = np.linalg.det(np.reshape(aff[0:9], (3, 3)))
# determinant is change of volume that occurred during transform
# assume equal scaling along all axes. take 3rd root to get
# scale factor
det_scale = np.power(abs(determinant), 1.0 / 3.0)
## measure scale along each axis
## keep this code here in case
#scale_x = abs(aff[0] + aff[3] + aff[6])
#scale_y = abs(aff[1] + aff[4] + aff[7])
#scale_z = abs(aff[2] + aff[5] + aff[8])
#avg_scale = (scale_x + scale_y + scale_z) / 3.0;
#
# use determinant for scaling for now as it's most simple
scale = det_scale
for seg in self.node_list:
x = seg.x*aff[0] + seg.y*aff[1] + seg.z*aff[2] + aff[9]
y = seg.x*aff[3] + seg.y*aff[4] + seg.z*aff[5] + aff[10]
z = seg.x*aff[6] + seg.y*aff[7] + seg.z*aff[8] + aff[11]
seg.x = x
seg.y = y
seg.z = z
seg.radius *= scale
def _separate_trees(self):
"""
Construct list of independent trees (each tree has a root of -1).
The soma root, if it exists, is in tree 0.
"""
trees = []
# reset each node's tree ID to indicate that it's not assigned
for seg in self.node_list:
seg.tree_id = -1
# construct trees for each node
# if a node is adjacent an existing tree, merge to it
# if a node is adjacent multiple trees, merge all
for seg in self.node_list:
# see what trees this node is adjacent to
local_trees = []
if seg.parent >= 0 and self.node_list[seg.parent].tree_id >= 0:
local_trees.append(self.node_list[seg.parent].tree_id)
for child_id in seg.children:
child = self.node_list[child_id]
if child.tree_id >= 0:
local_trees.append(child.tree_id)
# figure out which tree to put node into
# if there are muliple possibilities, merge all of them
if len(local_trees) == 0:
tree_num = len(trees) # create new tree
elif len(local_trees) == 1:
tree_num = local_trees[0] # use existing tree
elif len(local_trees) > 1:
# this node is an intersection of multiple trees
# merge all trees into the first one found
tree_num = local_trees[0]
for j in range(1,len(local_trees)):
dead_tree = local_trees[j]
trees[dead_tree] = []
for node in self.node_list:
if node.tree_id == dead_tree:
node.tree_id = tree_num
# merge node into tree
# ensure there's space
while len(trees) <= tree_num:
trees.append([])
trees[tree_num].append(seg)
seg.tree_id = tree_num
# consolidate tree lists into class's tree list object
self._tree_list = []
for tree in trees:
if len(tree) > 0:
self._tree_list.append(tree)
# make soma's tree be the first tree, if soma present
# this should be the case if the file is properly ordered, but
# don't assume that
soma_tree = -1
for seg in self.node_list:
if seg.t == 1:
soma_tree = seg.tree_id
break
if soma_tree > 0:
# swap soma tree for first tree in list
tmp = self._tree_list[soma_tree]
self._tree_list[soma_tree] = self._tree_list[0]
self._tree_list[0] = tmp
# reset node tree_id to correct tree number
self._reset_tree_ids()
def _reset_tree_ids(self):
"""
reset each node's tree_id value to the correct tree number
"""
for i in range(len(self._tree_list)):
for j in range(len(self._tree_list[i])):
self._tree_list[i][j].tree_id = i
def _check_consistency(self):
"""
internal function -- don't publish in the docs
TODO? print warning if unrecognized types are present
Return value: number of errors detected in file
"""
errs = 0
# Make sure that the parents are of proper ID range
n = self.num_nodes
for seg in self.node_list:
if seg.parent >= 0:
if seg.parent >= n:
print("Parent for node %d is invalid (%d)" % (seg.n, seg.parent))
errs += 1
# make sure that each tree has exactly one root
for i in range(self.num_trees):
tree = self.tree(i)
root = -1
for j in range(len(tree)):
if tree[j].parent == -1:
if root >= 0:
print("Too many roots in tree %d" % i)
errs += 1
root = j
if root == -1:
print("No root present in tree %d" % i)
errs += 1
# make sure each axon has at most one root
# find type boundaries. at each axon boundary, walk back up
# tree to root and make sure another axon segment not
# encountered
adoptees = self._find_type_boundary()
for child in adoptees:
if child.t == Morphology.AXON:
par_id = child.parent
while par_id >= 0:
par = self.node_list[par_id]
if par.t == Morphology.AXON:
print("Branch has multiple axon roots")
print(child)
print(par)
errs += 1
break
par_id = par.parent
if errs > 0:
print("Failed consistency check: %d errors encountered" % errs)
return errs
def _find_type_boundary(self):
"""
return a list of segments who have parents that are a different type
"""
adoptees = []
for node in self.node_list:
par = self.parent_of(node)
if par is None:
continue
if node.t != par.t:
adoptees.append(node)
return adoptees
# remove tree from swc's "forest"
[docs] def delete_tree(self, n):
""" Delete tree, and all of its nodes, from the morphology.
Parameters
----------
n: Integer
The tree number to delete
"""
if n < 0:
return
if n >= self.num_trees:
print("Error -- attempted to delete non-existing tree (%d)" % n)
raise ValueError
tree = self.tree(n)
for i in range(len(tree)):
self.node_list[tree[i].n] = None
del self._tree_list[n]
self._reconstruct()
# reset node tree_id to correct tree number
self._reset_tree_ids()
def _print_all_nodes(self):
"""
debugging function. prints all nodes
"""
for node in self.node_list:
print(node.short_string())