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()])
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])
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
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)
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
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
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 })
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 })
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)