Example #1
0
def _get_points(morph, method, neurite_type, target_point):
    """Extract relevant points of dendrite to align the morphology, see align_morphology."""
    _to_type = {
        'apical': SectionType.apical_dendrite,
        'axon': SectionType.axon
    }

    for root_section in morph.root_sections:
        if root_section.type == _to_type[neurite_type]:
            if method == AlignMethod.TRUNK.value:
                if target_point is not None:
                    target_secid = point_to_section_segment(
                        morph, target_point)[0] - 1
                    if target_secid is None:
                        return None
                elif neurite_type == 'apical':
                    target_secid = apical_point_section_segment(morph)[0]
                else:
                    raise Exception(
                        f"We don't know how to get target point for {neurite_type}."
                    )

                return np.vstack([
                    section.points for section in
                    morph.sections[target_secid].iter(IterType.upstream)
                ])
            if method == AlignMethod.FIRST_SECTION.value:
                return root_section.points

            if method == AlignMethod.FIRST_SEGMENT.value:
                return root_section.points[:2]

            return np.vstack(
                [section.points for section in root_section.iter()])
Example #2
0
def test_point_to_section_segment():
    neuron = Morphology(os.path.join(DATA, 'apical_test.h5'))

    section, segment = spatial.point_to_section_segment(neuron, [0., 25., 0.])
    eq_(section, 2)
    eq_(segment, 1)

    assert_raises(ValueError, spatial.point_to_section_segment, neuron, [24, 0, 0])
Example #3
0
def test_point_to_section_segment():
    neuron = Morphology(DATA / 'apical_test.h5')

    section, segment = spatial.point_to_section_segment(neuron, [0., 25., 0.])
    assert section == 1
    assert segment == 1

    with pytest.raises(ValueError):
        spatial.point_to_section_segment(neuron, [24, 0, 0])

    section, segment = spatial.point_to_section_segment(neuron, [0., 25.1, 0.],
                                                        rtol=1e-1,
                                                        atol=1e-1)
    assert section == 1
    assert segment == 1

    section, segment = spatial.point_to_section_segment(
        neuron, [0., 25.0001, 0.])
    assert section == 1
    assert segment == 1
Example #4
0
def test_legacy_compare_with_legacy_result():
    '''Comparing results with the old repair launch with the following commands:

    repair --dounravel 0 --inputdir /gpfs/bbp.cscs.ch/project/proj83/home/bcoste/release/out-new/01_ConvertMorphologies --input rp120430_P-2_idA --overlap=true --incremental=false --restrict=true --distmethod=mirror

    The arguments are the one used in the legacy morphology workflow.
    '''
    neuron = load_neuron(DATA_PATH / 'compare-bbpsdk/rp120430_P-2_idA.h5')
    points, sign = test_module.internal_cut_detection(neuron, 'z')
    assert_equal(sign, 1)
    cut_sections = {point_to_section_segment(neuron, point)[0]
                    for point in points}

    legacy_cut_sections = {13,14,17,18,38,39,40,45,58,67,68,69,73,75,76,93,94,101,102,103,105,106,109,110,111,120,124,125,148,149,150,156,157,158,162,163,164,166,167,168,169,192,201,202,203,205,206,208}
    assert_equal(cut_sections, legacy_cut_sections)
Example #5
0
def apical_point_section_segment(neuron):
    '''find the apical point's section and segment

    Args:
        neuron (morphio.Morphology): a morphology

    Returns:
        a tuple (MorphIO section ID, point ID) of the apical point
    '''
    point = apical_point_position(neuron)

    if point is None:
        L.warning('Could not find apical point')
        return None, None

    section, segment = point_to_section_segment(neuron, point)
    section -= 1  # MorphIO ID = NeuroM ID - 1
    return section, segment
Example #6
0
def apical_point_section_segment(neuron):
    """Find the apical point's section and segment.

    Args:
        neuron (morphio.Morphology): a morphology

    Returns:
        Tuple: (NeuroM/MorphIO section ID, point ID) of the apical point. Since NeuroM v2, section
        ids of NeuroM and MorphIO are the same excluding soma.
    """
    point = apical_point_position(neuron)

    if point is None:
        L.warning('Could not find apical point')
        return None, None

    section, segment = point_to_section_segment(neuron, point)
    return section, segment
Example #7
0
def test_legacy_compare_with_legacy_result2():
    '''Comparing results with the old repair launch with the following commands:

    repair --dounravel 0 --inputdir /gpfs/bbp.cscs.ch/project/proj83/home/gevaert/morph-release/morph_release_old_code-2020-07-27/output/04_ZeroDiameterFix --input vd100714B_idB --overlap=true --incremental=false --restrict=true --distmethod=mirror

    The arguments are the one used in the legacy morphology workflow.
    '''
    neuron = load_neuron(DATA_PATH / 'compare-bbpsdk/vd100714B_idB.h5')
    obj = test_module.Repair(inputfile=DATA_PATH /
                             'compare-bbpsdk/vd100714B_idB.h5',
                             legacy_detection=True)

    cut_sections = {
        point_to_section_segment(neuron, point)[0]
        for point in obj.cut_leaves
    }

    legacy_cut_sections = {
        62, 64, 65, 69, 73, 77, 78, 85, 87, 88, 89, 91, 93, 94, 115, 116, 119,
        120, 125, 126, 130, 133, 136, 137, 138, 140, 142, 144, 145, 147, 150,
        151, 152, 159, 165, 171, 172, 175, 177, 179, 180, 182, 184, 188, 191,
        200, 202, 204, 205, 207, 208, 209, 211, 215, 217, 218, 219, 220, 238,
        239, 241, 247, 248, 250, 251, 252, 253, 256, 257, 258, 261, 262, 264,
        266, 267, 283, 288, 289, 290, 291, 293, 294, 295, 316, 318, 320, 322,
        324, 326, 328, 330, 331, 337, 338, 339, 340, 343, 344, 345, 351, 357,
        359, 362, 363, 371, 372, 375, 377, 378, 384, 385, 386, 387, 388, 390,
        391, 394, 416, 426, 427, 429, 430, 431, 438, 439, 440, 441, 453, 466,
        468, 470, 471, 481, 486, 487, 488, 489, 527, 528, 529, 533, 534, 538,
        540, 541, 543, 545, 548, 549, 551, 572, 573, 574, 576, 577, 581, 583,
        584, 588, 595, 596, 598, 599, 602, 607, 608, 609, 610, 613, 614, 615,
        617, 620, 622, 623, 624, 626, 637, 639, 640, 645, 647, 648, 649, 650,
        653, 654, 665, 666, 667, 670, 677, 678, 679, 680, 689, 691, 693, 694,
        703, 716, 717, 721, 723, 725, 726
    }
    assert_equal(cut_sections, legacy_cut_sections)

    obj._fill_repair_type_map()

    types = defaultdict(list)
    for k, v in obj.repair_type_map.items():
        types[v].append(k)

    # offset due to the first section id in the old soft being the soma
    offset = 1

    assert_equal(obj.apical_section, None)

    assert_equal(
        {section.id + offset
         for section in types[RepairType.basal]}, {
             650, 651, 668, 671, 702, 719, 652, 655, 653, 654, 656, 663, 657,
             662, 658, 661, 659, 660, 664, 667, 665, 666, 669, 670, 672, 681,
             673, 680, 674, 679, 675, 678, 676, 677, 682, 695, 683, 684, 685,
             686, 687, 690, 688, 689, 691, 692, 693, 694, 696, 699, 697, 698,
             700, 701, 703, 704, 705, 706, 707, 710, 708, 709, 711, 718, 712,
             713, 714, 715, 716, 717, 720, 727, 721, 722, 723, 724, 725, 726,
             728, 735, 729, 730, 731, 734, 732, 733, 736, 737, 738
         })

    assert_array_equal(
        [section.id + offset for section in types[RepairType.oblique]], [])
    assert_array_equal(
        [section.id + offset for section in types[RepairType.trunk]], [])

    assert_equal({section.id + offset
                  for section in types[RepairType.tuft]}, set())

    intacts = defaultdict(list)

    for sec in obj._find_intact_sub_trees():
        intacts[obj.repair_type_map[sec]].append(sec)

    # Since there is no apical dendrite, all of those are empty
    for extended_type in [
            RepairType.trunk, RepairType.oblique, RepairType.tuft
    ]:
        assert_equal(intacts[extended_type], [])

    assert_equal({sec.id + offset
                  for sec in intacts[RepairType.basal]},
                 {
                     651, 668, 671, 702, 719, 652, 655, 656, 663, 657, 662,
                     658, 661, 659, 660, 664, 669, 672, 681, 673, 674, 675,
                     676, 682, 695, 683, 684, 685, 686, 687, 690, 688, 692,
                     696, 699, 697, 698, 700, 701, 704, 705, 706, 707, 710,
                     708, 709, 711, 718, 712, 713, 714, 715, 720, 727, 722,
                     724, 728, 735, 729, 730, 731, 734, 732, 733, 736, 737, 738
                 })
Example #8
0
def test_legacy_compare_with_legacy_result():
    '''Comparing results with the old repair launch with the following commands:

    repair --dounravel 0 --inputdir /gpfs/bbp.cscs.ch/project/proj83/home/gevaert/morph-release/morph_release_old_code-2020-07-27/output/04_ZeroDiameterFix --input rp120430_P-2_idA --overlap=true --incremental=false --restrict=true --distmethod=mirror

    The arguments are the one used in the legacy morphology workflow.
    '''
    neuron = load_neuron(DATA_PATH / 'compare-bbpsdk/rp120430_P-2_idA.h5')
    obj = test_module.Repair(inputfile=DATA_PATH /
                             'compare-bbpsdk/rp120430_P-2_idA.h5',
                             legacy_detection=True)

    cut_sections = {
        point_to_section_segment(neuron, point)[0]
        for point in obj.cut_leaves
    }

    legacy_cut_sections = {
        13, 14, 17, 18, 38, 39, 40, 45, 58, 67, 68, 69, 73, 75, 76, 93, 94,
        101, 102, 103, 105, 106, 109, 110, 111, 120, 124, 125, 148, 149, 150,
        156, 157, 158, 162, 163, 164, 166, 167, 168, 169, 192, 201, 202, 203,
        205, 206, 208
    }
    assert_equal(cut_sections, legacy_cut_sections)

    obj._fill_repair_type_map()

    types = defaultdict(list)
    for k, v in obj.repair_type_map.items():
        types[v].append(k)

    # offset due to the first section id in the old soft being the soma
    offset = 1

    # These numbers come from the attribute 'apical' from the h5py group 'neuron1'
    section_id, segment_id = 134, 8

    assert_equal(obj.apical_section.id + offset, section_id)

    assert_equal(len(obj.apical_section.points) - 1, segment_id)

    assert_array_equal(
        [section.id + offset for section in types[RepairType.basal]], [
            90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
            105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
            118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130,
            131, 132
        ])

    assert_array_equal(
        [0] + [section.id + offset for section in types[RepairType.axon]], [
            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
            19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
            36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
            53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
            70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86,
            87, 88, 89
        ])
    assert_array_equal(
        [section.id + offset for section in types[RepairType.oblique]],
        [217, 218, 219])
    assert_array_equal(
        [section.id + offset for section in types[RepairType.trunk]],
        [133, 134])

    expected_tufts = {
        135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148,
        149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162,
        163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176,
        177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190,
        191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204,
        205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216
    }
    actual_tufts = {section.id + offset for section in types[RepairType.tuft]}
    assert_equal(actual_tufts, expected_tufts)

    expected_axons = {
        1, 2, 77, 3, 70, 4, 59, 5, 46, 6, 41, 7, 40, 8, 39, 9, 38, 10, 19, 11,
        16, 12, 15, 13, 14, 17, 18, 20, 37, 21, 34, 22, 31, 23, 28, 24, 27, 25,
        26, 29, 30, 32, 33, 35, 36, 42, 45, 43, 44, 47, 58, 48, 53, 49, 52, 50,
        51, 54, 55, 56, 57, 60, 69, 61, 66, 62, 63, 64, 65, 67, 68, 71, 76, 72,
        75, 73, 74, 78, 83, 79, 82, 80, 81, 84, 85, 86, 89, 87, 88
    }
    actual_axons = {section.id + offset for section in types[RepairType.axon]}
    assert_equal(actual_axons, expected_axons)

    intacts = defaultdict(list)

    for sec in obj._find_intact_sub_trees():
        intacts[obj.repair_type_map[sec]].append(sec)

    assert_equal([sec.id + offset for sec in intacts[RepairType.trunk]], [])
    assert_equal([sec.id + offset for sec in intacts[RepairType.oblique]],
                 [217])
    assert_equal(
        {sec.id + offset
         for sec in intacts[RepairType.tuft]}, {
             135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147,
             151, 152, 153, 154, 155, 159, 160, 161, 165, 170, 171, 172, 173,
             174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186,
             187, 188, 189, 190, 191, 193, 194, 195, 196, 197, 198, 199, 200,
             204, 207, 209, 210, 211, 212, 213, 214, 215, 216
         })
Example #9
0
    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)