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 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)