def _compute_sholl_data(self, branches): '''Compute the number of termination, bifurcation and continuation section for each neurite type, sholl layer and shell order data[neurite_type][layer][order][action_type] = counts Args: branches: a collection of Neurite or Section that will be traversed Note: This is based on https://bbpcode.epfl.ch/browse/code/platform/BlueRepairSDK/tree/BlueRepairSDK/src/morphstats.cpp#n93 ''' data = defaultdict(lambda: defaultdict(dict)) for branch in branches: origin = self._get_origin(branch) order_offset = self._get_order_offset(branch) for section in branch.ipreorder(): repair_type = self.repair_type_map[section] assert repair_type == self.repair_type_map[branch], \ 'RepairType should not change along the branch way' order = branch_order(section) - order_offset first_layer, last_layer = ( np.linalg.norm(section.points[[0, -1], COLS.XYZ] - origin, axis=1) // self.params['sholl_layer_size']).astype(int) per_type = data[repair_type] starting_layer = min(first_layer, last_layer) # TODO: why starting_layer + 1 and not starting_layer ? # bcoste The continuation from the starting layer should be taken into account # But that's how it is done in: # https://bbpcode.epfl.ch/source/xref/platform/BlueRepairSDK/BlueRepairSDK/src/morphstats.cpp#88 for layer in range(starting_layer + 1, max(first_layer, last_layer)): if order not in per_type[layer]: per_type[layer][order] = { Action.TERMINATION: 0, Action.CONTINUATION: 0, Action.BIFURCATION: 0 } per_type[layer][order][Action.CONTINUATION] += 1 if order not in per_type[last_layer]: per_type[last_layer][order] = { Action.TERMINATION: 0, Action.CONTINUATION: 0, Action.BIFURCATION: 0 } per_type[last_layer][order][Action.BIFURCATION if section. children else Action. TERMINATION] += 1 return data
def _tree_distance(sec1, sec2): ''' Returns the number of sections between the 2 sections Reimplementation of: https://bbpcode.epfl.ch/browse/code/platform/BlueRepairSDK/tree/BlueRepairSDK/src/helper_axon.cpp#n35 raises: if both sections are not part of the same neurite Note: I think the implementation of tree distance is true to the original but I would expect the tree distance of 2 children with the same parent to be 2 and not 1 Because in the current case, (root, child1) and (child1, child2) have the same tree distance and it should probably not be the case ''' original_sections = (sec1, sec2) dist = 0 while True: diff = branch_order(sec1) - branch_order(sec2) if diff == 0: break if diff > 0: sec1 = sec1.parent dist += 1 else: sec2 = sec2.parent dist += 1 if sec1.id == sec2.id: return dist dist -= 1 while sec1.id != sec2.id: sec1 = sec1.parent sec2 = sec2.parent dist += 2 if None in {sec1, sec2}: raise Exception( 'Sections {} and {} are not part of the same neurite'.format( original_sections[0], original_sections[1])) return dist
def _get_order_offset(self, branch): r''' Return what should be considered as the branch order offset for this branch For obliques, the branch order is computed with respect to the branch order of the first oblique section so we have to remove the offset. 3 2 | oblique ---->\ | \| 1 | | | 0 oblique order is 2 - 1 = 1 ''' if self.repair_type_map[branch] == RepairType.oblique: return branch_order(branch) if self.repair_type_map[branch] == RepairType.tuft: return branch_order(self.apical_section) return 0
def _grow(self, section, order_offset, origin): '''grow main method Will either: - continue growing the section - create a bifurcation - terminate the growth Note: based on https://bbpcode.epfl.ch/browse/code/platform/BlueRepairSDK/tree/BlueRepairSDK/src/helper_dendrite.cpp#n387 # noqa, pylint: disable=line-too-long ''' if (self.repair_type_map[section] == RepairType.tuft and _y_cylindrical_extent(section) > self.max_y_cylindrical_extent): return sholl_layer = _get_sholl_layer(section, origin, self.params['sholl_layer_size']) pseudo_order = branch_order(section) - order_offset L.debug('In _grow. Layer: %s, order: %s', sholl_layer, pseudo_order) proba = _get_sholl_proba(self.info['sholl'], self.repair_type_map[section], sholl_layer, pseudo_order) L.debug('action proba[%s][%s][%s]: %s', section.type, sholl_layer, pseudo_order, proba) action = np.random.choice(list(proba.keys()), p=list(proba.values())) if action == Action.CONTINUATION: L.debug('Continuing') backwards_sections = _grow_until_sholl_sphere( section, origin, sholl_layer, self.params, self.taper, self.tip_radius, self.current_trunk_radius) if backwards_sections == 0: self._grow(section, order_offset, origin) L.debug(section.points[-1]) elif action == Action.BIFURCATION: L.debug('Bifurcating') backwards_sections = _grow_until_sholl_sphere( section, origin, sholl_layer, self.params, self.taper, self.tip_radius, self.current_trunk_radius) if backwards_sections == 0: self._bifurcation(section, order_offset) for child in section.children: self.repair_type_map[child] = self.repair_type_map[section] self._grow(child, order_offset, origin)
def _branching_angles(section, order_offset=0): '''Return a list of 2-tuples. The first element is the branching order and the second one is the angles between the direction of the section and its children's ones Note: based on https://bbpcode.epfl.ch/browse/code/platform/BlueRepairSDK/tree/BlueRepairSDK/src/morphstats.cpp#n194 # noqa, pylint: disable=line-too-long ''' if section_length(section) < EPSILON: return [] res = [] branching_order = branch_order(section) - order_offset for child in section.children: if section_length(child) < EPSILON: continue theta = np.math.acos( np.dot(direction(section), direction(child)) / (section_length(section) * section_length(child))) res.append((branching_order, theta)) return res
def _bifurcation(self, section, order_offset): '''Create 2 children at the end of the current section Note: based on https://bbpcode.epfl.ch/browse/code/platform/BlueRepairSDK/tree/BlueRepairSDK/src/helper_dendrite.cpp#n287 # noqa, pylint: disable=line-too-long ''' current_diameter = section.points[-1, COLS.R] * 2 child_diameters = 2 * [ self.params["children_diameter_ratio"] * current_diameter ] median_angle = np.median( self._best_case_angle_data(self.repair_type_map[section], branch_order(section) - order_offset)) last_segment_vec = _last_segment_vector(section) orthogonal = np.cross(last_segment_vec, section.points[-1, COLS.XYZ]) def shuffle_direction(direction_): '''Return the direction to which to grow a child section''' theta = median_angle + np.radians( np.random.random() * self.params['bifurcation_angle']) first_rotation = np.dot(rotation_matrix(orthogonal, theta), direction_) new_dir = np.dot( rotation_matrix(direction_, np.random.random() * np.pi * 2), first_rotation) return new_dir / np.linalg.norm(new_dir) for child_diameter in child_diameters: child_start = section.points[-1, COLS.XYZ] points = [ child_start.tolist(), (child_start + shuffle_direction(last_segment_vec) * self.params['seg_length']).tolist() ] prop = PointLevel(points, [current_diameter, child_diameter]) child = section.append_section(prop) L.debug('section appended: %s', child.id)
np.std(seg_taper_rate), ', min=', np.min(seg_taper_rate), ', max=', np.max(seg_taper_rate), sep='') # Number of bifurcation points. print( 'Number of bifurcation points:', sum(1 for _ in nm.iter_sections(nrn, iterator_type=Tree.ibifurcation_point))) # Number of bifurcation points for apical dendrites print( 'Number of bifurcation points (apical dendrites):', sum(1 for _ in nm.iter_sections(nrn, iterator_type=Tree.ibifurcation_point, neurite_filter=tree_type_checker( nm.APICAL_DENDRITE)))) # Maximum branch order print('Maximum branch order:', max(sectionfunc.branch_order(s) for s in nm.iter_sections(nrn))) # Neuron's bounding box # Note: does not account for soma radius print('Bounding box ((min x, y, z), (max x, y, z))', geom.bounding_box(nrn))