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
示例#2
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