コード例 #1
0
def test_cable_length():
  skel = PrecomputedSkeleton([ 
      (0,0,0), (1,0,0), (2,0,0), (3,0,0), (4,0,0), (5,0,0)
    ], 
    edges=[ (1,0), (1,2), (2,3), (3,4), (5,4) ],
    radii=[ 1, 2, 3, 4, 5, 6 ],
    vertex_types=[1, 2, 3, 4, 5, 6]
  )

  assert skel.cable_length() == (skel.vertices.shape[0] - 1)

  skel = PrecomputedSkeleton([ 
      (2,0,0), (1,0,0), (0,0,0), (0,5,0), (0,6,0), (0,7,0)
    ], 
    edges=[ (1,0), (1,2), (2,3), (3,4), (5,4) ],
    radii=[ 1, 2, 3, 4, 5, 6 ],
    vertex_types=[1, 2, 3, 4, 5, 6]
  )
  assert skel.cable_length() == 9

  skel = PrecomputedSkeleton([ 
      (1,1,1), (0,0,0), (1,0,0)
    ], 
    edges=[ (1,0), (1,2) ],
    radii=[ 1, 2, 3],
    vertex_types=[1, 2, 3]
  )
  assert abs(skel.cable_length() - (math.sqrt(3) + 1)) < 1e-6
コード例 #2
0
def test_components():
    skel = PrecomputedSkeleton(
        [
            (0, 0, 0),
            (1, 0, 0),
            (2, 0, 0),
            (0, 1, 0),
            (0, 2, 0),
            (0, 3, 0),
        ],
        edges=[(0, 1), (1, 2), (3, 4), (4, 5), (3, 5)],
        segid=666,
    )

    components = skel.components()
    assert len(components) == 2
    assert components[0].vertices.shape[0] == 3
    assert components[1].vertices.shape[0] == 3
    assert components[0].edges.shape[0] == 2
    assert components[1].edges.shape[0] == 3

    skel1_gt = PrecomputedSkeleton([(0, 0, 0), (1, 0, 0), (2, 0, 0)], [(0, 1),
                                                                       (1, 2)])
    skel2_gt = PrecomputedSkeleton([(0, 1, 0), (0, 2, 0), (0, 3, 0)], [(0, 1),
                                                                       (0, 2),
                                                                       (1, 2)])

    assert PrecomputedSkeleton.equivalent(components[0], skel1_gt)
    assert PrecomputedSkeleton.equivalent(components[1], skel2_gt)
コード例 #3
0
def test_consolidate():
    skel = PrecomputedSkeleton(
        vertices=np.array([
            (0, 0, 0),
            (1, 0, 0),
            (2, 0, 0),
            (0, 0, 0),
            (2, 1, 0),
            (2, 2, 0),
            (2, 2, 1),
            (2, 2, 2),
        ],
                          dtype=np.float32),
        edges=np.array([
            [0, 1],
            [1, 2],
            [2, 3],
            [3, 4],
            [4, 5],
            [5, 6],
            [6, 7],
        ],
                       dtype=np.uint32),
        radii=np.array([0, 1, 2, 3, 4, 5, 6, 7], dtype=np.float32),
        vertex_types=np.array([0, 1, 2, 3, 4, 5, 6, 7], dtype=np.uint8),
    )

    correct_skel = PrecomputedSkeleton(
        vertices=np.array([
            (0, 0, 0),
            (1, 0, 0),
            (2, 0, 0),
            (2, 1, 0),
            (2, 2, 0),
            (2, 2, 1),
            (2, 2, 2),
        ],
                          dtype=np.float32),
        edges=np.array([
            [0, 1],
            [0, 2],
            [0, 3],
            [1, 2],
            [3, 4],
            [4, 5],
            [5, 6],
        ],
                       dtype=np.uint32),
        radii=np.array([0, 1, 2, 4, 5, 6, 7], dtype=np.float32),
        vertex_types=np.array([0, 1, 2, 4, 5, 6, 7], dtype=np.uint8),
    )

    consolidated = skel.consolidate()

    assert np.all(consolidated.vertices == correct_skel.vertices)
    assert np.all(consolidated.edges == correct_skel.edges)
    assert np.all(consolidated.radii == correct_skel.radii)
    assert np.all(consolidated.vertex_types == correct_skel.vertex_types)
コード例 #4
0
def test_downsample():
  skel = PrecomputedSkeleton([ 
      (0,0,0), (1,0,0), (1,1,0), (1,1,3), (2,1,3), (2,2,3)
    ], 
    edges=[ (1,0), (1,2), (2,3), (3,4), (5,4) ],
    radii=[ 1, 2, 3, 4, 5, 6 ],
    vertex_types=[1, 2, 3, 4, 5, 6],
    segid=1337,
  )

  def should_error(x):
    try:
      skel.downsample(x)
      assert False
    except ValueError:
      pass

  should_error(-1)
  should_error(0)
  should_error(.5)
  should_error(2.00000000000001)

  dskel = skel.downsample(1)
  assert PrecomputedSkeleton.equivalent(dskel, skel)
  assert dskel.id == skel.id
  assert dskel.id == 1337

  dskel = skel.downsample(2)
  dskel_gt = PrecomputedSkeleton(
    [ (0,0,0), (1,1,0), (2,1,3), (2,2,3) ], 
    edges=[ (1,0), (1,2), (2,3) ],
    radii=[1,3,5,6], vertex_types=[1,3,5,6] 
  )
  assert PrecomputedSkeleton.equivalent(dskel, dskel_gt)

  dskel = skel.downsample(3)
  dskel_gt = PrecomputedSkeleton(
    [ (0,0,0), (1,1,3), (2,2,3) ], edges=[ (1,0), (1,2) ],
    radii=[1,4,6], vertex_types=[1,4,6],
  )
  assert PrecomputedSkeleton.equivalent(dskel, dskel_gt)

  skel = PrecomputedSkeleton([ 
      (0,0,0), (1,0,0), (1,1,0), (1,1,3), (2,1,3), (2,2,3)
    ], 
    edges=[ (1,0), (1,2), (3,4), (5,4) ],
    radii=[ 1, 2, 3, 4, 5, 6 ],
    vertex_types=[1, 2, 3, 4, 5, 6]
  )
  dskel = skel.downsample(2)
  dskel_gt = PrecomputedSkeleton(
    [ (0,0,0), (1,1,0), (1,1,3), (2,2,3) ], 
    edges=[ (1,0), (2,3) ],
    radii=[1,3,4,6], vertex_types=[1,3,4,6] 
  )
  assert PrecomputedSkeleton.equivalent(dskel, dskel_gt)
コード例 #5
0
def test_downsample_joints():
  skel = PrecomputedSkeleton([ 
      
                        (2, 3,0), # 0
                        (2, 2,0), # 1
                        (2, 1,0), # 2
      (0,0,0), (1,0,0), (2, 0,0), (3,0,0), (4,0,0), # 3, 4, 5, 6, 7
                        (2,-1,0), # 8
                        (2,-2,0), # 9
                        (2,-3,0), # 10

    ], 
    edges=[ 
                  (0, 1),
                  (1, 2),
                  (2, 5),
        (3,4), (4,5), (5, 6), (6,7),
                  (5, 8),
                  (8, 9),
                  (9,10)
    ],
    radii=[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ],
    vertex_types=[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ],
    segid=1337,
  )

  ds_skel = skel.downsample(2)
  ds_skel_gt = PrecomputedSkeleton([ 

                        (2, 3,0), # 0
                        
                        (2, 2,0), # 1
      (0,0,0),          (2, 0,0),     (4,0,0), # 2, 3, 4

                        (2,-2,0), # 5                        
                        (2,-3,0), # 6

    ], 
    edges=[ 
                  (0,1),
                  (1,3),
              (2,3),  (3,4), 
                  (3,5),
                  (5,6)
    ],
    radii=[ 0, 1, 3, 5, 7, 9, 10 ],
    vertex_types=[ 0, 1, 3, 5, 7, 9, 10 ],
    segid=1337,
  )

  assert PrecomputedSkeleton.equivalent(ds_skel, ds_skel_gt)
コード例 #6
0
  def fuse_skeletons(self, skels):
    if len(skels) == 0:
      return PrecomputedSkeleton()

    bbxs = [ item[0] for item in skels ]
    skeletons = [ item[1] for item in skels ]

    skeletons = self.crop_skels(bbxs, skeletons)
    skeletons = [ s for s in skeletons if not s.empty() ]

    if len(skeletons) == 0:
      return PrecomputedSkeleton()

    return PrecomputedSkeleton.simple_merge(skeletons).consolidate()
コード例 #7
0
def test_caching():
    vol = CloudVolume('file:///tmp/cloudvolume/test-skeletons',
                      info=info,
                      cache=True)

    vol.cache.flush()

    skel = PrecomputedSkeleton(
        [
            (0, 0, 0),
            (1, 0, 0),
            (2, 0, 0),
            (0, 1, 0),
            (0, 2, 0),
            (0, 3, 0),
        ],
        edges=[(0, 1), (1, 2), (3, 4), (4, 5), (3, 5)],
        segid=666,
    )

    vol.skeleton.upload(skel)

    assert vol.cache.list_skeletons() == ['666.gz']

    skel.id = 1
    with open(os.path.join(vol.cache.path, 'skeletons/1'), 'wb') as f:
        f.write(skel.encode())

    cached_skel = vol.skeleton.get(1)

    assert cached_skel == skel

    vol.cache.flush()
コード例 #8
0
def test_read_swc():

  # From http://research.mssm.edu/cnic/swc.html
  test_file = """# ORIGINAL_SOURCE NeuronStudio 0.8.80
# CREATURE
# REGION
# FIELD/LAYER
# TYPE
# CONTRIBUTOR
# REFERENCE
# RAW
# EXTRAS
# SOMA_AREA
# SHINKAGE_CORRECTION 1.0 1.0 1.0
# VERSION_NUMBER 1.0
# VERSION_DATE 2007-07-24
# SCALE 1.0 1.0 1.0
1 1 14.566132 34.873772 7.857000 0.717830 -1
2 0 16.022520 33.760513 7.047000 0.463378 1
3 5 17.542000 32.604973 6.885001 0.638007 2
4 0 19.163984 32.022469 5.913000 0.602284 3
5 0 20.448090 30.822802 4.860000 0.436025 4
6 6 21.897903 28.881084 3.402000 0.471886 5
7 0 18.461960 30.289471 8.586000 0.447463 3
8 6 19.420759 28.730757 9.558000 0.496217 7"""

  skel = PrecomputedSkeleton.from_swc(test_file)
  assert skel.vertices.shape[0] == 8
  assert skel.edges.shape[0] == 7

  skel_gt = PrecomputedSkeleton(
    vertices=[
      [14.566132, 34.873772, 7.857000],
      [16.022520, 33.760513, 7.047000],
      [17.542000, 32.604973, 6.885001],
      [19.163984, 32.022469, 5.913000],
      [20.448090, 30.822802, 4.860000],
      [21.897903, 28.881084, 3.402000],
      [18.461960, 30.289471, 8.586000],
      [19.420759, 28.730757, 9.558000]
    ],
    edges=[ (0,1), (1,2), (2,3), (3,4), (4,5), (2,6), (7,6) ],
    radii=[ 
      0.717830, 0.463378, 0.638007, 0.602284, 
      0.436025, 0.471886, 0.447463, 0.496217
    ],
    vertex_types=[
      1, 0, 5, 0, 0, 6, 0, 6
    ],
  )

  assert PrecomputedSkeleton.equivalent(skel, skel_gt)
コード例 #9
0
def test_remove_disconnected_vertices():
  skel = PrecomputedSkeleton(
    [ 
      (0,0,0), (1,0,0), (2,0,0),
      (0,1,0), (0,2,0), (0,3,0),
      (-1, -1, -1)
    ], 
    edges=[ 
      (0,1), (1,2), 
      (3,4), (4,5), (3,5)
    ],
    segid=666,
  )

  res = skel.remove_disconnected_vertices()
  assert res.vertices.shape[0] == 6
  assert res.edges.shape[0] == 5 
  assert res.radii.shape[0] == 6
  assert res.vertex_types.shape[0] == 6
  assert res.id == 666
コード例 #10
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,
    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
コード例 #11
0
def test_equivalent():
    assert PrecomputedSkeleton.equivalent(PrecomputedSkeleton(),
                                          PrecomputedSkeleton())

    identity = PrecomputedSkeleton([(0, 0, 0), (1, 0, 0)], [(0, 1)])
    assert PrecomputedSkeleton.equivalent(identity, identity)

    diffvertex = PrecomputedSkeleton([(0, 0, 0), (0, 1, 0)], [(0, 1)])
    assert not PrecomputedSkeleton.equivalent(identity, diffvertex)

    single1 = PrecomputedSkeleton([(0, 0, 0), (1, 0, 0)], edges=[(1, 0)])
    single2 = PrecomputedSkeleton([(0, 0, 0), (1, 0, 0)], edges=[(0, 1)])
    assert PrecomputedSkeleton.equivalent(single1, single2)

    double1 = PrecomputedSkeleton([(0, 0, 0), (1, 0, 0)], edges=[(1, 0)])
    double2 = PrecomputedSkeleton([(0, 0, 0), (1, 0, 0)], edges=[(0, 1)])
    assert PrecomputedSkeleton.equivalent(double1, double2)

    double1 = PrecomputedSkeleton([(0, 0, 0), (1, 0, 0), (1, 1, 0)],
                                  edges=[(1, 0), (1, 2)])
    double2 = PrecomputedSkeleton([(0, 0, 0), (1, 0, 0), (1, 1, 0)],
                                  edges=[(2, 1), (0, 1)])
    assert PrecomputedSkeleton.equivalent(double1, double2)

    double1 = PrecomputedSkeleton([(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 3)],
                                  edges=[(1, 0), (1, 2), (1, 3)])
    double2 = PrecomputedSkeleton([(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 3)],
                                  edges=[(3, 1), (2, 1), (0, 1)])
    assert PrecomputedSkeleton.equivalent(double1, double2)
コード例 #12
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