Source code for neuron_morphology.features.path

from typing import Optional, List, Dict

from neuron_morphology.constants import (
    SOMA, AXON, APICAL_DENDRITE, BASAL_DENDRITE)

from neuron_morphology.feature_extractor.mark import RequiresRoot, Geometric
from neuron_morphology.feature_extractor.marked_feature import marked
from neuron_morphology.feature_extractor.data import (
    MorphologyLike, get_morphology)


# TODO: There is a breadth_first_traversal method defined on Morphology. We 
# should use that here
def _calculate_max_path_distance(morphology, root, node_types):
    # if root not specified, grab the soma root if it exists, and the
    #   root of the first disconnected tree if not

    if node_types is None:
        node_types = [SOMA, AXON, APICAL_DENDRITE, BASAL_DENDRITE]

    nodes = morphology.get_node_by_types(node_types)
    if root is None:
        root = morphology.get_root()
    total_length = 0.0
    # sum up length for all child compartments
    while len(morphology.get_children(root)) > 0:
        # the next node is a continuation from this node (ie, no
        #   bifurcation). update path length and evaluate the
        #   next node
        # path tracing is done using nodes. if a node is associated
        #   with a compartment (all non-root nodes are) then add that
        #   compartment's length to the accumulated distance
        if len(morphology.get_children(root)) == 1:
            # get length of associated compartment, if it exists, and if
            #   it's not soma
            if root['type'] != SOMA and root['type'] in node_types:
                compartment = morphology.get_compartment_for_node(root, node_types)
                if compartment:
                    total_length += morphology.get_compartment_length(compartment)
            root = morphology.get_children(root)[0]
        else:
            # we reached a bifurcation point
            # recurse to find length of each child branch and then
            #   exit loop
            max_sub_dist = 0.0
            children_of_root = morphology.get_children(root, node_types)
            for child in children_of_root:
                dist = _calculate_max_path_distance(morphology, child, node_types)
                if dist > max_sub_dist:
                    max_sub_dist = dist
            total_length += max_sub_dist
            break
    # the length of this compartment hasn't been included yet, and if it
    #   isn't part of the soma
    if root['type'] != SOMA:
        compartment = morphology.get_compartment_for_node(root, node_types)
        if compartment:
            total_length += morphology.get_compartment_length(compartment)
    return total_length


[docs]def calculate_max_path_distance(morphology, root=None, node_types=None): """ Helper for max_path_distance. See below for more information. """ max_path = 0.0 roots = morphology.get_roots_for_analysis(root, node_types) if roots is None: return float('nan') for node in roots: path = _calculate_max_path_distance(morphology, node, node_types) if path > max_path: max_path = path return max_path
@marked(RequiresRoot) @marked(Geometric) def max_path_distance( data: MorphologyLike, node_types: Optional[List[int]] = None ) -> float: """ Calculate the distance, following the path of adjacent neurites, from the soma to the furthest compartment. This is equivalent to the distance to the furthest SWC node. Parameters ---------- data : the input reconstruction node_types : if provided, restrict the calculation to nodes of these types Returns ------- The along-path distance from the soma to the farthest (in the along-path sense) node. """ morphology = get_morphology(data) return calculate_max_path_distance( morphology, morphology.get_root(), node_types ) @marked(RequiresRoot) @marked(Geometric) def early_branch_path( data: MorphologyLike, node_types: Optional[List[int]] = None, soma: Optional[Dict] = None ) -> float: """ Returns the ratio of the longest 'short' branch from a bifurcation to the maximum path length of the tree. In other words, for each bifurcation, the maximum path length below that branch is calculated, and the shorter of these values is used. The maximum of these short values is divided by the maximum path length. Parameters ---------- data : the input reconstruction node_types : if provided, restrict the calculation to nodes of these types soma : if provided, use this node as the root, otherwise infer the root from the argued morphology Returns ------- ratio of max short branch to max path length """ morphology = get_morphology(data) soma = soma or morphology.get_root() path_len = _calculate_max_path_distance(morphology, soma, node_types) if path_len == 0: return 0.0 nodes = morphology.get_node_by_types(node_types) longest_short = 0.0 for node in nodes: if len(morphology.get_children(node, node_types)) < 2: continue current_short = min( _calculate_max_path_distance(morphology, child, node_types) for child in morphology.children_of(node) ) longest_short = max(longest_short, current_short) return longest_short / path_len def _calculate_mean_contraction(morphology, reference, root, node_types): """ Calculate the average contraction of all sections. In other words, calculate the average ratio of euclidean distance to path distance between all bifurcations in the morphology. Trifurcations are treated as bifurcations. Parameters ---------- morphology: Morphology object reference: dict This is the node of the previous bifurcation root: dict This is the node from which to measure branch contraction under node_types: list (AXON, BASAL_DENDRITE, APICAL_DENDRITE) Type to restrict search to Returns ------- Two scalars: euclidean distance, path distance These are the total bif-bif and bif-tip distances under this root """ # advance to next bifurcation or tip and measure distance to # reference location # on bifurcation, recurse into this function again # treat trifurcation as 2 successive bifs euc_dist = 0 path_dist = morphology.euclidean_distance(reference, root) tot_path = 0.0 tot_euc = 0.0 while len(morphology.get_children(root, node_types)) > 0: # if the next compartment is a continuation (ie, no # bifurcation) then get the next node if len(morphology.get_children(root, node_types)) == 1: next_node = morphology.get_children(root, node_types)[0] path_dist += morphology.euclidean_distance(root, next_node) root = next_node else: # we reached a bifurcation point. recurse and analyze children for child in morphology.get_children(root, node_types): euc, path = _calculate_mean_contraction(morphology, root, child, node_types) tot_euc += euc tot_path += path break euc_dist += morphology.euclidean_distance(root, reference) return tot_euc + euc_dist, tot_path + path_dist
[docs]def calculate_mean_contraction(morphology, root=None, node_types=None): """ See mean_contraction """ roots = morphology.get_roots_for_analysis(root, node_types) if roots is None: return float('nan') euc_dist = 0.0 path_dist = 0.0 for ref in roots: # advance to next bifurcation while len(morphology.get_children(ref, node_types)) == 1: ref = morphology.get_children(ref, node_types)[0] if len(morphology.get_children(ref, node_types)) == 0: continue # analyze each branch from this bifurcation point for child in morphology.get_children(ref, node_types): euc, path = _calculate_mean_contraction(morphology, ref, child, node_types) euc_dist += euc path_dist += path if path_dist == 0.0: return float('nan') return 1.0 * euc_dist / path_dist
@marked(Geometric) @marked(RequiresRoot) def mean_contraction( data: MorphologyLike, node_types: Optional[List[int]] = None ) -> float: """ Calculate the average contraction of all sections. In other words, calculate the average ratio of euclidean distance to path distance between all bifurcations in the morphology. Trifurcations are treated as bifurcations. Parameters ---------- data : the input reconstruction node_types : if provided, restrict the calculation to nodes of these types Returns ------- The average contraction across all sections in this reconstruction """ morphology = get_morphology(data) return calculate_mean_contraction( morphology, morphology.get_root(), node_types )