def test_cut_real_neuron(): filename = DATA / 'Neuron_slice.h5' result = CutPlane.find(filename, bin_width=10).to_json() assert_equal(result['status'], 'ok') assert_almost_equal(result['cut-plane']['a'], 0) assert_almost_equal(result['cut-plane']['b'], 0) assert_almost_equal(result['cut-plane']['c'], 1) assert_almost_equal(result['cut-plane']['d'], -48.68020515427703, decimal=5) assert_equal(result['cut-plane']['comment'], 'Equation: a*X + b*Y + c*Z + d < 0') leaves_coord = [[63.97577896, 61.52564626, 44.46020393], [70.55578079, 91.74564748, 44.07020454], [-99.8342186, -2.24435372, 48.60020332], [-141.51421891, 20.18564611, 48.68020515], [-53.53421936, 97.40564351, 46.4102047], [56.85578384, -71.19435496, 43.36020546], [36.01578369, 4.57564645, 44.46020393], [34.87578048, 4.86564641, 39.14020424], [16.15578308, -22.08435435, 45.86020546], [34.8457817, 3.39564615, 39.69020348], [61.36578216, -80.39435191, 40.55020409], [85.11578598, -43.26435465, 44.38020592], [39.88578262, -15.05435366, 45.24020271], [88.63578262, 11.38564592, 45.08020287], [132.03578415, 48.62564474, 40.1602047], [-14.65421734, -9.67435355, 47.27020531], [-30.67421685, -16.84435458, 45.71020393], [-35.61421738, -15.95435328, 46.57020454], [-24.96421776, -0.52435374, 46.64020424], [-16.08421765, 19.26564603, 46.49020271], [-6.47421751, 13.07564645, 46.18020515], [-7.89421759, 29.27564626, 45.39020424], [28.88578262, 36.64564519, 42.6602047], [27.37578239, 49.95564657, 45.86020546], [-3.61421762, 44.92564779, 46.02020531], [-65.55421982, 55.61564641, 39.14020424], [37.63578262, 43.8256455, 43.99020271], [48.4157814, 65.95564657, 42.50020485], [21.21578254, 49.98564535, 44.14020424], [35.52578201, 70.56564718, 42.89020424], [5.38578214, 61.3256455, 44.93020515]] assert_array_almost_equal(result['cut-leaves'], leaves_coord, decimal=5) plane = CutPlane.from_json(result, filename) assert_array_almost_equal( [sec.points[-1, COLS.XYZ] for sec in plane.cut_sections], leaves_coord, decimal=5)
def __init__(self, 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): '''Repair the input morphology 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. 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() 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, _ = apical_point_section_segment(self.neuron) if apical_section_id: self.apical_section = self.neuron.sections[apical_section_id] else: self.apical_section = None
def unravel_all(raw_dir, unravelled_dir, raw_planes_dir, unravelled_planes_dir, window_half_length=DEFAULT_WINDOW_HALF_LENGTH): '''Repair all morphologies in input folder ''' if not os.path.exists(raw_planes_dir): raise Exception('{} does not exist'.format(raw_planes_dir)) if not os.path.exists(unravelled_planes_dir): os.mkdir(unravelled_planes_dir) for inputfilename in iter_morphology_files(raw_dir): L.info('Unravelling: %s', inputfilename) outfilename = Path(unravelled_dir, inputfilename.name) raw_plane = CutPlane.from_json( Path(raw_planes_dir, inputfilename.name).with_suffix('.json')) unravelled_plane = Path(unravelled_planes_dir, inputfilename.name).with_suffix('.json') try: neuron, mapping = unravel(str(inputfilename), window_half_length) neuron.write(str(outfilename)) with open(str(unravelled_plane), 'w') as f: json.dump(unravel_plane(raw_plane, mapping).to_json(), f, cls=RepairJSON) except Exception as e: # noqa, pylint: disable=broad-except L.warning('Unravelling %s failed', f) L.warning(e, exc_info=True)
def test_cut_plane_from_rotations_translations(): filename = DATA / 'Neuron_slice.h5' equation = CutPlane.from_rotations_translations([0, 21, -21, 0, 0, 50], morphology=filename, bin_width=10) assert_array_almost_equal(equation.coefs, [35.836795, 0., 93.358043, -4667.902132])
def test_cut_neuron_simple(): filename = DATA / 'simple2.asc' result = CutPlane.find(filename, bin_width=0.2).to_json() ok_('The probability that there is in fact NO cut plane is high: -log(p)' in result['status']) assert_almost_equal(result['cut-plane']['a'], 0) assert_almost_equal(result['cut-plane']['b'], 0) assert_almost_equal(result['cut-plane']['c'], 1) assert_allclose(result['cut-plane']['d'], -2, rtol=0.2)
def test_repaired_neuron(): result = CutPlane.find(DATA / 'bio_neuron-000.h5', bin_width=10).to_json() assert_not_equal(result['status'], 'ok')
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)
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)