def has_no_dangling_branch(neuron): """Check if the neuron has dangling neurites. Are considered dangling - dendrites whose first point is too far from the soma center - axons whose first point is too far from the soma center AND from any point belonging to a dendrite Arguments: neuron(Neuron): The neuron object to test Returns: CheckResult with a list of all first segments of dangling neurites """ if len(neuron.soma.points) == 0: raise NeuroMError( 'Can\'t check for dangling neurites if there is no soma') soma_center = neuron.soma.points[:, COLS.XYZ].mean(axis=0) recentered_soma = neuron.soma.points[:, COLS.XYZ] - soma_center radius = np.linalg.norm(recentered_soma, axis=1) soma_max_radius = radius.max() dendritic_points = np.array( list( chain.from_iterable(n.points for n in iter_neurites(neuron) if n.type != NeuriteType.axon))) def is_dangling(neurite): """Is the neurite dangling ?.""" starting_point = neurite.points[0][COLS.XYZ] if np.linalg.norm(starting_point - soma_center) - soma_max_radius <= 12.: return False if neurite.type != NeuriteType.axon: return True distance_to_dendrites = np.linalg.norm(dendritic_points[:, COLS.XYZ] - starting_point, axis=1) return np.all( distance_to_dendrites >= 2 * dendritic_points[:, COLS.R] + 2) bad_ids = [(n.root_node.id, [n.root_node.points[0]]) for n in iter_neurites(neuron) if is_dangling(n)] return CheckResult(len(bad_ids) == 0, bad_ids)
def has_no_jumps(neuron, max_distance=30.0, axis='z'): """Check if there are jumps (large movements in the `axis`). Arguments: neuron(Neuron): The neuron object to test max_distance(float): value above which consecutive z-values are considered a jump axis(str): one of x/y/z, which axis to check for jumps Returns: CheckResult with result list of ids of bad sections """ bad_ids = [] axis = { 'x': COLS.X, 'y': COLS.Y, 'z': COLS.Z, }[axis.lower()] for neurite in iter_neurites(neuron): section_segment = ((sec, seg) for sec in iter_sections(neurite) for seg in iter_segments(sec)) for sec, (p0, p1) in islice(section_segment, 1, None): # Skip neurite root segment if max_distance < abs(p0[axis] - p1[axis]): bad_ids.append((sec.id, [p0, p1])) return CheckResult(len(bad_ids) == 0, bad_ids)
def partition_asymmetries(neurites, neurite_type=NeuriteType.all, variant='branch-order'): """Partition asymmetry at bifurcation points of a collection of neurites. Variant: length is a different definition, as the absolute difference in downstream path lenghts, relative to the total neurite path length """ if variant not in {'branch-order', 'length'}: raise ValueError('Please provide a valid variant for partition asymmetry,\ found %s' % variant) if variant == 'branch-order': return map(bifurcationfunc.partition_asymmetry, iter_sections(neurites, iterator_type=Section.ibifurcation_point, neurite_filter=is_type(neurite_type))) asymmetries = list() for neurite in iter_neurites(neurites, filt=is_type(neurite_type)): neurite_length = total_length_per_neurite(neurite)[0] for section in iter_sections(neurite, iterator_type=Section.ibifurcation_point, neurite_filter=is_type(neurite_type)): pathlength_diff = abs(sectionfunc.downstream_pathlength(section.children[0]) - sectionfunc.downstream_pathlength(section.children[1])) asymmetries.append(pathlength_diff / neurite_length) return asymmetries
def plot_neuron3d(ax, nrn, neurite_type=NeuriteType.all, diameter_scale=_DIAMETER_SCALE, linewidth=_LINEWIDTH, color=None, alpha=_ALPHA): """Generates a figure of the neuron, that contains a soma and a list of trees. Args: ax(matplotlib axes): on what to plot nrn(neuron): neuron to be plotted neurite_type(NeuriteType): an optional filter on the neurite type diameter_scale(float): Scale factor multiplied with segment diameters before plotting linewidth(float): all segments are plotted with this width, but only if diameter_scale=None color(str or None): Color of plotted values, None corresponds to default choice alpha(float): Transparency of plotted values """ plot_soma3d(ax, nrn.soma, color=color, alpha=alpha) for neurite in iter_neurites(nrn, filt=tree_type_checker(neurite_type)): plot_tree3d(ax, neurite, diameter_scale=diameter_scale, linewidth=linewidth, color=color, alpha=alpha) ax.set_title(nrn.name)
def test_iter_neurites_filter_mapping(): n = [ n for n in iter_neurites(POP, mapfun=lambda n: len(n.points), filt=lambda n: len(n.points) > 250) ] ref = [500, 500, 500] assert n == ref
def principal_direction_extents(neurites, neurite_type=NeuriteType.all, direction=0): """Principal direction extent of neurites in neurons.""" def _pde(neurite): """Get the PDE of a single neurite.""" # Get the X, Y,Z coordinates of the points in each section points = neurite.points[:, :3] return morphmath.principal_direction_extent(points)[direction] return [_pde(neurite) for neurite in iter_neurites(neurites, filt=is_type(neurite_type))]
def has_no_root_node_jumps(neuron, radius_multiplier=2): """Check that the neurites have no root node jumps. Their first point not should not be further than `radius_multiplier * soma radius` from the soma center """ bad_ids = [] for neurite in iter_neurites(neuron): p0 = neurite.root_node.points[0, COLS.XYZ] distance = np.linalg.norm(p0 - neuron.soma.center) if distance > radius_multiplier * neuron.soma.radius: bad_ids.append((neurite.root_node.id, [p0])) return CheckResult(len(bad_ids) == 0, bad_ids)
def segment_radial_distances(neurites, neurite_type=NeuriteType.all, origin=None): """Returns the list of distances between all segment mid points and origin.""" def _radial_distances(sec, pos): """List of distances between the mid point of each segment and pos.""" mid_pts = 0.5 * (sec.points[:-1, COLS.XYZ] + sec.points[1:, COLS.XYZ]) return np.linalg.norm(mid_pts - pos[COLS.XYZ], axis=1) dist = [] for n in iter_neurites(neurites, filt=is_type(neurite_type)): pos = n.root_node.points[0] if origin is None else origin dist.extend([s for ss in n.iter_sections() for s in _radial_distances(ss, pos)]) return dist
def section_radial_distances(neurites, neurite_type=NeuriteType.all, origin=None, iterator_type=Section.ipreorder): """Section radial distances in a collection of neurites. The iterator_type can be used to select only terminal sections (ileaf) or only bifurcations (ibifurcation_point). """ dist = [] for n in iter_neurites(neurites, filt=is_type(neurite_type)): pos = n.root_node.points[0] if origin is None else origin dist.extend(sectionfunc.section_radial_distance(s, pos) for s in iter_sections(n, iterator_type=iterator_type)) return dist
def plot_neuron(ax, nrn, neurite_type=NeuriteType.all, plane='xy', soma_outline=True, diameter_scale=_DIAMETER_SCALE, linewidth=_LINEWIDTH, color=None, alpha=_ALPHA, realistic_diameters=False): """Plots a 2D figure of the neuron, that contains a soma and the neurites. Args: ax(matplotlib axes): on what to plot neurite_type(NeuriteType|tuple): an optional filter on the neurite type nrn(neuron): neuron to be plotted soma_outline(bool): should the soma be drawn as an outline plane(str): Any pair of 'xyz' diameter_scale(float): Scale factor multiplied with segment diameters before plotting linewidth(float): all segments are plotted with this width, but only if diameter_scale=None color(str or None): Color of plotted values, None corresponds to default choice alpha(float): Transparency of plotted values realistic_diameters(bool): scale linewidths with axis data coordinates """ plot_soma(ax, nrn.soma, plane=plane, soma_outline=soma_outline, linewidth=linewidth, color=color, alpha=alpha) for neurite in iter_neurites(nrn, filt=tree_type_checker(neurite_type)): plot_tree(ax, neurite, plane=plane, diameter_scale=diameter_scale, linewidth=linewidth, color=color, alpha=alpha, realistic_diameters=realistic_diameters) ax.set_title(nrn.name) ax.set_xlabel(plane[0]) ax.set_ylabel(plane[1])
def sholl_crossings(neurites, center, radii, neurite_filter=None): """Calculate crossings of neurites. This function can also be used with a list aa neurites, as follow: secs = (sec for sec in nm.iter_sections(neuron) if complex_filter(sec)) sholl = nm.features.neuronfunc.sholl_crossings(secs, center=neuron.soma.center, radii=np.arange(0, 1000, 100)) Args: neurites(list): morphology on which to perform Sholl analysis, or list of neurites center(Point): center point radii(iterable of floats): radii for which crossings will be counted Returns: Array of same length as radii, with a count of the number of crossings for the respective radius """ def _count_crossings(neurite, radius): """Used to count_crossings of segments in neurite with radius.""" r2 = radius**2 count = 0 for start, end in iter_segments(neurite): start_dist2, end_dist2 = (morphmath.point_dist2(center, start), morphmath.point_dist2(center, end)) count += int(start_dist2 <= r2 <= end_dist2 or end_dist2 <= r2 <= start_dist2) return count return np.array([ sum( _count_crossings(neurite, r) for neurite in iter_neurites(neurites, filt=neurite_filter)) for r in radii ])
def neurite_volume_density(neurites, neurite_type=NeuriteType.all): """Get the volume density per neurite. The volume density is defined as the ratio of the neurite volume and the volume of the neurite's enclosing convex hull TODO: the convex hull fails on some morphologies, it may be good to instead use bounding_box to compute the neurite enclosing volume .. note:: Returns `np.nan` if the convex hull computation fails. """ def vol_density(neurite): """Volume density of a single neurite.""" try: volume = convex_hull(neurite).volume except scipy.spatial.qhull.QhullError: L.exception('Failure to compute neurite volume using the convex hull. ' 'Feature `neurite_volume_density` will return `np.nan`.\n') return np.nan return neurite.volume / volume return list(vol_density(n) for n in iter_neurites(neurites, filt=is_type(neurite_type)))
def total_area_per_neurite(neurites, neurite_type=NeuriteType.all): """Surface area in a collection of neurites. The area is defined as the sum of the area of the sections. """ return [neurite.area for neurite in iter_neurites(neurites, filt=is_type(neurite_type))]
def total_volume_per_neurite(neurites, neurite_type=NeuriteType.all): """Get the volume per neurite in a collection.""" return list(sum(s.volume for s in n.iter_sections()) for n in iter_neurites(neurites, filt=is_type(neurite_type)))
def total_length_per_neurite(neurites, neurite_type=NeuriteType.all): """Get the path length per neurite in a collection.""" return list(sum(s.length for s in n.iter_sections()) for n in iter_neurites(neurites, filt=is_type(neurite_type)))
def terminal_path_lengths_per_neurite(neurites, neurite_type=NeuriteType.all): """Get the path lengths to each terminal point per neurite in a collection.""" return list(sectionfunc.section_path_length(s) for n in iter_neurites(neurites, filt=is_type(neurite_type)) for s in iter_sections(n, iterator_type=Section.ileaf))
def number_of_sections_per_neurite(neurites, neurite_type=NeuriteType.all): """Get the number of sections per neurite in a collection of neurites.""" return list(sum(1 for _ in n.iter_sections()) for n in iter_neurites(neurites, filt=is_type(neurite_type)))
def test_iter_neurites_filter(): for ntyp in nm.NEURITE_TYPES: a = [n for n in POP.neurites if n.type == ntyp] b = [n for n in iter_neurites(POP, filt=lambda n: n.type == ntyp)] assert a == b
def test_iter_neurites_mapping(): n = [n for n in iter_neurites(POP, mapfun=lambda n: len(n.points))] ref = [211, 211, 211, 211, 211, 211, 211, 211, 211, 500, 500, 500] assert n == ref
def n_neurites(neurites, neurite_type=NeuriteType.all): """Number of neurites in a collection of neurites.""" return sum(1 for _ in iter_neurites(neurites, filt=is_type(neurite_type)))
def test_iter_neurites_nrn_order(): actual = list( iter_neurites(REVERSED_NEURITES, neurite_order=NeuriteIter.NRN)) expected = list(reversed(list(iter_neurites(REVERSED_NEURITES)))) assert actual == expected
def _fun(neurites, neurite_type=NeuriteType.all): """Wrap neurite function from outer scope and map into list.""" return list( func(n) for n in iter_neurites(neurites, filt=tree_type_checker(neurite_type)))
def test_iter_neurites_default(): assert list(POP.neurites) == [n for n in iter_neurites(POP)]