medial op• Data kinds: voxel → table
• Call: import fullseye as fs; fs.ledger.skeleton_graph3d(vol, distance=None, spacing=(1.0, 1.0, 1.0), min_branch_len=0.0) (implementation को सीधे call करना हो तो import medial; medial.skeleton_graph3d(vol, distance=None, spacing=(1.0, 1.0, 1.0), min_branch_len=0.0), registry से लेना हो तो ops3d.get("skeleton_graph3d"))
3D skeleton को nodes (junctions, end points) और branches (लंबाई, radius) के graph में जोड़ता है।
skeleton_junctions3d / skeleton_endpoints3d / skeleton_branches3d केवल "कौन-सा voxel node है / branch है" को mask के रूप में लौटाते हैं; कौन-सी branch किस node को किस node से जोड़ती है यह नहीं लौटाते। circuit बनाने के लिए यही connection चाहिए — neuron morphology की cable theory में compartment की axial conductance diameter और length से तय होती है, और compartments के आपस में जुड़ने का तरीका ही circuit है। यह op उसी 1 कड़ी को भरता है।
Arguments:
vol: 3-D skeleton (bool या {0, 1}; skeletonize_vol का output)। अगर content
blob हो (6-neighbourhood पूरी तरह foreground वाला interior voxel मौजूद हो), तो family के बाकी op की
तरह अंदर पहले skeletonize_vol लगाया जाता है।
distance: वैकल्पिक। समान shape का distance transform volume (vol_distance_transform का
output)। देने पर हर node और हर branch को radius मिलता है। unit दिए गए distance field के अनुसार होती है
— physical unit चाहिए तो vol_distance_transform(mask, spacing) दें।
spacing: (sz, sy, sx)। branch की length real distance में मापी जाती है (EM के anisotropic voxel
default मान्यता हैं)। VolumeMeta भी स्वीकार करता है।
min_branch_len: इससे छोटी terminal branch (spur) को काटता है (default 0 = नहीं काटता)।
unit spacing की real distance है। काटी जाने वाली branch वह है जिसका "एक सिरा endpoint (degree 1) हो और दूसरा
सिरा degree 2 या अधिक" — दोनों सिरे endpoint वाली branch नहीं काटी जाती। वह
अपने आप में 1 पूरा connected component (छोटा isolated tube) है, इसलिए काटने पर पूरी संरचना ही
गायब हो जाएगी। काटी गई संख्या n_pruned_branches में लौटती है।
Returns: dict (ledger में घोषित out type = table)।
• nodes: node table। id / z,y,x (voxel index में centroid; real coordinate के लिए spacing से गुणा करें) / kind / degree / n_voxels / radius (distance देने पर उस node के voxels पर maximum = inscribed radius) / component।
• edges: branch table। u / v (node id की जोड़ी) / length (skeleton के साथ real distance, spacing सहित) / radius_mean / radius_min / n_points (path पर voxel की संख्या, दोनों सिरों के node voxel सहित) / component।
• n_nodes / n_edges / n_components / n_cycles / n_pruned_branches / n_skeleton_voxels / spacing / has_radius।
Conventions (यहीं topology तय होती है):
• 26-neighbourhood degree 2 वाला voxel branch का बीच का हिस्सा है और node नहीं बनता। degree 1 या कम endpoint है (isolated voxel सहित), 3 या अधिक junction है।
• junction voxel ज़रूरी नहीं कि 1 ही हो (discrete skeleton में branching कई voxel का समूह बन जाती है)। 26-connectivity से समूह बनाकर उसे 1 node गिना जाता है, और coordinate उसका centroid होता है।
• कई connected component हों तो उन्हें चुपचाप जोड़ा नहीं जाता। n_components में संख्या लौटती है, और हर node और हर branch को component दिया जाता है।
• केवल closed loop वाले component (ऐसा ring जिसमें node बनने वाला एक भी (1) voxel न हो) में, उस component के पहले voxel पर केवल 1 seed node खड़ा करके उसे 1 self-loop branch बनाया जाता है। ऐसा करने से Euler का संबंध n_cycles = n_edges - n_nodes + n_components ring पर भी सही रहता है (tree मानकर नहीं चला जाता)।
• kind prune के बाद की degree से तय होता है: 0 = isolated / 1 = endpoint / 2 = chain (ring का seed node, या prune के परिणाम से ऐसा बना node) / 3 या अधिक = junction। prune करने से junction degree 2 पर आ सकता है, और वह node बचा रहता है (compartment की सीमा के रूप में यह सही है, पर ध्यान दें कि ऊपर का नियम "degree 2 node नहीं बनता" prune से पहले की बात है)।
• जब junction के समूह सीधे सटे हों (बीच में degree 2 का रास्ता न हो), तब उस जोड़ी के लिए 1 branch बनाई जाती है (length = सटे हुए voxel जोड़ों में सबसे छोटी)।
Validation (सब ValueError के साथ fail-closed): 3-D नहीं / खाली array / NaN, Inf / ऐसे मान जो न bool हों न {0,1} (बीच के "शायद foreground" मानों को चुपचाप round नहीं किया जाता) / foreground शून्य / distance की shape अलग, negative या non-finite / spacing 3 positive finite मान नहीं / min_branch_len negative। thinning की ज़रूरत वाले input पर scikit-image न हो तो ImportError।
सावधानी (honest, सब मापे हुए):
• length 26-neighbourhood polyline का योग है, इसलिए मुड़ी हुई branch continuous curve से लंबी आती है। radius 14 voxel की बंद tube (सच्ची परिधि 87.96) पर 94.7 = +7.7 %।
• मोटे input के सिरे सिकुड़ते हैं। free end की thinning end cap से पहले रुक जाती है। length 35 के सीधे cylinder पर मापने से radius 1 और 2 पर 35.00 (सिकुड़न शून्य), radius 3 पर 33, radius 4 पर 31 — radius 3 या अधिक होने पर हर सिरा 1 से 2 voxel भीतर आ जाता है। length को ground truth से मिलाना हो तो thinning से न गुज़रने वाला 1 voxel चौड़ा skeleton दें (तब बिल्कुल मेल खाता है: 31 voxel की सीधी रेखा पर 30.0)।
• branching पर, junction के समूह (degree 3 या अधिक, 26-connectivity से समूहित) में निगले गए हिस्से जितनी branch छोटी हो जाती है। junction से छोटे spur branch नहीं बनते, वे node के n_voxels में शामिल होकर गायब हो जाते हैं (वे min_branch_len से काटे जाने के उम्मीदवार भी नहीं बनते)।
• radius distance का मान ही है। endpoint पर यह end cap तक की दूरी से तय होता है, इसलिए tube के radius से छोटा आता है (radius 3 के cylinder पर endpoint 2.0, branch का mean 3.07)। branch की मोटाई देखनी हो तो radius_mean / radius_min का उपयोग करें।
• 90 degree rotation: 1 voxel चौड़ा skeleton देने पर graph isomorphic होते हैं और length भी बिल्कुल मेल खाती है (9 तरह से मापा गया)। मोटा blob देने पर node और branch की संख्या मेल खाती है, पर length अधिकतम 1.41 (= 1 diagonal कदम) तक अलग होती है — अंतर इस op से नहीं, बल्कि thinning helper (skimage Lee) के rotation पर बिल्कुल एक जैसा skeleton न बनाने के कारण है।
• Sample data catalog (DL URL / license) — 2-D के लिए skimage.data (BSD/public) + synthetic, 3-D के लिए real data sources (Stanford/PDS आदि) के DL URL।
• Operators का स्रोत और संदर्भ — इस op family के मूल research/methods के स्रोत।
• medial_topology — py -3.11 examples_3d/medial_topology.py
table को input के रूप में लेते हैं)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 note tools/opdocs.py md से अपने-आप generate होता है (हाथ से edit न करें)।*
© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.