def cut_detect(neuron, cut, offset, axis): '''Detect the cut leaves the old way The cut leaves are simply the leaves that live on the half-space (split along the 'axis' coordinate) with the biggest number of leaves ''' count_plus = count_minus = sum_plus = sum_minus = 0 for leaf in iter_sections(neuron, iterator_type=Tree.ileaf): coord = leaf.points[-1, axis] if coord > offset: count_plus += 1 sum_plus += coord else: count_minus += 1 sum_minus += coord if count_plus == 0 or count_minus == 0: raise Exception( "cut detection warning:one of the sides is empty. can't decide on cut side" ) if -sum_minus / count_minus > sum_plus / count_plus: sign = 1 else: sign = -1 for leaf in iter_sections(neuron, iterator_type=Tree.ileaf): if leaf.points[-1, axis] * sign > offset: cut[leaf] = True return sign
def get_apical_point(neurite, morph, tuft_percent=27): '''Attempt to find the apical point in 'tufted' neurons Consider a neuron: | / | Tuft = 20% |--/ | | / |--/ | ----.----- All endpoints in the top 'tuft_percent' are found, then their common branch segment, furthest from the soma, is identified. Args: morph: neurom.fst._core.Neuron tuft_percent: percentage of the 'height' of the apical dendrite that would enclose the tuft, only leaves in this volume are considered as endpoints. Note that this a spherical shell centered at the soma Returns: Section whose *end* is where the apical branching begins, or None if there is a problem ''' apical = neurite max_distance2 = -float('inf') for leaf in apical.root_node.ileaf(): point = leaf.points[-1, COLS.XYZ] max_distance2 = max(max_distance2, point_dist2(point, morph.soma.center)) min_distance2 = max_distance2 * (1 - tuft_percent / 100.)**2 common_parents = set(nm.iter_sections(apical)) #Iterator to the sections in a neurite, neuron or neuron population. all_parents = set([]) for leaf in apical.root_node.ileaf(): point = leaf.points[-1, COLS.XYZ] if min_distance2 <= point_dist2(point, morph.soma.center): parents = leaf.iupstream() set_parents = set(parents) common_parents &= set_parents all_parents |= set_parents apical_point_section = None for parent_section in nm.iter_sections(apical): if parent_section in common_parents: common_parents.remove(parent_section) if not common_parents: #print parent_section if parent_section in all_parents: #return parent_section apical_point_section = parent_section else: apical_point_section = None #return None return apical_point_section
def _get_points(): '''Utility function to get all neuron points''' neuron = nm.load_neuron(DATA / 'rotated.h5') return np.array([ point for neurite in neuron.neurites for section in nm.iter_sections(neurite) for point in section.points ])
def cut_sections(self): '''Returns sections that ends within the cut plane''' leaves = np.array([leaf for neurite in self.morphology.neurites for leaf in iter_sections(neurite, iterator_type=Section.ileaf)]) leaves_coord = [leaf.points[-1, COLS.XYZ] for leaf in leaves] return leaves[self.distance(leaves_coord) < self.bin_width]
def get_list_of_diff_section_types(morphology, apical_point_sections): morph = morphology _, ax = draw(morph) apical_trunk_sections = [] for apical_point_section in apical_point_sections: some_trunk_sections = list(apical_point_section.iupstream()) apical_trunk_sections = list( set(apical_trunk_sections) | set(some_trunk_sections)) apical_trunk_ids = [section.id for section in apical_trunk_sections] apical_trunk_points = [ section.points[-1, :2] for section in apical_trunk_sections ] for point in apical_trunk_points: ax.plot(point[0], point[1], 'bo', ms=5) apical_tuft_sections = [] for apical_point_section in apical_point_sections: some_tuft_sections = list(apical_point_section.ipreorder()) #print some_tuft_sections apical_tuft_sections = apical_tuft_sections + some_tuft_sections """!!!!!!""" apical_tuft_sections = [ sec for sec in apical_tuft_sections if sec not in apical_point_sections ] """!!!!!!""" apical_tuft_ids = [section.id for section in apical_tuft_sections] apical_tuft_points = [ section.points[-1, :2] for section in apical_tuft_sections ] for point in apical_tuft_points: ax.plot(point[0], point[1], 'go', ms=5) filter1 = lambda n: n.type == nm.APICAL_DENDRITE all_sections = list(nm.iter_sections(morph, neurite_filter=filter1)) all_ids = [section.id for section in all_sections] oblique_ids = set(all_ids) - (set(apical_trunk_ids) | set(apical_tuft_ids)) oblique_sections = [ section for section in all_sections if section.id in oblique_ids ] oblique_points = [section.points[-1, :2] for section in oblique_sections] for point in oblique_points: ax.plot(point[0], point[1], 'yo', ms=5) plt.show() section_types = { 'tuft': apical_tuft_sections, 'trunk': apical_trunk_sections, 'obliques': oblique_sections } return section_types
def find_remote_axons(neuron): remote_axons = list() for section in iter_sections(neuron): if section.is_root(): continue if section.type == NeuriteType.axon and section.parent.type != NeuriteType.axon: remote_axons.append(section) return remote_axons
def _get_cut_leaves(neuron, cut_plane_and_position, tolerance): '''Returns leaves within cut plane tolerance''' cut_plane, position = cut_plane_and_position leaves = np.array([ leaf.points[-1, COLS.XYZ] for neurite in neuron.neurites for leaf in nm.iter_sections(neurite, iterator_type=Tree.ileaf) ]) idx = 'XYZ'.find(cut_plane) return leaves[np.abs(leaves[:, idx] - position) < tolerance]
def optimize(n_clicks, rot_x, rot_y, rot_z, transl_x, transl_y, transl_z): '''Optimize cut plane parameters''' if not n_clicks: return rot_x, rot_y, rot_z, transl_x, transl_y, transl_z points = np.array([ point for neurite in (NEURON.neurites or []) for section in nm.iter_sections(neurite) for point in section.points ]) params = rot_x, rot_y, rot_z, transl_x, transl_y, transl_z result = _minimize(params, points, bin_width=BIN_WIDTH) return result
def get_stem_count(neuron, secname,parnames): partypes=[] for par in parnames: partypes.append(getattr(NeuriteType, par)) SC=0 for section in iter_sections(neuron): if section.type == getattr(NeuriteType, secname) and section.is_root(): SC+=1 continue if section.type == getattr(NeuriteType, secname) and section.parent.type in partypes: SC+=1 return SC
def get_obliques(neuron, extended_types): ''' Returns the oblique roots. https://bbpcode.epfl.ch/source/xref/platform/BlueRepairSDK/BlueRepairSDK/src/helper_dendrite.cpp#212 ''' return [ section for section in iter_sections(neuron) if (extended_types[section] == RepairType.oblique and ( section.parent is None or extended_types[section.parent] == RepairType.trunk)) ]
def _fill_statistics_for_intact_subtrees(self): '''Compute statistics''' branches = self._find_intact_sub_trees() self.info = dict( intact_branching_angles=self._intact_branching_angles(branches), dendritic_sections=[ section for section in iter_sections(self.neuron) if section.type in {nm.APICAL_DENDRITE, nm.BASAL_DENDRITE} ], sholl=self._compute_sholl_data(branches), )
def _make_trace(neuron, plane, prefix='', opacity=1., visible=True, style=None, line_width=2): '''Create the trace to be plotted''' names = defaultdict(int) lines = list() for neurite in iter_neurites(neuron): names[neurite.type] += 1 coords = dict(x=list(), y=list(), z=list()) colors = list() try: default_color = style[neurite]['color'] except KeyError: default_color = TREE_COLOR.get(neurite.root_node.type, 'black') for section in iter_sections(neurite): segs = [(s[0][COLS.XYZ], s[1][COLS.XYZ]) for s in iter_segments(section)] section_style = style.get(section, { 'range': slice(0, len(segs)), 'color': default_color }) range_ = section_style['range'] colors += list(repeat(default_color, 3 * range_.start)) colors += list( repeat(section_style['color'], 3 * (range_.stop - range_.start))) colors += list(repeat(default_color, 3 * (len(segs) - range_.stop))) for i, coord in enumerate('xyz'): coords[coord] += list( chain.from_iterable( (p1[i], p2[i], None) for p1, p2 in segs) if coord in plane else chain.from_iterable((0, 0, None) for _ in segs)) lines.append( go.Scatter3d(name=_neurite_name(neurite, prefix, names), showlegend=False, visible=visible, opacity=opacity, line=dict(color=colors, width=line_width), mode='lines', **coords)) return lines
def NeuroM_section_to_NRN_section(filename: Path): """Returns a mapping from NeuroM section IDs to NRN ones.""" NeuroM_cell = load_morphology(filename) NRN_cell = get_NRN_cell(filename) mapping = {} NRN_sections = list(NRN_cell.icell.all) def is_soma(NRN_section): """Is the NRN section a soma section.""" return NRN_section.name().endswith('.soma[0]') # Skip soma if exists counter = 1 if is_soma(NRN_sections[0]) else 0 for NeuroM_section in iter_sections(NeuroM_cell, neurite_order=NeuriteIter.NRN): if _zero_length_section(NeuroM_section): mapping[NeuroM_section.id] = None if not NeuroM_section.children: L.debug( 'Zero length section without children (NeuroM section id: %s)', NeuroM_section.id) continue L.debug('Zero length section with children') NRN_section = NRN_sections[counter] counter -= 1 else: mapping[NeuroM_section.id] = counter NRN_section = NRN_sections[counter] L.debug('NeuroM section (%s) has been mapped to NRN section (%s)', NeuroM_section.id, mapping[NeuroM_section.id]) # Skip single child NeuroM_section because they have already been # merged in the NeuroM morphology while _has_single_child(NRN_section): L.debug('Skipping single child') counter += 1 NRN_section = NRN_section.children()[0] counter += 1 _validate_section_mapping(NeuroM_cell, NRN_cell, mapping) return mapping
def _find_intact_sub_trees(self): '''Returns intact neurites There is a fallback mechanism in case there are no intact basals: https://bbpcode.epfl.ch/source/xref/platform/BlueRepairSDK/BlueRepairSDK/src/repair.cpp#658 ''' basals = [ neurite.root_node for neurite in iter_neurites(self.neuron) if (neurite.type == NeuriteType.basal_dendrite and is_branch_intact(neurite.root_node, self.cut_leaves)) ] if not basals: L.warning( "No intact basals found. Falling back on less strict selection." ) basals = [ section for section in iter_sections(self.neuron) if (section.type == NeuriteType.basal_dendrite and not is_cut_section(section, self.cut_leaves)) ] axons = [ neurite.root_node for neurite in iter_neurites(self.neuron) if (neurite.type == NeuriteType.axon and is_branch_intact(neurite.root_node, self.cut_leaves)) ] obliques = self._find_intact_obliques() tufts = [ section for section in iter_sections(self.neuron) if (self.repair_type_map[section] == RepairType.tuft and not is_cut_section(section, self.cut_leaves)) ] return basals + obliques + axons + tufts
def _make_trace2d(neuron, plane, prefix='', opacity=1., visible=True, style=None, line_width=2): '''Create the trace to be plotted''' names = defaultdict(int) lines = list() for neurite in iter_neurites(neuron): names[neurite.type] += 1 try: neurite_color = style[neurite]['color'] except KeyError: neurite_color = TREE_COLOR.get(neurite.root_node.type, 'black') name = _neurite_name(neurite, prefix, names) for section in iter_sections(neurite): segs = [(s[0][COLS.XYZ], s[1][COLS.XYZ]) for s in iter_segments(section)] try: colors = style[section]['color'] except KeyError: colors = neurite_color coords = dict() for i, coord in enumerate('xyz'): coords[coord] = list( chain.from_iterable( (p1[i], p2[i], None) for p1, p2 in segs)) coords = dict(x=coords[plane[0]], y=coords[plane[1]]) lines.append( go.Scattergl(name=name, visible=visible, opacity=opacity, showlegend=False, line=dict(color=colors, width=line_width), mode='lines', **coords)) return lines
def repair_type_map(neuron, apical_section): '''Return a dict of extended types''' extended_types = dict() for section in iter_sections(neuron): if section.type == SectionType.apical_dendrite: extended_types[section] = RepairType.oblique elif section.type == SectionType.basal_dendrite: extended_types[section] = RepairType.basal elif section.type == SectionType.axon: extended_types[section] = RepairType.axon if apical_section is not None: for section in apical_section.ipreorder(): extended_types[section] = RepairType.tuft # The value for the apical section must be overriden to 'trunk' for section in apical_section.iupstream(): extended_types[section] = RepairType.trunk return extended_types
def _find_intact_obliques(self): ''' Find root sections of all intact obliques Root obliques are obliques with a section parent of type 'trunk' Note: based on https://bbpcode.epfl.ch/browse/code/platform/BlueRepairSDK/tree/BlueRepairSDK/src/helper_dendrite.cpp#n193 ''' root_obliques = ( section for section in iter_sections(self.neuron) if (self.repair_type_map[section] == RepairType.oblique and not section.is_root() and self.repair_type_map[section.parent] == RepairType.trunk)) intacts = [ oblique for oblique in root_obliques if is_branch_intact(oblique, self.cut_leaves) ] return intacts
def find_cut_plane(neuron, bin_width=10, display=False): """Find the cut plane Parameters: neuron: a Neuron object bin_width: The size of the binning display: where or not to display the control plots Note: It is the user responsability to call matplotlib.pyplot.show() Returns: A dictionary with the following items: status: 'ok' if everything went write, else an informative string cut_plane: a tuple (plane, position) where 'plane' is 'X', 'Y' or 'Z' and 'position' is the position cut_leaves: an np.array of all termination points in the cut plane figures: if 'display' option was used, a dict where values are tuples (fig, ax) for each figure details: A dict currently only containing -LogP of the bin where the cut plane was found 1) The distribution of all points along X, Y and Z is computed and put into 3 histograms. 2) For each histogram we look at the first and last empty bins (ie. the last bin before the histogram starts rising, and the first after it reaches zero again). Under the assumption that there is no cut plane, the posteriori probability of observing this empty bin given the value of the not-empty neighbour bin is then computed. 3) The lowest probability of the 6 probabilities (2 for each axes) corresponds to the cut plane""" points = np.array([ point for neurite in (neuron.neurites or []) for section in nm.iter_sections(neurite) for point in section.points ]) if not points.size: return { 'cut_leaves': None, 'status': "Empty neuron", 'cut_plane': None, 'details': None } hist = _create_1d_distributions(points, bin_width) cut_plane, side, minus_log_p, histo = max(_get_probabilities(hist), key=operator.itemgetter(2)) cut_position = histo[1][side] cut_leaves = _get_cut_leaves(neuron, (cut_plane, cut_position), bin_width) result = { 'cut_leaves': cut_leaves, 'status': _get_status(minus_log_p), 'details': { '-LogP': minus_log_p }, 'cut_plane': (cut_plane, cut_position) } if display: result['figures'] = dict() result['figures'].update( draw_neuron(neuron, (cut_plane, cut_position), cut_leaves)) result['figures'].update(draw_dist_1d(histo, cut_position)) L.info('Trigger the plot display with: matplotlib.pyplot.show()') return result
def get_sections(neuron, secname): secs = list() for section in iter_sections(neuron): if section.type == getattr(NeuriteType, secname): secs.append(section) return secs
def print_section_types(neuron, secname): for section in iter_sections(neuron): print(getattr(NeuriteType, secname)) return
def run(self, outputfile: Path, plot_file: Optional[Path] = None): '''Run''' if self.cut_leaves.size == 0: L.warning('No cut leaves. Nothing to repair for morphology %s', self.inputfile) self.neuron.write(outputfile) return # See https://github.com/BlueBrain/MorphIO/issues/161 keep_axons_alive = list() for axon_donor in self.axon_donors: if self.legacy_detection: plane = CutPlane.find_legacy(axon_donor, 'z') else: plane = CutPlane.find(axon_donor) keep_axons_alive.append(plane) self.donated_intact_axon_sections.extend([ section for section in iter_sections(plane.morphology) if section.type == SectionType.axon and is_branch_intact(section, plane.cut_leaves_coordinates) ]) self._fill_repair_type_map() self._fill_statistics_for_intact_subtrees() intact_axonal_sections = [ section for section in iter_sections(self.neuron) if section.type == SectionType.axon and is_branch_intact(section, self.cut_leaves) ] # BlueRepairSDK used to have a bounding cylinder filter but # I don't know what is it good at so I have commented # the only relevant line # bounding_cylinder_radius = 10000 cut_sections_in_bounding_cylinder = [ section for section in iter_sections(self.neuron) if (is_cut_section(section, cut_points=self.cut_leaves) # and np.linalg.norm(section.points[-1, COLS.XZ]) < bounding_cylinder_radius ) ] used_axon_branches = set() cut_leaves_ids = { section: len(section.points) for section in cut_sections_in_bounding_cylinder } for section in sorted(cut_sections_in_bounding_cylinder, key=section_path_length): type_ = self.repair_type_map[section] if not self.repair_flags.get(type_, True): continue L.info('Repairing: %s, section id: %s', type_, section.id) if type_ in { RepairType.basal, RepairType.oblique, RepairType.tuft }: origin = self._get_origin(section) if section.type == NeuriteType.basal_dendrite: _continuation(section, origin) self._grow(section, self._get_order_offset(section), origin) elif type_ == RepairType.axon: axon.repair(self.neuron, section, intact_axonal_sections, self.donated_intact_axon_sections, used_axon_branches, self.max_y_extent) elif type_ == RepairType.trunk: L.info('Trunk repair is not (nor has ever been) implemented') else: raise Exception('Unknown type: {}'.format(type_)) if plot_file is not None: try: from neuror.view import plot_repaired_neuron plot_repaired_neuron(self.neuron, cut_leaves_ids, plot_file) except ImportError: L.warning( 'Skipping writing plots as [plotly] extra is not installed' ) self.neuron.write(outputfile) L.info('Repair successful for %s', self.inputfile)
# load a neuron from an SWC file nrn = nm.load_neuron(filename) # Some examples of what can be done using iteration # instead of pre-packaged functions that return lists. # The iterations give us a lot of flexibility: we can map # any function that takes a segment or section. # Get of all neurites in cell by iterating over sections, # and summing the section lengths def sec_len(sec): """Return the length of a section.""" return mm.section_length(sec.points) print('Total neurite length (sections):', sum(sec_len(s) for s in nm.iter_sections(nrn))) # Get length of all neurites in cell by iterating over segments, # and summing the segment lengths. # This should yield the same result as iterating over sections. print('Total neurite length (segments):', sum(mm.segment_length(s) for s in nm.iter_segments(nrn))) # get volume of all neurites in cell by summing over segment # volumes print('Total neurite volume:', sum(mm.segment_volume(s) for s in nm.iter_segments(nrn))) # get area of all neurites in cell by summing over segment # areas print('Total neurite surface area:',
# load a neuron from an SWC file nrn = nm.load_neuron(filename) # Some examples of what can be done using iteration # instead of pre-packaged functions that return lists. # The iterations give us a lot of flexibility: we can map # any function that takes a segment or section. # Get of all neurites in cell by iterating over sections, # and summing the section lengths def sec_len(sec): '''Return the length of a section''' return mm.section_length(sec.points) print('Total neurite length (sections):', sum(sec_len(s) for s in nm.iter_sections(nrn))) # Get length of all neurites in cell by iterating over segments, # and summing the segment lengths. # This should yield the same result as iterating over sections. print('Total neurite length (segments):', sum(mm.segment_length(s) for s in nm.iter_segments(nrn))) # get volume of all neurites in cell by summing over segment # volumes print('Total neurite volume:', sum(mm.segment_volume(s) for s in nm.iter_segments(nrn))) # get area of all neurites in cell by summing over segment # areas print('Total neurite surface area:',
def _get_points(neuron): return np.array([point for neurite in (neuron.neurites or []) for section in nm.iter_sections(neurite) for point in section.points])
def test_section_lengths(): ref_seclen = [n.length for n in iter_sections(NEURON)] seclen = fst_get('section_lengths', NEURON) nt.eq_(len(seclen), 84) assert_allclose(seclen, ref_seclen)
def test_section_lengths(): ref_seclen = [n.length for n in iter_sections(NEURON)] seclen = get_feature('section_lengths', NEURON) assert len(seclen) == 84 assert_allclose(seclen, ref_seclen)
def __init__( self, # pylint: disable=too-many-arguments inputfile: Path, axons: Optional[Path] = None, seed: Optional[int] = 0, cut_leaves_coordinates: Optional[NDArray[(3, Any)]] = None, legacy_detection: bool = False, repair_flags: Optional[Dict[RepairType, bool]] = None, apical_point: NDArray[3, float] = None, params: Dict = None): '''Repair the input morphology The repair algorithm uses sholl analysis of intact branches to grow new branches from cut leaves. The algorithm is fairly complex, but can be controled via a few parameters in the params dictionary. By default, they are: _PARAMS = { 'seg_length': 5.0, # lenghts of new segments 'sholl_layer_size': 10, # resoluion of the shll profile 'noise_continuation': 0.5, # together with seg_length, this controls the tortuosity 'soma_repulsion': 0.2, # if 0, previous section direction, if 1, radial direction 'bifurcation_angle': 20, # noise amplitude in degree around mean bif angle on the cell 'path_length_ratio': 0.5, # a smaller value will make a strornger taper rate 'children_diameter_ratio': 0.8, # 1: child diam = parent diam, 0: child diam = tip diam 'tip_percentile': 25, # percentile of tip radius distributions to use as tip radius } Args: inputfile: the input neuron to repair axons: donor axons whose section will be used to repair this axon seed: the numpy seed cut_leaves_coordinates: List of 3D coordinates from which to start the repair legacy_detection: if True, use the legacy cut plane detection (see neuror.legacy_detection) repair_flags: a dict of flags where key is a RepairType and value is whether it should be repaired or not. If not provided, all types will be repaired. apical_point: 3d vector for apical point, else, the automatic apical detection is used if apical_point == -1, no automatic detection will be tried params: repair internal parameters (see comments in code for details) Note: based on https://bbpcode.epfl.ch/browse/code/platform/BlueRepairSDK/tree/BlueRepairSDK/src/repair.cpp#n469 # noqa, pylint: disable=line-too-long ''' np.random.seed(seed) self.legacy_detection = legacy_detection self.inputfile = inputfile self.axon_donors = axons or list() self.donated_intact_axon_sections = list() self.repair_flags = repair_flags or dict() self.params = params if params is not None else _PARAMS if legacy_detection: self.cut_leaves = CutPlane.find_legacy(inputfile, 'z').cut_leaves_coordinates elif cut_leaves_coordinates is None: self.cut_leaves = CutPlane.find( inputfile, bin_width=15).cut_leaves_coordinates else: self.cut_leaves = np.asarray(cut_leaves_coordinates) self.neuron = load_neuron(inputfile) self.repair_type_map = dict() self.max_y_cylindrical_extent = _max_y_dendritic_cylindrical_extent( self.neuron) self.max_y_extent = max( np.max(section.points[:, COLS.Y]) for section in self.neuron.iter()) self.info = dict() apical_section_id = None if apical_point != -1: if apical_point: # recall MorphIO ID = NeuroM ID - 1 apical_section_id = point_to_section_segment( self.neuron, apical_point)[0] - 1 else: apical_section_id, _ = apical_point_section_segment( self.neuron) if apical_section_id: self.apical_section = self.neuron.sections[apical_section_id] else: self.apical_section = None # record the tip radius as a lower bound for diameters with taper, excluding axons # because they are treated separately, with thinner diameters _diameters = [ np.mean(leaf.points[:, COLS.R]) for neurite in self.neuron.neurites if neurite.type is not NeuriteType.axon for leaf in iter_sections(neurite, iterator_type=Section.ileaf) ] self.tip_radius = (np.percentile( _diameters, self.params["tip_percentile"]) if _diameters else None) self.current_trunk_radius = None # estimate a global tapering rate of the morphology as a function of trunk radius, # such that the tip radius is attained on average af max_path_length, defined as a fraction # of the maximal path length via the parameter path_lengt_ratio. The smaller this parameter # the faster the radii will convert to tip_radius. # TODO: maybe we would need this per neurite_type max_path_length = self.params["path_length_ratio"] * np.max( nm.get("terminal_path_lengths_per_neurite", self.neuron)) self.taper = (lambda trunk_radius: (trunk_radius - self.tip_radius) * self.params["seg_length"] / max_path_length)