def morphology(neuron, neurite_type=None, reset_radii=True): pdata = [] edges = [] if neurite_type is None: it = iter_sections(neuron) else: it = iter_sections(neuron, neurite_filter=lambda n: n.type == neurite_type) for section in it: section_points = section.points[:, :4] if reset_radii: section_points[:, 3] = 0.01 section_points = section_points.tolist() N = len(pdata) neurite_edges = [[i, i + 1] for i in range(N, len(section_points) + N - 1)] pdata.extend(section_points) edges.extend(neurite_edges) return numpy.asarray(pdata), numpy.asarray(edges)
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( partition_asymmetry, iter_sections(neurites, iterator_type=Tree.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=Tree.ibifurcation_point, neurite_filter=is_type(neurite_type)): pathlength_diff = abs( downstream_pathlength(section.children[0]) - downstream_pathlength(section.children[1])) asymmetries.append(pathlength_diff / neurite_length) return asymmetries
def plot_tree3d(ax, tree, diameter_scale=_DIAMETER_SCALE, linewidth=_LINEWIDTH, color=None, alpha=_ALPHA): """Generates a figure of the tree in 3d. If the tree contains one single point the plot will be empty \ since no segments can be constructed. Args: ax(matplotlib axes): on what to plot tree(neurom.core.Tree or neurom.core.Neurite): plotted tree 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 """ section_segment_list = [(section, segment) for section in iter_sections(tree) for segment in iter_segments(section)] segs = [(seg[0][COLS.XYZ], seg[1][COLS.XYZ]) for _, seg in section_segment_list] colors = [_get_color(color, section.type) for section, _ in section_segment_list] linewidth = _get_linewidth(tree, diameter_scale=diameter_scale, linewidth=linewidth) collection = Line3DCollection(segs, colors=colors, linewidth=linewidth, alpha=alpha) ax.add_collection3d(collection) _update_3d_datalim(ax, tree)
def plot_tree(ax, tree, plane='xy', diameter_scale=_DIAMETER_SCALE, linewidth=_LINEWIDTH, color=None, alpha=_ALPHA): """Plots a 2d figure of the tree's segments. Args: ax(matplotlib axes): on what to plot tree(neurom.core.Tree or neurom.core.Neurite): plotted tree 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 Note: If the tree contains one single point the plot will be empty since no segments can be constructed. """ plane0, plane1 = _plane2col(plane) section_segment_list = [(section, segment) for section in iter_sections(tree) for segment in iter_segments(section)] segs = [((seg[0][plane0], seg[0][plane1]), (seg[1][plane0], seg[1][plane1])) for _, seg in section_segment_list] colors = [_get_color(color, section.type) for section, _ in section_segment_list] linewidth = _get_linewidth(tree, diameter_scale=diameter_scale, linewidth=linewidth) collection = LineCollection(segs, colors=colors, linewidth=linewidth, alpha=alpha) ax.add_collection(collection)
def bifurcation_partitions(neurites, neurite_type=NeuriteType.all): """Partition at bifurcation points of a collection of neurites.""" return map( bifurcationfunc.bifurcation_partition, iter_sections(neurites, iterator_type=Tree.ibifurcation_point, neurite_filter=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=Tree.ileaf) )
def test_iter_sections_filter(): for ntyp in nm.NEURITE_TYPES: a = [s for n in filter(lambda nn: nn.type == ntyp, POP.neurites) for s in n.iter_sections()] b = [n for n in core.iter_sections(POP, neurite_filter=lambda n: n.type == ntyp)] assert_sequence_equal(a, b)
def has_no_narrow_neurite_section(neuron, neurite_filter, radius_threshold=0.05, considered_section_min_length=50): '''Check if the neuron has dendrites with narrow sections Arguments: neuron(Neuron): The neuron object to test neurite_filter(callable): filter the neurites by this callable radius_threshold(float): radii below this are considered narro considered_section_min_length(float): sections with length below this are not taken into account Returns: CheckResult with result. result.info contains the narrow section ids and their first point ''' considered_sections = (sec for sec in iter_sections(neuron, neurite_filter=neurite_filter) if sec.length > considered_section_min_length) def narrow_section(section): '''Select narrow sections''' return section.points[:, COLS.R].mean() < radius_threshold bad_ids = [(section.id, section.points[1]) for section in considered_sections if narrow_section(section)] 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 test_iter_sections_filter(): for ntyp in nm.NEURITE_TYPES: a = [s.id for n in filter(lambda nn: nn.type == ntyp, POP.neurites) for s in n.iter_sections()] b = [n.id for n in core.iter_sections(POP, neurite_filter=lambda n: n.type == ntyp)] assert_sequence_equal(a, b)
def has_no_narrow_neurite_section(neuron, neurite_filter, radius_threshold=0.05, considered_section_min_length=50): '''Check if the neuron has dendrites with narrow sections Arguments: neuron(Neuron): The neuron object to test neurite_filter(callable): filter the neurites by this callable radius_threshold(float): radii below this are considered narro considered_section_min_length(float): sections with length below this are not taken into account Returns: CheckResult with result. result.info contains the narrow section ids and their first point ''' considered_sections = ( sec for sec in iter_sections(neuron, neurite_filter=neurite_filter) if sec.length > considered_section_min_length) def narrow_section(section): '''Select narrow sections''' return section.points[:, COLS.R].mean() < radius_threshold bad_ids = [(section.id, section.points[1]) for section in considered_sections if narrow_section(section)] return CheckResult(len(bad_ids) == 0, bad_ids)
def partition_asymmetries(neurites, neurite_type=NeuriteType.all): '''Partition asymmetry at bifurcation points of a collection of neurites''' return map( _bifurcationfunc.partition_asymmetry, iter_sections(neurites, iterator_type=Tree.ibifurcation_point, neurite_filter=is_type(neurite_type)))
def n_sections(neurites, neurite_type=NeuriteType.all, iterator_type=Tree.ipreorder): '''Number of sections in a collection of neurites''' return sum(1 for _ in iter_sections(neurites, iterator_type=iterator_type, neurite_filter=is_type(neurite_type)))
def test_iter_section_nrn(): ref = list(core.iter_sections(SIMPLE)) nt.eq_(len(ref), 6) ref = list( core.iter_sections(SIMPLE, neurite_filter=lambda n: n.type == nm.AXON)) nt.eq_(len(ref), 3) ref = list( core.iter_sections( SIMPLE, neurite_filter=lambda n: n.type == nm.BASAL_DENDRITE)) nt.eq_(len(ref), 3) ref = list( core.iter_sections( SIMPLE, neurite_filter=lambda n: n.type == nm.APICAL_DENDRITE)) nt.eq_(len(ref), 0)
def partition_pairs(neurites, neurite_type=NeuriteType.all): '''Partition pairs at bifurcation points of a collection of neurites. Partition pait is defined as the number of bifurcations at the two daughters of the bifurcating section''' return map(_bifurcationfunc.partition_pair, iter_sections(neurites, iterator_type=Tree.ibifurcation_point, neurite_filter=is_type(neurite_type)))
def test_iter_sections_iforking(): ref = [ s for n in POP.neurites for s in n.iter_sections(Tree.iforking_point) ] assert_sequence_equal(ref, [ n for n in core.iter_sections(POP, iterator_type=Tree.iforking_point) ])
def map_segments(func, neurites, neurite_type): ''' Map `func` to all the segments in a collection of neurites `func` accepts a section and returns list of values corresponding to each segment. ''' neurite_filter = is_type(neurite_type) return [ s for ss in iter_sections(neurites, neurite_filter=neurite_filter) for s in func(ss) ]
def segment_lengths(neurites, neurite_type=NeuriteType.all): """Lengths of the segments in a collection of neurites""" def _seg_len(sec): """list of segment lengths of a section""" return np.linalg.norm(np.diff(sec.points[:, : COLS.R], axis=0), axis=1) neurite_filter = is_type(neurite_type) return [s for ss in iter_sections(neurites, neurite_filter=neurite_filter) for s in _seg_len(ss)]
def plot_tree(ax, tree, plane='xy', diameter_scale=_DIAMETER_SCALE, linewidth=_LINEWIDTH, color=None, alpha=_ALPHA, realistic_diameters=False): """Plots a 2d figure of the tree's segments. Args: ax(matplotlib axes): on what to plot tree(neurom.core.Tree or neurom.core.Neurite): plotted tree 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 Note: If the tree contains one single point the plot will be empty since no segments can be constructed. """ plane0, plane1 = _plane2col(plane) section_segment_list = [(section, segment) for section in iter_sections(tree) for segment in iter_segments(section)] colors = [_get_color(color, section.type) for section, _ in section_segment_list] if realistic_diameters: def _get_rectangle(x, y, linewidth): """Draw a rectangle to represent a secgment.""" x, y = np.array(x), np.array(y) diff = y - x angle = np.arctan2(diff[1], diff[0]) % (2 * np.pi) return Rectangle(x - linewidth / 2. * np.array([-np.sin(angle), np.cos(angle)]), np.linalg.norm(diff), linewidth, np.rad2deg(angle)) segs = [_get_rectangle((seg[0][plane0], seg[0][plane1]), (seg[1][plane0], seg[1][plane1]), 2 * segment_radius(seg) * diameter_scale) for _, seg in section_segment_list] collection = PatchCollection(segs, alpha=alpha, facecolors=colors) else: segs = [((seg[0][plane0], seg[0][plane1]), (seg[1][plane0], seg[1][plane1])) for _, seg in section_segment_list] linewidth = _get_linewidth( tree, diameter_scale=diameter_scale, linewidth=linewidth, ) collection = LineCollection(segs, colors=colors, linewidth=linewidth, alpha=alpha) ax.add_collection(collection)
def segment_radii(neurites, neurite_type=NeuriteType.all): """arithmetic mean of the radii of the points in segments in a collection of neurites""" def _seg_radii(sec): """vectorized mean radii""" pts = sec.points[:, COLS.R] return np.divide(np.add(pts[:-1], pts[1:]), 2.0) neurite_filter = is_type(neurite_type) return [s for ss in iter_sections(neurites, neurite_filter=neurite_filter) for s in _seg_radii(ss)]
def segment_midpoints(neurites, neurite_type=NeuriteType.all): """Return a list of segment mid-points in a collection of neurites""" def _seg_midpoint(sec): """Return the mid-points of segments in a section""" pts = sec.points return np.divide(np.add(pts[:-1], pts[1:])[:, :3], 2.0) neurite_filter = is_type(neurite_type) return [s for ss in iter_sections(neurites, neurite_filter=neurite_filter) for s in _seg_midpoint(ss)]
def map_sections(fun, neurites, neurite_type=NeuriteType.all, iterator_type=Tree.ipreorder): '''Map `fun` to all the sections in a collection of neurites''' return map( fun, iter_sections(neurites, iterator_type=iterator_type, neurite_filter=is_type(neurite_type)))
def segment_lengths(neurites, neurite_type=NeuriteType.all): '''Lengths of the segments in a collection of neurites''' def _seg_len(sec): '''list of segment lengths of a section''' vecs = np.diff(sec.points, axis=0)[:, :3] return np.sqrt([np.dot(p, p) for p in vecs]) neurite_filter = is_type(neurite_type) return [s for ss in iter_sections(neurites, neurite_filter=neurite_filter) for s in _seg_len(ss)]
def segment_lengths(neurites, neurite_type=NeuriteType.all): '''Lengths of the segments in a collection of neurites''' def _seg_len(sec): '''list of segment lengths of a section''' return np.linalg.norm(np.diff(sec.points[:, :COLS.R], axis=0), axis=1) neurite_filter = is_type(neurite_type) return [ s for ss in iter_sections(neurites, neurite_filter=neurite_filter) for s in _seg_len(ss) ]
def sibling_ratios(neurites, neurite_type=NeuriteType.all, method='first'): '''Sibling ratios at bifurcation points of a collection of neurites. The sibling ratio is the ratio between the diameters of the smallest and the largest child. It is a real number between 0 and 1. Method argument allows one to consider mean diameters along the child section instead of diameter of the first point. ''' return map( lambda bif_point: _bifurcationfunc.sibling_ratio(bif_point, method), iter_sections(neurites, iterator_type=Tree.ibifurcation_point, neurite_filter=is_type(neurite_type)))
def section_radial_distances(neurites, neurite_type=NeuriteType.all, origin=None, iterator_type=Tree.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 segment_midpoints(neurites, neurite_type=NeuriteType.all): '''Return a list of segment mid-points in a collection of neurites''' def _seg_midpoint(sec): '''Return the mid-points of segments in a section''' pts = sec.points return np.divide(np.add(pts[:-1], pts[1:])[:, :3], 2.0) neurite_filter = is_type(neurite_type) return [ s for ss in iter_sections(neurites, neurite_filter=neurite_filter) for s in _seg_midpoint(ss) ]
def segment_radii(neurites, neurite_type=NeuriteType.all): '''arithmetic mean of the radii of the points in segments in a collection of neurites''' def _seg_radii(sec): '''vectorized mean radii''' pts = sec.points[:, COLS.R] return np.divide(np.add(pts[:-1], pts[1:]), 2.0) neurite_filter = is_type(neurite_type) return [ s for ss in iter_sections(neurites, neurite_filter=neurite_filter) for s in _seg_radii(ss) ]
def diameter_power_relations(neurites, neurite_type=NeuriteType.all, method='first'): '''Calculate the diameter power relation at a bifurcation point as defined in https://www.ncbi.nlm.nih.gov/pubmed/18568015 This quantity gives an indication of how far the branching is from the Rall ratio (when =1).''' return ( _bifurcationfunc.diameter_power_relation(bif_point, method) for bif_point in iter_sections(neurites, iterator_type=Tree.ibifurcation_point, neurite_filter=is_type(neurite_type)))
def segment_taper_rates(neurites, neurite_type=NeuriteType.all): """taper rates of the segments in a collection of neurites The taper rate is defined as the absolute radii differences divided by length of the section """ def _seg_taper_rates(sec): """vectorized taper rates""" pts = sec.points[:, : COLS.TYPE] diff = np.diff(pts, axis=0) distance = np.linalg.norm(diff[:, : COLS.R], axis=1) return np.divide(2 * np.abs(diff[:, COLS.R]), distance) neurite_filter = is_type(neurite_type) return [s for ss in iter_sections(neurites, neurite_filter=neurite_filter) for s in _seg_taper_rates(ss)]
def section_path_lengths(neurites, neurite_type=NeuriteType.all): '''Path lengths of a collection of neurites ''' # Calculates and stores the section lengths in one pass, # then queries the lengths in the path length iterations. # This avoids repeatedly calculating the lengths of the # same sections. dist = {} neurite_filter = is_type(neurite_type) for s in iter_sections(neurites, neurite_filter=neurite_filter): dist[s] = s.length def pl2(node): '''Calculate the path length using cached section lengths''' return sum(dist[n] for n in node.iupstream()) return map_sections(pl2, neurites, neurite_type=neurite_type)
def section_path_lengths(neurites, neurite_type=NeuriteType.all): """Path lengths of a collection of neurites """ # Calculates and stores the section lengths in one pass, # then queries the lengths in the path length iterations. # This avoids repeatedly calculating the lengths of the # same sections. dist = {} neurite_filter = is_type(neurite_type) for s in iter_sections(neurites, neurite_filter=neurite_filter): dist[s] = s.length def pl2(node): """Calculate the path length using cached section lengths""" return sum(dist[n] for n in node.iupstream()) return map_sections(pl2, neurites, neurite_type=neurite_type)
def segment_taper_rates(neurites, neurite_type=NeuriteType.all): '''taper rates of the segments in a collection of neurites The taper rate is defined as the absolute radii differences divided by length of the section ''' def _seg_taper_rates(sec): '''vectorized taper rates''' pts = sec.points[:, :COLS.TYPE] diff = np.diff(pts, axis=0) distance = np.linalg.norm(diff[:, :COLS.R], axis=1) return np.divide(2 * np.abs(diff[:, COLS.R]), distance) neurite_filter = is_type(neurite_type) return [ s for ss in iter_sections(neurites, neurite_filter=neurite_filter) for s in _seg_taper_rates(ss) ]
def segment_path_lengths(neurites, neurite_type=NeuriteType.all): '''Returns pathlengths between all non-root points and their root point''' pathlength = {} neurite_filter = is_type(neurite_type) def _get_pathlength(section): if section.id not in pathlength: if section.parent: pathlength[ section.id] = section.parent.length + _get_pathlength( section.parent) else: pathlength[section.id] = 0 return pathlength[section.id] return np.hstack([ _get_pathlength(section) + sectionfunc.segment_lengths(section) for section in iter_sections(neurites, neurite_filter=neurite_filter) ])
def segment_path_lengths(neurites, neurite_type=NeuriteType.all): """Returns pathlengths between all non-root points and their root point.""" pathlength = {} neurite_filter = is_type(neurite_type) def _get_pathlength(section): if section.id not in pathlength: if section.parent: pathlength[ section.id] = section.parent.length + _get_pathlength( section.parent) else: pathlength[section.id] = 0 return pathlength[section.id] result = [ _get_pathlength(section) + np.cumsum(sectionfunc.segment_lengths(section)) for section in iter_sections(neurites, neurite_filter=neurite_filter) ] return np.hstack(result) if result else np.array([])
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 has_no_narrow_neurite_section(neuron, neurite_filter, radius_threshold=0.05, considered_section_min_length=50): '''Check if the neuron has dendrites with narrow sections sections below 'considered_section_min_length' are not taken into account Returns: CheckResult with result. result.info contains the narrow section ids and their first point ''' considered_sections = ( sec for sec in iter_sections(neuron, neurite_filter=neurite_filter) if sec.length > considered_section_min_length) def narrow_section(section): '''Select narrow sections''' return section.points[:, COLS.R].mean() < radius_threshold bad_ids = [(section.id, section.points[1]) for section in considered_sections if narrow_section(section)] return CheckResult(len(bad_ids) == 0, bad_ids)
def test_iter_sections_ipostorder(): ref = [s for n in POP.neurites for s in n.iter_sections(Tree.ipostorder)] assert_sequence_equal(ref, [n for n in core.iter_sections(POP, iterator_type=Tree.ipostorder)])
def n_segments(neurites, neurite_type=NeuriteType.all): '''Number of segments in a collection of neurites''' return sum(len(s.points) - 1 for s in iter_sections(neurites, neurite_filter=is_type(neurite_type)))
def partition_asymmetries(neurites, neurite_type=NeuriteType.all): '''Partition asymmetry at bifurcation points of a collection of neurites''' return map(_bifurcationfunc.partition_asymmetry, iter_sections(neurites, iterator_type=Tree.ibifurcation_point, neurite_filter=is_type(neurite_type)))
def test_iter_sections_default(): ref = [s for n in POP.neurites for s in n.iter_sections()] assert_sequence_equal(ref, [n for n in core.iter_sections(POP)])
def bifurcation_partitions(neurites, neurite_type=NeuriteType.all): """Partition at bifurcation points of a collection of neurites""" return map( _bifurcationfunc.bifurcation_partition, iter_sections(neurites, iterator_type=Tree.ibifurcation_point, neurite_filter=is_type(neurite_type)), )
def map_sections(fun, neurites, neurite_type=NeuriteType.all, iterator_type=Tree.ipreorder): """Map `fun` to all the sections in a collection of neurites""" return map(fun, iter_sections(neurites, iterator_type=iterator_type, neurite_filter=is_type(neurite_type)))
def n_sections(neurites, neurite_type=NeuriteType.all, iterator_type=Tree.ipreorder): """Number of sections in a collection of neurites""" return sum(1 for _ in iter_sections(neurites, iterator_type=iterator_type, neurite_filter=is_type(neurite_type)))
def n_segments(neurites, neurite_type=NeuriteType.all): """Number of segments in a collection of neurites""" return sum(len(s.points) - 1 for s in iter_sections(neurites, neurite_filter=is_type(neurite_type)))
def test_iter_sections_ipostorder(): ref = [s for n in POP.neurites for s in n.iter_sections(Tree.ipostorder)] assert_sequence_equal( ref, [n for n in core.iter_sections(POP, iterator_type=Tree.ipostorder)])
def bifurcation_partitions(neurites, neurite_type=NeuriteType.all): '''Partition at bifurcation points of a collection of neurites''' return map(bifurcation_partition, iter_sections(neurites, iterator_type=Tree.ibifurcation_point, neurite_filter=is_type(neurite_type)))
def test_iter_sections_iforking(): ref = [s for n in POP.neurites for s in n.iter_sections(Tree.iforking_point)] assert_sequence_equal(ref, [n for n in core.iter_sections(POP, iterator_type=Tree.iforking_point)])