def test_dijkstra_parental(): for dtype in TEST_TYPES: values = np.ones((10, 10, 1), dtype=dtype) parents = dijkstra3d.parental_field(values, (0, 0, 0)) path = dijkstra3d.path_from_parents(parents, (3, 0, 0)) assert len(path) == 4 assert np.all(path == np.array([ [0, 0, 0], [1, 1, 0], [2, 1, 0], [3, 0, 0], ])) # Symmetric Test for _ in range(50): values = np.random.randint(1, 255, size=(10, 10, 10)) start = np.random.randint(0, 9, size=(3, )) target = np.random.randint(0, 9, size=(3, )) parents = dijkstra3d.parental_field(values, start) path = dijkstra3d.path_from_parents(parents, target) path_orig = dijkstra3d.dijkstra(values, start, target) assert np.all(path == path_orig) # Asymmetric Test for _ in range(50): values = np.random.randint(1, 255, size=(11, 10, 10)) start = np.random.randint(0, 9, size=(3, )) target = np.random.randint(0, 9, size=(3, )) parents = dijkstra3d.parental_field(values, start) path = dijkstra3d.path_from_parents(parents, target) path_orig = dijkstra3d.dijkstra(values, start, target) print(start, target) print(path) print(path_orig) assert np.all(path == path_orig)
def trace( labels, DBF, scale=10, const=10, anisotropy=(1, 1, 1), soma_detection_threshold=1100, soma_acceptance_threshold=4000, pdrf_scale=5000, pdrf_exponent=16, soma_invalidation_scale=0.5, soma_invalidation_const=0, fix_branching=True, manual_targets_before=[], manual_targets_after=[], root=None, max_paths=None, voxel_graph=None, ): """ Given the euclidean distance transform of a label ("Distance to Boundary Function"), convert it into a skeleton using an algorithm based on TEASAR. DBF: Result of the euclidean distance transform. Must represent a single label, assumed to be expressed in chosen physical units (i.e. nm) scale: during the "rolling ball" invalidation phase, multiply the DBF value by this. const: during the "rolling ball" invalidation phase, this is the minimum radius in chosen physical units (i.e. nm). anisotropy: (x,y,z) conversion factor for voxels to chosen physical units (i.e. nm) soma_detection_threshold: if object has a DBF value larger than this, root will be placed at largest DBF value and special one time invalidation will be run over that root location (see soma_invalidation scale) expressed in chosen physical units (i.e. nm) pdrf_scale: scale factor in front of dbf, used to weight dbf over euclidean distance (higher to pay more attention to dbf) (default 5000) pdrf_exponent: exponent in dbf formula on distance from edge, faster if factor of 2 (default 16) soma_invalidation_scale: the 'scale' factor used in the one time soma root invalidation (default .5) soma_invalidation_const: the 'const' factor used in the one time soma root invalidation (default 0) (units in chosen physical units (i.e. nm)) fix_branching: When enabled, zero out the graph edge weights traversed by of previously found paths. This causes branch points to occur closer to the actual path divergence. However, there is a large performance penalty associated with this as dijkstra's algorithm is computed once per a path rather than once per a skeleton. manual_targets_before: list of (x,y,z) that correspond to locations that must have paths drawn to. Used for specifying root and border targets for merging adjacent chunks out-of-core. Targets are applied before ordinary target selection. manual_targets_after: Same as manual_targets_before but the additional targets are applied after the usual algorithm runs. The current invalidation status of the shape makes no difference. max_paths: If a label requires drawing this number of paths or more, abort and move onto the next label. root: If you want to force the root to be a particular voxel, you can specify it here. voxel_graph: a connection graph that defines permissible directions of motion between voxels. This is useful for dealing with self-touches. The graph is defined by the conventions used in cc3d.voxel_connectivity_graph (https://github.com/seung-lab/connected-components-3d/blob/3.2.0/cc3d_graphs.hpp#L73-L92) Based on the algorithm by: M. Sato, I. Bitter, M. Bender, A. Kaufman, and M. Nakajima. "TEASAR: tree-structure extraction algorithm for accurate and robust skeletons" Proc. the Eighth Pacific Conference on Computer Graphics and Applications. Oct. 2000. doi:10.1109/PCCGA.2000.883951 (https://ieeexplore.ieee.org/document/883951/) Returns: Skeleton object """ dbf_max = np.max(DBF) labels = np.asfortranarray(labels) DBF = np.asfortranarray(DBF) soma_mode = False # > 5000 nm, gonna be a soma or blood vessel # For somata: specially handle the root by # placing it at the approximate center of the soma if dbf_max > soma_detection_threshold: labels, num_voxels_filled = fill_voids.fill(labels, in_place=True, return_fill_count=True) if num_voxels_filled > 0: del DBF DBF = edt.edt(labels, anisotropy=anisotropy, order='F', black_border=np.all(labels)) dbf_max = np.max(DBF) soma_mode = dbf_max > soma_acceptance_threshold soma_radius = 0.0 if soma_mode: if root is not None: manual_targets_before.insert(0, root) root = find_soma_root(DBF, dbf_max) soma_radius = dbf_max * soma_invalidation_scale + soma_invalidation_const elif root is None: root = find_root(labels, anisotropy) if root is None: return PrecomputedSkeleton() free_space_radius = 0 if not soma_mode else DBF[root] # DBF: Distance to Boundary Field # DAF: Distance from any voxel Field (distance from root field) # PDRF: Penalized Distance from Root Field DBF = kimimaro.skeletontricks.zero2inf(DBF) # DBF[ DBF == 0 ] = np.inf DAF, target = dijkstra3d.euclidean_distance_field( labels, root, anisotropy=anisotropy, free_space_radius=free_space_radius, voxel_graph=voxel_graph, return_max_location=True, ) DAF = kimimaro.skeletontricks.inf2zero(DAF) # DAF[ DAF == np.inf ] = 0 PDRF = compute_pdrf(dbf_max, pdrf_scale, pdrf_exponent, DBF, DAF) # Use dijkstra propogation w/o a target to generate a field of # pointers from each voxel to its parent. Then we can rapidly # compute multiple paths by simply hopping pointers using path_from_parents if not fix_branching: parents = dijkstra3d.parental_field(PDRF, root, voxel_graph) del PDRF else: parents = PDRF if soma_mode: invalidated, labels = kimimaro.skeletontricks.roll_invalidation_ball( labels, DBF, np.array([root], dtype=np.uint32), scale=soma_invalidation_scale, const=soma_invalidation_const, anisotropy=anisotropy) # This target is only valid if no # invalidations have occured yet. elif len(manual_targets_before) == 0: manual_targets_before.append(target) # delete reference to DAF and place it in # a list where we can delete it later and # free that memory. DAF = [DAF] paths = compute_paths(root, labels, DBF, DAF, parents, scale, const, anisotropy, soma_mode, soma_radius, fix_branching, manual_targets_before, manual_targets_after, max_paths, voxel_graph) skel = PrecomputedSkeleton.simple_merge([ PrecomputedSkeleton.from_path(path) for path in paths if len(path) > 0 ]).consolidate() verts = skel.vertices.flatten().astype(np.uint32) skel.radii = DBF[verts[::3], verts[1::3], verts[2::3]] return skel
def trace( labels, DBF, scale=10, const=10, anisotropy=(1, 1, 1), soma_detection_threshold=1100, soma_acceptance_threshold=4000, pdrf_scale=5000, pdrf_exponent=16, soma_invalidation_scale=0.5, soma_invalidation_const=0, fix_branching=True, ): """ Given the euclidean distance transform of a label ("Distance to Boundary Function"), convert it into a skeleton using an algorithm based on TEASAR. DBF: Result of the euclidean distance transform. Must represent a single label, assumed to be expressed in chosen physical units (i.e. nm) scale: during the "rolling ball" invalidation phase, multiply the DBF value by this. const: during the "rolling ball" invalidation phase, this is the minimum radius in chosen physical units (i.e. nm). anisotropy: (x,y,z) conversion factor for voxels to chosen physical units (i.e. nm) soma_detection_threshold: if object has a DBF value larger than this, root will be placed at largest DBF value and special one time invalidation will be run over that root location (see soma_invalidation scale) expressed in chosen physical units (i.e. nm) pdrf_scale: scale factor in front of dbf, used to weight dbf over euclidean distance (higher to pay more attention to dbf) (default 5000) pdrf_exponent: exponent in dbf formula on distance from edge, faster if factor of 2 (default 16) soma_invalidation_scale: the 'scale' factor used in the one time soma root invalidation (default .5) soma_invalidation_const: the 'const' factor used in the one time soma root invalidation (default 0) (units in chosen physical units (i.e. nm)) fix_branching: When enabled, zero out the graph edge weights traversed by of previously found paths. This causes branch points to occur closer to the actual path divergence. However, there is a large performance penalty associated with this as dijkstra's algorithm is computed once per a path rather than once per a skeleton. Based on the algorithm by: M. Sato, I. Bitter, M. Bender, A. Kaufman, and M. Nakajima. "TEASAR: tree-structure extraction algorithm for accurate and robust skeletons" Proc. the Eighth Pacific Conference on Computer Graphics and Applications. Oct. 2000. doi:10.1109/PCCGA.2000.883951 (https://ieeexplore.ieee.org/document/883951/) Returns: Skeleton object """ dbf_max = np.max(DBF) labels = np.asfortranarray(labels) DBF = np.asfortranarray(DBF) soma_mode = False # > 5000 nm, gonna be a soma or blood vessel # For somata: specially handle the root by # placing it at the approximate center of the soma if dbf_max > soma_detection_threshold: del DBF labels = ndimage.binary_fill_holes(labels) labels = np.asfortranarray(labels) DBF = edt.edt(labels, anisotropy=anisotropy, order='F') dbf_max = np.max(DBF) soma_mode = dbf_max > soma_acceptance_threshold if soma_mode: root = np.unravel_index(np.argmax(DBF), DBF.shape) soma_radius = dbf_max * soma_invalidation_scale + soma_invalidation_const else: root = find_root(labels, anisotropy) soma_radius = 0.0 if root is None: return PrecomputedSkeleton() # DBF: Distance to Boundary Field # DAF: Distance from any voxel Field (distance from root field) # PDRF: Penalized Distance from Root Field DBF = kimimaro.skeletontricks.zero2inf(DBF) # DBF[ DBF == 0 ] = np.inf DAF = dijkstra3d.euclidean_distance_field(labels, root, anisotropy=anisotropy) DAF = kimimaro.skeletontricks.inf2zero(DAF) # DAF[ DAF == np.inf ] = 0 PDRF = compute_pdrf(dbf_max, pdrf_scale, pdrf_exponent, DBF, DAF) # Use dijkstra propogation w/o a target to generate a field of # pointers from each voxel to its parent. Then we can rapidly # compute multiple paths by simply hopping pointers using path_from_parents if not fix_branching: parents = dijkstra3d.parental_field(PDRF, root) del PDRF else: parents = PDRF if soma_mode: invalidated, labels = kimimaro.skeletontricks.roll_invalidation_ball( labels, DBF, np.array([root], dtype=np.uint32), scale=soma_invalidation_scale, const=soma_invalidation_const, anisotropy=anisotropy) paths = compute_paths(root, labels, DBF, DAF, parents, scale, const, anisotropy, soma_mode, soma_radius, fix_branching) skel = PrecomputedSkeleton.simple_merge( [PrecomputedSkeleton.from_path(path) for path in paths]).consolidate() verts = skel.vertices.flatten().astype(np.uint32) skel.radii = DBF[verts[::3], verts[1::3], verts[2::3]] return skel
def test_dijkstra_parental(dtype, compass): values = np.ones((10, 10, 1), dtype=dtype, order='F') parents = dijkstra3d.parental_field(values, (0, 0, 0)) path = dijkstra3d.path_from_parents(parents, (3, 0, 0)) assert len(path) == 4 assert np.all(path == np.array([ [0, 0, 0], [1, 1, 0], [2, 1, 0], [3, 0, 0], ])) def path_len(path, values): length = 0 for p in path: length += values[tuple(p)] return length # Symmetric Test for _ in range(500): values = np.random.randint(1, 10, size=(10, 10, 1)) values = np.asfortranarray(values) start = np.random.randint(0, 9, size=(3, )) target = np.random.randint(0, 9, size=(3, )) start[2] = 0 target[2] = 0 parents = dijkstra3d.parental_field(values, start) path = dijkstra3d.path_from_parents(parents, target) path_orig = dijkstra3d.dijkstra(values, start, target, compass=compass) if path_len(path, values) != path_len(path_orig, values): print(start, target) print(path) print(path_orig) print(values[:, :, 0]) print('parents_path') for p in path: print(values[tuple(p)]) print('compass_path') for p in path_orig: print(values[tuple(p)]) assert path_len(path, values) == path_len(path_orig, values) if compass == False: assert np.all(path == path_orig) # Asymmetric Test for _ in range(500): values = np.random.randint(1, 255, size=(11, 10, 10)) values = np.asfortranarray(values) start = np.random.randint(0, 9, size=(3, )) target = np.random.randint(0, 9, size=(3, )) start[0] = np.random.randint(0, 10) target[0] = np.random.randint(0, 10) parents = dijkstra3d.parental_field(values, start) path = dijkstra3d.path_from_parents(parents, target) path_orig = dijkstra3d.dijkstra(values, start, target, compass=compass) if path_len(path, values) != path_len(path_orig, values): print(start, target) print(path) print(path_orig) assert path_len(path, values) == path_len(path_orig, values) if compass == False: assert np.all(path == path_orig)