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를 붙인다.

• 닫힌 루프만 있는 성분(노드가 될 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}도 아닌 값(중간값인 "아마 전경"을 몰래 반올림하지 않음) / 전경이 0 / distance의 형상이 다름, 음수, 비유한 / spacing이 3개의 양의유한값이 아님 / min_branch_len이 음수. 세선화가 필요한 입력에서 scikit-image가없으면 ImportError.

주의(honest, 모두 실측):

• 길이는 26 근방 꺾은선의 합이므로, 굽은 가지는 연속 곡선보다 길게 나온다. 반지름 14 voxel의 닫힌 관(참 둘레 87.96)에서 94.7 = +7.7 %.

• 굵은 입력은 끝이 줄어든다. 자유단의 세선화는 끝 뚜껑 앞에서 멈춘다. 길이 35의직원기둥에서 실측하면 반지름 1·2는 35.00(줄어듦 0), 반지름 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)가 회전에서 엄밀히 같은 골격을 만들지 않기 때문.

참고(샘플 데이터·문헌)

• 샘플 데이터 카탈로그(DL URL / 라이선스) —— 2-D 는 skimage.data(BSD/public)+ 합성, 3-D 는 실데이터 소스(Stanford/PDS 등)의 DL 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 연산자 레지스트리. 이 op 노트는 tools/opdocs.py md 가 자동 생성합니다(직접 편집하지 마세요).*

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