Esempio n. 1
0
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
Esempio n. 3
0
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
Esempio n. 4
0
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)