medial opvoxel → tableimport 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 を付ける。
* 閉ループだけの成分(ノードになる voxel が 1 つも無い輪)は、その成分の
先頭 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、いずれも実測):
n_voxels
に含まれて消える(min_branch_len で刈る対象にすらならない)。distance の値そのもの。端点は端の蓋までの距離で決まるので
管の半径より小さく出る(半径 3 の円柱で端点 2.0、枝の平均 3.07)。
枝の太さを見るなら radius_mean / radius_min を使う。py -3.11 examples_3d/medial_topology.pytable を入力に取れる)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 operator registry. この per-op ノートは tools/opdocs.py md が自動生成(手編集しない)。
© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.