def test_independent_axon_count_zero(self): nodes = [ test_node(id=1, type=SOMA, parent_node_id=-1), test_node(id=2, type=AXON, parent_node_id=1) ] test_morphology = Morphology(nodes, node_id_cb=lambda node: node['id'], parent_id_cb=lambda node: node['parent']) test_morphology.validate(strict=False) stat = morphology_statistics(test_morphology) self.assertEqual(stat["Number of Independent Axons"], 0)
def estimate_scale_correction(morphology: Morphology, soma_depth: float, soma_marker_z: float, cut_thickness: Optional[float] = 350): """ Estimate a scale factor to correct the reconstructed morphology for slice shrinkage Prior to reconstruction, the slice shrinks due to evaporation. This is most notable in the z axis, which is the slice thickness. To correct for shrinkage we compare soma depth within the slice obtained soon after cutting the slice to the fixed_soma_depth obtained during the reconstruction. Then the scale correction is estimated as: scale = soma_depth / fixed_soma_depth. This is sensible as long as the z span of the corrected reconstruction is contained within the slice thickness. Thus we also estimate the maximum scale correction as: scale_max = cut_thickness / z_span, and take the smaller of scale and scale_max Parameters ---------- morphology: Morphology object soma_depth: recorded depth of the soma when it was sliced soma_marker_z: soma marker z value from revised marker file (z is on the slice surface for the marker file) cut_thickness: thickness of the cut slice Returns ------- scale factor correction """ soma_morph_z = morphology.get_soma()['z'] fixed_depth = np.abs(soma_morph_z - soma_marker_z) scale = soma_depth / fixed_depth node_z = [node['z'] for node in morphology.nodes()] z_range = np.max(node_z) - np.min(node_z) scale_max = cut_thickness / z_range if scale > scale_max: scale = scale_max warnings.warn(f"Shrinkage scale correction factor: {scale} " f"exceeded the max allowed value: {scale_max}. " f"Will correct for shrinkage using the maximum value." ) return scale
def determine_slice_flip(morphology: Morphology, soma_marker: Dict, slice_image_flip: bool): """ Determines whether the tilt correction should be positive or negative Parameters ---------- morphology: Morphology object soma_marker: soma marker dictionary from reconstruction marker file slice_image_flip: indicates whether the image was flipped relative to the slice (e.g the z axis of the image is opposite to the z axis in the slice) Returns ------- flip_toggle -1 or 1 to be multiplied against tilt correction """ flip_toggle = 1 morph_soma = morphology.get_soma() if (soma_marker['z'] - morph_soma['z']) > 0: flip_toggle = -1 if flip_toggle == 1 and not slice_image_flip: flip_toggle *= -1 return flip_toggle
def parent_daughter_ratio_visitor(node: Dict[str, Any], morphology: Morphology, counters: Dict[str, Union[int, float]], node_types: Optional[List[int]] = None): """ Calculates for a single node the ratio of the node's parent's radius to the node's radius. Stores these values in a provided dictionary. Parameters ---------- node : The node under consideration morphology : The reconstruction to which this node belongs counters : a dictionary used for storing running ratio totals and counts. node_types : skip nodes not of one of these types Notes ----- see mean_parent_daughter_ratio for usage """ parent = morphology.parent_of(node) if parent is None: return if node_types is not None: if node["type"] not in node_types or parent["type"] not in node_types: return counters["ratio_sum"] += parent["radius"] / node["radius"] counters["ratio_count"] += 1
def setUp(self): self.morphology = Morphology( basic_nodes(), node_id_cb=lambda node: node["id"], parent_id_cb=lambda node: node["parent_id"], ) self.data = Data(self.morphology, relative_soma_depth=0.25)
def calculate_transform( gradient_field: xr.DataArray, morph: Morphology, node: Optional[List[float]] = None, ): theta = get_upright_angle(gradient_field, node) transform = np.eye(4) transform[0:3, 0:3] = aff.rotation_from_angle(theta) soma = morph.get_soma() cos_theta = np.cos(theta) sin_theta = np.sin(theta) transform[0:3, 3] = np.asarray([ -soma["x"] * cos_theta + soma["y"] * sin_theta + soma["x"], -soma["x"] * sin_theta - soma["y"] * cos_theta + soma["y"], 0 ]) output = { 'upright_transform': aff.AffineTransform(transform), 'upright_angle': theta } return output
def build(self): """ Construct a Morphology object using this builder. This is a non- destructive operation. The Morphology will be validated at this stage. """ return Morphology(self.nodes, node_id_cb=lambda node: node["id"], parent_id_cb=lambda node: node["parent"])
def test_tree(nodes=None, strict_validation=False): if not nodes: return None for node in nodes: # unfortunately, pandas automatically promotes numeric types to float in to_dict node['parent'] = int(node['parent']) node['id'] = int(node['id']) node['type'] = int(node['type']) node_id_cb = lambda node: node['id'] parent_id_cb = lambda node: node['parent'] morpho = Morphology(nodes, node_id_cb, parent_id_cb) morpho.validate(strict=strict_validation) return morpho
def setUp(self): # morphology with 3/4 axons below soma in z self.morphology = Morphology( [ { "id": 0, "parent_id": -1, "type": SOMA, "x": 0, "y": 0, "z": 100, "radius": 5 }, { "id": 1, "parent_id": 0, "type": AXON, "x": 0, "y": 0, "z": 110, "radius": 1 }, { "id": 2, "parent_id": 1, "type": AXON, "x": 0, "y": 0, "z": 90, "radius": 1 }, { "id": 3, "parent_id": 2, "type": AXON, "x": 0, "y": 0, "z": 80, "radius": 1 }, { "id": 4, "parent_id": 3, "type": AXON, "x": 0, "y": 0, "z": 70, "radius": 1 }, ], node_id_cb=lambda node: node["id"], parent_id_cb=lambda node: node["parent_id"], ) self.data = Data(self.morphology)
def setUp(self): self.morphology = Morphology( [ { "id": 0, "parent_id": -1, "type": SOMA, "x": 0, "y": 0, "z": 100, "radius": 1 }, { "id": 1, "parent_id": 0, "type": AXON, "x": 0, "y": 0, "z": 110, "radius": 20 }, { "id": 2, "parent_id": 1, "type": AXON, "x": 0, "y": 0, "z": 120, "radius": 1 }, { "id": 3, "parent_id": 0, "type": APICAL_DENDRITE, "x": 0, "y": 3, "z": 100, "radius": 10 }, { "id": 4, "parent_id": 3, "type": APICAL_DENDRITE, "x": 0, "y": 6, "z": 100, "radius": 1 }, ], node_id_cb=lambda node: node["id"], parent_id_cb=lambda node: node["parent_id"], )
def transform_morphology( self, morphology: Morphology, clone: bool = False, scale_radius: bool = True, ) -> Morphology: """ Apply this transform to all nodes in a morphology. Parameters ---------- morphology: a Morphology loaded from an swc file clone: make a new object if True scale_radius: apply radius scaling if True Returns ------- A Morphology """ if clone: morphology = morphology.clone() if scale_radius: scaling_factor = self._get_scaling_factor() else: scaling_factor = 1 for node in morphology.nodes(): coordinates = np.array((node['x'], node['y'], node['z']), dtype=float) new_coordinates = self.transform(coordinates) node['x'] = new_coordinates[0] node['y'] = new_coordinates[1] node['z'] = new_coordinates[2] # approximate with uniform scaling in each dimension node['radius'] *= scaling_factor return morphology
def setUp(self): nodes = basic_nodes() self.sizes = np.random.rand(len(nodes)) for sz, node in zip(self.sizes, nodes): node["radius"] = sz self.morphology = Morphology( nodes, node_id_cb=lambda node: node["id"], parent_id_cb=lambda node: node["parent_id"], ) self.mean_diameter = np.mean(self.sizes) * 2
def test_morphology_from_data_file_by_node_type(node_types=None): morphology = swc.morphology_from_swc(test_file) nodes = morphology.get_node_by_types(node_types) for node in nodes: # unfortunately, pandas automatically promotes numeric types to float in to_dict node['parent'] = int(node['parent']) node['id'] = int(node['id']) node['type'] = int(node['type']) node_id_cb = lambda node: node['id'] parent_id_cb = lambda node: node['parent'] axon_only_morphology = Morphology(nodes, node_id_cb, parent_id_cb) return axon_only_morphology
def calculate_outer_bifs(morphology: Morphology, soma: Dict, node_types: Optional[List[int]]) -> int: """ Counts the number of bifurcation points beyond the a sphere with 1/2 the radius from the soma to the most distant point in the morphology, with that sphere centered at the soma. Parameters ---------- morphology: Describes the structure of a neuron soma: Must have keys "x", "y", and "z", describing the position of this morphology's soma in node_types: Restrict included nodes to these types. See neuron_morphology.constants for avaiable node types. Returns ------- the number of bifurcations """ nodes = morphology.get_node_by_types(node_types) far = 0 for node in nodes: dist = morphology.euclidean_distance(soma, node) if dist > far: far = dist count = 0 rad = far / 2.0 for node in nodes: if len(morphology.children_of(node)) > 1: dist = morphology.euclidean_distance(soma, node) if dist > rad: count += 1 return count
def morphology_from_swc(swc_path): swc_data = read_swc(swc_path, sep=' ') nodes = swc_data.to_dict('record') for node in nodes: # unfortunately, pandas automatically promotes numeric types to float in to_dict node['parent'] = int(node['parent']) node['id'] = int(node['id']) node['type'] = int(node['type']) return Morphology( nodes, node_id_cb=lambda node: node['id'], parent_id_cb=lambda node: node['parent'] )
def morphology_png_to_s3(bucket: str, key: str, morphology: Morphology): tmp_path = key.split("/")[-1] nodes = morphology.nodes() x = [node['x'] for node in nodes] y = [node['y'] for node in nodes] z = [node['z'] for node in nodes] fig, ax = plt.subplots(1, 2) ax[0].scatter(x, y, s=0.1) ax[0].set_title('x-y view') ax[1].scatter(z, y, s=0.1) ax[1].set_title('z-y view') fig.suptitle(tmp_path[:-4], fontsize=16) fig.savefig(tmp_path) s3.upload_file(Filename=tmp_path, Bucket=bucket, Key=key) os.remove(tmp_path) return key
def test_morphology_large(): nodes = [ test_node(id=1, type=SOMA, x=800, y=610, z=30, radius=35, parent_node_id=-1), test_node(id=2, type=BASAL_DENDRITE, x=400, y=600, z=10, radius=3, parent_node_id=1), test_node(id=3, type=BASAL_DENDRITE, x=430, y=630, z=20, radius=3, parent_node_id=2), test_node(id=4, type=BASAL_DENDRITE, x=460, y=660, z=30, radius=3, parent_node_id=3), test_node(id=5, type=BASAL_DENDRITE, x=490, y=690, z=40, radius=3, parent_node_id=4), test_node(id=6, type=APICAL_DENDRITE, x=600, y=300, z=20, radius=3, parent_node_id=1), test_node(id=7, type=APICAL_DENDRITE, x=630, y=330, z=30, radius=3, parent_node_id=6), test_node(id=8, type=APICAL_DENDRITE, x=660, y=360, z=40, radius=3, parent_node_id=7), test_node(id=9, type=APICAL_DENDRITE, x=690, y=390, z=50, radius=3, parent_node_id=8), test_node(id=10, type=APICAL_DENDRITE, x=710, y=420, z=60, radius=3, parent_node_id=9), test_node(id=11, type=APICAL_DENDRITE, x=740, y=450, z=70, radius=3, parent_node_id=10), test_node(id=12, type=AXON, x=900, y=600, z=30, radius=3, parent_node_id=1), test_node(id=13, type=AXON, x=930, y=630, z=40, radius=3, parent_node_id=12), test_node(id=14, type=AXON, x=960, y=660, z=50, radius=3, parent_node_id=13), test_node(id=15, type=AXON, x=990, y=690, z=60, radius=3, parent_node_id=14), test_node(id=16, type=AXON, x=1020, y=720, z=70, radius=3, parent_node_id=15), test_node(id=17, type=AXON, x=1050, y=750, z=80, radius=3, parent_node_id=16) ] for node in nodes: # unfortunately, pandas automatically promotes numeric types to float in to_dict node['parent'] = int(node['parent']) node['id'] = int(node['id']) node['type'] = int(node['type']) return Morphology(nodes, node_id_cb=lambda node: node['id'], parent_id_cb=lambda node: node['parent'])
def test_morphology_small_multiple_trees(): nodes = [ test_node(id=1, type=SOMA, x=800, y=610, z=30, radius=35, parent_node_id=-1), test_node(id=2, type=BASAL_DENDRITE, x=400, y=600, z=10, radius=3, parent_node_id=1), test_node(id=3, type=APICAL_DENDRITE, x=600, y=300, z=20, radius=3, parent_node_id=1), test_node(id=4, type=AXON, x=900, y=600, z=30, radius=3, parent_node_id=1), test_node(id=5, type=AXON, x=900, y=600, z=30, radius=3, parent_node_id=-1), test_node(id=6, type=AXON, x=900, y=600, z=30, radius=3, parent_node_id=5), test_node(id=7, type=AXON, x=900, y=600, z=30, radius=3, parent_node_id=6), test_node(id=8, type=AXON, x=900, y=600, z=30, radius=3, parent_node_id=7), test_node(id=9, type=AXON, x=900, y=600, z=30, radius=3, parent_node_id=8) ] for node in nodes: # unfortunately, pandas automatically promotes numeric types to float in to_dict node['parent'] = int(node['parent']) node['id'] = int(node['id']) node['type'] = int(node['type']) return Morphology(nodes, node_id_cb=lambda node: node['id'], parent_id_cb=lambda node: node['parent'])
def setUp(self): # Create an axon that extends positively, # and a basal dendrite that extends negatively self.one_dim_neuron = Morphology( [ { "id": 0, "parent_id": -1, "type": SOMA, "x": 0, "y": 100, "z": 0, "radius": 10 }, # Axon node [100, 125, 150, 175, 200] { "id": 1, "parent_id": 0, "type": AXON, "x": 0, "y": 100, "z": 0, "radius": 3 }, { "id": 2, "parent_id": 1, "type": AXON, "x": 0, "y": 125, "z": 0, "radius": 3 }, { "id": 3, "parent_id": 2, "type": AXON, "x": 0, "y": 150, "z": 0, "radius": 3 }, { "id": 4, "parent_id": 3, "type": AXON, "x": 0, "y": 175, "z": 0, "radius": 3 }, { "id": 5, "parent_id": 4, "type": AXON, "x": 0, "y": 200, "z": 0, "radius": 3 }, # Basal node [100, 75, 50] { "id": 11, "parent_id": 0, "type": BASAL_DENDRITE, "x": 0, "y": 100, "z": 0, "radius": 3 }, { "id": 12, "parent_id": 11, "type": BASAL_DENDRITE, "x": 0, "y": 75, "z": 0, "radius": 3 }, { "id": 13, "parent_id": 12, "type": BASAL_DENDRITE, "x": 0, "y": 50, "z": 0, "radius": 3 }, ], node_id_cb=lambda node: node["id"], parent_id_cb=lambda node: node["parent_id"], ) self.one_dim_neuron_data = Data(self.one_dim_neuron) self.dimension_features = nested_specialize( di.dimension, [COORD_TYPE_SPECIALIZATIONS, NEURITE_SPECIALIZATIONS])
class TestOuterBifurcations(unittest.TestCase): def setUp(self): self.one_dim_neuron = Morphology([ { "id": 0, "parent_id": -1, "type": SOMA, "x": 0, "y": 0, "z": 100, "radius": 1 }, { "id": 1, "parent_id": 0, "type": AXON, "x": 0, "y": 0, "z": 101, "radius": 1 }, { "id": 2, "parent_id": 0, "type": APICAL_DENDRITE, "x": 0, "y": 0, "z": 102, "radius": 1 }, { # bifurcates and is within 120 "id": 3, "parent_id": 1, "type": AXON, "x": 0, "y": 0, "z": 110, "radius": 1 }, { # This is the farthest node from the root "id": 4, "parent_id": 3, "type": AXON, "x": 0, "y": 0, "z": 140, "radius": 1 }, { # bifurcates, and is beyond 120 "id": 5, "parent_id": 3, "type": AXON, "x": 0, "y": 0, "z": 130, "radius": 1 }, { "id": 6, "parent_id": 5, "type": AXON, "x": 0, "y": 0, "z": 135, "radius": 1 }, { "id": 7, "parent_id": 5, "type": AXON, "x": 0, "y": 0, "z": 136, "radius": 1 }, { # bifurcates and is beyond 120 "id": 8, "parent_id": 2, "type": APICAL_DENDRITE, "x": 0, "y": 0, "z": 125, "radius": 1 }, { "id": 9, "parent_id": 8, "type": APICAL_DENDRITE, "x": 0, "y": 0, "z": 126, "radius": 1 }, { "id": 10, "parent_id": 8, "type": APICAL_DENDRITE, "x": 0, "y": 0, "z": 127, "radius": 1 }, ], node_id_cb=lambda node: node["id"], parent_id_cb=lambda node: node["parent_id"], ) self.one_dim_neuron_data = Data(self.one_dim_neuron) self.neurite_features = specialize( bf.num_outer_bifurcations, {AxonSpec, ApicalDendriteSpec, BasalDendriteSpec} ) def extract(self, feature): extractor = FeatureExtractor([feature]) return ( extractor.extract(self.one_dim_neuron_data) .results ) def test_calculate_outer_bifs(self): self.assertEqual(bf.calculate_outer_bifs( self.one_dim_neuron, self.one_dim_neuron.get_root(), None ), 2) def test_num_outer_bifurcations(self): self.assertEqual( self.extract(bf.num_outer_bifurcations)["num_outer_bifurcations"], 2 ) def test_apical_num_outer_bifurcations(self): self.assertEqual( self.extract(self.neurite_features)["apical_dendrite.num_outer_bifurcations"], 1 ) def test_axon_num_outer_bifurcations(self): self.assertEqual( self.extract(self.neurite_features)["axon.num_outer_bifurcations"], 1 ) def test_basal_num_outer_bifurcations(self): # skipped due to no basal dendrite nodes self.assertNotIn( "basal_dendrite.num_outer_bifurcations", self.extract(self.neurite_features), )
def setUp(self): self.one_dim_neuron = Morphology([ { "id": 0, "parent_id": -1, "type": SOMA, "x": 0, "y": 0, "z": 100, "radius": 1 }, { "id": 1, "parent_id": 0, "type": AXON, "x": 0, "y": 0, "z": 101, "radius": 1 }, { "id": 2, "parent_id": 0, "type": APICAL_DENDRITE, "x": 0, "y": 0, "z": 102, "radius": 1 }, { # bifurcates and is within 120 "id": 3, "parent_id": 1, "type": AXON, "x": 0, "y": 0, "z": 110, "radius": 1 }, { # This is the farthest node from the root "id": 4, "parent_id": 3, "type": AXON, "x": 0, "y": 0, "z": 140, "radius": 1 }, { # bifurcates, and is beyond 120 "id": 5, "parent_id": 3, "type": AXON, "x": 0, "y": 0, "z": 130, "radius": 1 }, { "id": 6, "parent_id": 5, "type": AXON, "x": 0, "y": 0, "z": 135, "radius": 1 }, { "id": 7, "parent_id": 5, "type": AXON, "x": 0, "y": 0, "z": 136, "radius": 1 }, { # bifurcates and is beyond 120 "id": 8, "parent_id": 2, "type": APICAL_DENDRITE, "x": 0, "y": 0, "z": 125, "radius": 1 }, { "id": 9, "parent_id": 8, "type": APICAL_DENDRITE, "x": 0, "y": 0, "z": 126, "radius": 1 }, { "id": 10, "parent_id": 8, "type": APICAL_DENDRITE, "x": 0, "y": 0, "z": 127, "radius": 1 }, ], node_id_cb=lambda node: node["id"], parent_id_cb=lambda node: node["parent_id"], ) self.one_dim_neuron_data = Data(self.one_dim_neuron) self.neurite_features = specialize( bf.num_outer_bifurcations, {AxonSpec, ApicalDendriteSpec, BasalDendriteSpec} )
def setUp(self): self.morphology = Morphology([ { "id": 0, "parent_id": -1, "type": SOMA, "x": 0, "y": 0, "z": 100, "radius": 1 }, { "id": 1, "parent_id": 0, "type": AXON, "x": 0, "y": 0, "z": 101, "radius": 1 }, { "id": 2, "parent_id": 0, "type": APICAL_DENDRITE, "x": 0, "y": 0, "z": 102, "radius": 1 }, { # bifurcates "id": 3, "parent_id": 1, "type": AXON, "x": 0, "y": 0, "z": 110, "radius": 1 }, { "id": 4, "parent_id": 3, "type": AXON, "x": 0, "y": 0, "z": 140, "radius": 1 }, { # this node backtracks, causing the path distance to differ from the euclidean "id": 11, "parent_id": 4, "type": AXON, "x": 0, "y": 0, "z": 130, "radius": 1 }, { # bifurcates "id": 5, "parent_id": 3, "type": AXON, "x": 0, "y": 0, "z": 130, "radius": 1 }, { "id": 6, "parent_id": 5, "type": AXON, "x": 0, "y": 0, "z": 135, "radius": 1 }, { "id": 7, "parent_id": 5, "type": AXON, "x": 30, "y": 0, "z": 103, "radius": 1 }, { # bifurcates "id": 8, "parent_id": 2, "type": APICAL_DENDRITE, "x": 0, "y": 0, "z": 125, "radius": 1 }, { "id": 9, "parent_id": 8, "type": APICAL_DENDRITE, "x": 0, "y": 0, "z": 126, "radius": 1 }, { "id": 10, "parent_id": 8, "type": APICAL_DENDRITE, "x": 0, "y": 0, "z": 127, "radius": 1 }, ], node_id_cb=lambda node: node["id"], parent_id_cb=lambda node: node["parent_id"], )
def setUp(self): self.morphology = Morphology( basic_nodes(), node_id_cb=lambda node: node["id"], parent_id_cb=lambda node: node["parent_id"], )
def setUp(self): # Create an Axon that extends y 100 to 120 # and Basal Dendrite that extends y 100 to 110 and 100 to 80 # So that 3/4 axon nodes overlap, 1/4 are above basal # and that 3/6 basal are below, and 3/6 overlap self.one_dim_neuron = Morphology( [ { "id": 0, "parent_id": -1, "type": SOMA, "x": 0, "y": 100, "z": 0, "radius": 10 }, # Axon y 100 to 150 { "id": 1, "parent_id": 0, "type": AXON, "x": 0, "y": 101, "z": 0, "radius": 3 }, { "id": 2, "parent_id": 1, "type": AXON, "x": 0, "y": 102, "z": 0, "radius": 3 }, { "id": 3, "parent_id": 2, "type": AXON, "x": 0, "y": 110, "z": 0, "radius": 3 }, { "id": 4, "parent_id": 3, "type": AXON, "x": 0, "y": 120, "z": 0, "radius": 3 }, { "id": 11, "parent_id": 0, "type": BASAL_DENDRITE, "x": 0, "y": 101, "z": 0, "radius": 3 }, { "id": 12, "parent_id": 11, "type": BASAL_DENDRITE, "x": 0, "y": 102, "z": 0, "radius": 3 }, { "id": 13, "parent_id": 12, "type": BASAL_DENDRITE, "x": 0, "y": 110, "z": 0, "radius": 3 }, # Basal Dendrite y 100 to 80 { "id": 14, "parent_id": 12, "type": BASAL_DENDRITE, "x": 0, "y": 99, "z": 0, "radius": 3 }, { "id": 15, "parent_id": 14, "type": BASAL_DENDRITE, "x": 0, "y": 90, "z": 0, "radius": 3 }, { "id": 16, "parent_id": 15, "type": BASAL_DENDRITE, "x": 0, "y": 80, "z": 0, "radius": 3 }, ], node_id_cb=lambda node: node["id"], parent_id_cb=lambda node: node["parent_id"], ) self.one_dim_neuron_data = Data(self.one_dim_neuron) self.overlap_features = nested_specialize( ol.overlap, [ NEURITE_SPECIALIZATIONS, #.remove(AllNeuriteSpec), NEURITE_COMPARISON_SPECIALIZATIONS ] #.remove(AllNeuriteCompareSpec)] )