コード例 #1
0
ファイル: legacy_detection.py プロジェクト: jdcourcol/NeuroR
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
コード例 #2
0
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
コード例 #3
0
ファイル: test_detection.py プロジェクト: markovg/NeuroR
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
    ])
コード例 #4
0
ファイル: detection.py プロジェクト: markovg/NeuroR
 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]
コード例 #5
0
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
コード例 #6
0
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
コード例 #7
0
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]
コード例 #8
0
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
コード例 #9
0
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
コード例 #10
0
ファイル: legacy_detection.py プロジェクト: jdcourcol/NeuroR
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))
    ]
コード例 #11
0
    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),
        )
コード例 #12
0
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
コード例 #13
0
ファイル: nrnhines.py プロジェクト: arnaudon/morph-tool
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
コード例 #14
0
    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
コード例 #15
0
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
コード例 #16
0
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
コード例 #17
0
    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
コード例 #18
0
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
コード例 #19
0
def get_sections(neuron, secname):
    secs = list()
    for section in iter_sections(neuron):
        if section.type == getattr(NeuriteType, secname):
            secs.append(section)
    return secs
コード例 #20
0
def print_section_types(neuron, secname):
    for section in iter_sections(neuron):
        print(getattr(NeuriteType, secname))
    return 
コード例 #21
0
    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)
コード例 #22
0
    #  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:',
コード例 #23
0
    #  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:',
コード例 #24
0
ファイル: detection.py プロジェクト: markovg/NeuroR
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])
コード例 #25
0
ファイル: test_get_features.py プロジェクト: jdcourcol/NeuroM
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)
コード例 #26
0
ファイル: test_get_features.py プロジェクト: markovg/NeuroM
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)
コード例 #27
0
ファイル: test_get_features.py プロジェクト: nagyistge/NeuroM
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)
コード例 #28
0
ファイル: main.py プロジェクト: markovg/NeuroR
    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)