skeleton_graph3d — 3D medial op

• 数据种类:voxel → table

• 调用: import fullseye as fs; fs.ledger.skeleton_graph3d(vol, distance=None, spacing=(1.0, 1.0, 1.0), min_branch_len=0.0)(要直接调用实现,import medial; medial.skeleton_graph3d(vol, distance=None, spacing=(1.0, 1.0, 1.0), min_branch_len=0.0);从台账取用则 ops3d.get("skeleton_graph3d"))

用法

把 3D 骨架组装成由节点(连接点、端点)和分支(长度、半径)构成的图。

skeleton_junctions3d / skeleton_endpoints3d / skeleton_branches3d 只以 掩模 返回"哪些 voxel 是节点/分支",并不返回 哪条分支连接哪个节点与哪个节点。要构成电路就需要这种连接 —— 在神经形态的电缆理论中,区室的轴向电导由 直径和长度 决定,区室之间的 连接方式 本身就是电路。本 op 填补这 1 个环节。

参数:

vol: 3-D 骨架(bool 或 {0, 1}。skeletonize_vol 的输出)。若内容

是块(存在 6 邻域全为前景的 interior voxel),则与同族的其他 op

一样,在内部先施加 skeletonize_vol。

distance: 可选。同形状的距离变换体(vol_distance_transform 的

输出)。传入后各节点、各分支会附带半径。单位遵循传入的距离场

—— 需要物理单位时传 vol_distance_transform(mask, spacing)。

spacing: (sz, sy, sx)。以 实际距离 测量分支长度(默认设想 EM 的各向异性

体素)。也接受 VolumeMeta。

min_branch_len: 剪掉短于该值的 末端分支(毛刺)(默认 0 = 不剪)。

单位是 spacing 下的实际距离。被剪的是"一端为端点(度 1)、另

一端度为 2 以上"的分支 —— 两端都是端点 的分支不剪。这种分支

本身就是 1 个连通分量(短的孤立管),剪掉会让整个结构

消失。剪掉的数量返回到 n_pruned_branches。

返回值: dict(台账声明的 out 类型 = table)。

• nodes: 节点表。id / z,y,x(以 voxel 下标表示的质心。实际坐标需乘以 spacing)/ kind / degree / n_voxels / radius (传入 distance 时,该节点各 voxel 上的最大值 = 内切半径)/ component。

• edges: 分支表。u / v(节点 id 对)/ length(沿骨架的实际距离,含 spacing)/ radius_mean / radius_min / n_points (路径上的 voxel 数,含两端节点 voxel)/ component。

• n_nodes / n_edges / n_components / n_cycles / n_pruned_branches / n_skeleton_voxels / spacing / has_radius。

约定(这里决定拓扑):

• 26 邻域度为 2 的 voxel 是分支的中途,不作为节点。度 1 以下 为端点(含孤立 voxel),3 以上 为接合。

• 接合 voxel 不一定只有 1 个(离散骨架中分支会成为数个 voxel 的块)。按 26 连通归并成块并计为 1 个节点,坐标为其质心。

• 有多个连通分量时不会擅自连接。 在 n_components 中返回个数, 并给各节点、各分支加上 component。

• 只有闭环的分量(没有 1 个会成为节点的 voxel 的环),只在该分量的首个 voxel 上立 1 个种子节点,形成 1 条自环分支。这样 欧拉关系 n_cycles = n_edges - n_nodes + n_components 对环也成立(不假定为树)。

• kind 由 剪枝后的 度决定: 0 = isolated / 1 = endpoint / 2 = chain(环的种子节点,或剪枝后变成这样的节点)/ 3 以上 = junction。剪枝可能使接合降为度 2,这种节点会保留(作为区室边界是正确的,但请注意上面"度 2 不作为节点" 的约定说的是 剪枝前 的情况)。

• 接合块之间直接相邻(中间没有度 2 的路径)时,为该对生成 1 条 分支(长度 = 相邻 voxel 对的最短值)。

校验(全部以 ValueError fail-closed): 不是 3-D/空数组/NaN、Inf/ 既不是 bool 也不是 {0,1} 的值(不会把中间值的"大概是前景"擅自取整)/ 前景为零/distance 形状不同、为负、非有限/spacing 不是 3 个正的有限值/min_branch_len 为负。需要细化的输入在没有 scikit-image 时为 ImportError。

注意(honest,均为实测):

• 长度是 26 邻域折线 之和,因此弯曲的分支会比连续曲线长。半径 14 voxel 的闭合管(真实周长 87.96)得到 94.7 = +7.7 %。

• 粗的输入端部会收缩。 自由端的细化会停在端盖之前。在长 35 的

直圆柱上实测: 半径 1、2 为 35.00(零收缩),半径 3 为 33,

半径 4 为 31 —— 半径达到 3 以上时每端向内偏移 1~2 voxel。

要与真值比较长度,请传入 不经过细化的 1 voxel 宽骨架

(此时严格一致: 31 voxel 的直线得到 30.0)。

• 在分支处,分支会缩短被接合块(度 3 以上按 26 连通归并而成)吞掉的部分。比接合短的毛刺不会成为分支,而是计入节点的 n_voxels 而消失(甚至不会成为 min_branch_len 的剪枝对象)。

• 半径就是 distance 的值本身。端点由到端盖的距离决定,因此 会小于管的半径(半径 3 的圆柱: 端点 2.0,分支平均 3.07)。看分支粗细时请用 radius_mean / radius_min。

• 90 度旋转: 输入 1 voxel 宽的骨架时,图同构且长度也严格一致(实测 9 种)。传入粗块时节点数、分支数一致,但长度最多相差 1.41(= 对角 1 步)—— 产生偏差的不是本 op,而是 细化辅助函数(skimage Lee)在旋转下并不严格生成相同的骨架。

参考(示例数据、文献)

• 示例数据目录(下载 URL / 许可证) —— 2-D 用 skimage.data(BSD/公有领域)加合成图,3-D 给出真实数据源(Stanford/PDS 等)的下载 URL。

• 算子来历与参考文献 —— 该算子族所依据的研究/方法出处。

可运行的示例(实际调用该算子并已验证的样例)

• medial_topology — py -3.11 examples_3d/medial_topology.py

类型可衔接的下一个算子(可接受 table 作为输入)

fuse_to_voxel · mesh_select_lod

同类别(medial)

distance_ridge · skeletonize_vol · medial_axis_points · topology_signature · medial_match · skeleton_junctions3d · skeleton_endpoints3d · skeleton_prune3d


*Provenance: medial.py — 3D 算子登记表。本条目由 tools/opdocs.py md 自动生成(请勿手工编辑)。*

© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.