├── .gitignore ├── FillEulerLUT.m ├── README.md ├── Skeleton3D.m ├── Test_Skeleton3D.m ├── license.txt ├── p_EulerInv.m ├── p_is_simple.m ├── p_oct_label.m ├── pk_get_nh.m ├── pk_get_nh_idx.m ├── readme.txt └── testvol.mat /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.m~ 3 | -------------------------------------------------------------------------------- /FillEulerLUT.m: -------------------------------------------------------------------------------- 1 | function LUT = FillEulerLUT 2 | 3 | LUT = zeros(255,1, 'int8'); 4 | 5 | LUT(1) = 1; 6 | LUT(3) = -1; 7 | LUT(5) = -1; 8 | LUT(7) = 1; 9 | LUT(9) = -3; 10 | LUT(11) = -1; 11 | LUT(13) = -1; 12 | LUT(15) = 1; 13 | LUT(17) = -1; 14 | LUT(19) = 1; 15 | LUT(21) = 1; 16 | LUT(23) = -1; 17 | LUT(25) = 3; 18 | LUT(27) = 1; 19 | LUT(29) = 1; 20 | LUT(31) = -1; 21 | LUT(33) = -3; 22 | LUT(35) = -1; 23 | LUT(37) = 3; 24 | LUT(39) = 1; 25 | LUT(41) = 1; 26 | LUT(43) = -1; 27 | LUT(45) = 3; 28 | LUT(47) = 1; 29 | LUT(49) = -1; 30 | LUT(51) = 1; 31 | 32 | LUT(53) = 1; 33 | LUT(55) = -1; 34 | LUT(57) = 3; 35 | LUT(59) = 1; 36 | LUT(61) = 1; 37 | LUT(63) = -1; 38 | LUT(65) = -3; 39 | LUT(67) = 3; 40 | LUT(69) = -1; 41 | LUT(71) = 1; 42 | LUT(73) = 1; 43 | LUT(75) = 3; 44 | LUT(77) = -1; 45 | LUT(79) = 1; 46 | LUT(81) = -1; 47 | LUT(83) = 1; 48 | LUT(85) = 1; 49 | LUT(87) = -1; 50 | LUT(89) = 3; 51 | LUT(91) = 1; 52 | LUT(93) = 1; 53 | LUT(95) = -1; 54 | LUT(97) = 1; 55 | LUT(99) = 3; 56 | LUT(101) = 3; 57 | LUT(103) = 1; 58 | 59 | LUT(105) = 5; 60 | LUT(107) = 3; 61 | LUT(109) = 3; 62 | LUT(111) = 1; 63 | LUT(113) = -1; 64 | LUT(115) = 1; 65 | LUT(117) = 1; 66 | LUT(119) = -1; 67 | LUT(121) = 3; 68 | LUT(123) = 1; 69 | LUT(125) = 1; 70 | LUT(127) = -1; 71 | LUT(129) = -7; 72 | LUT(131) = -1; 73 | LUT(133) = -1; 74 | LUT(135) = 1; 75 | LUT(137) = -3; 76 | LUT(139) = -1; 77 | LUT(141) = -1; 78 | LUT(143) = 1; 79 | LUT(145) = -1; 80 | LUT(147) = 1; 81 | LUT(149) = 1; 82 | LUT(151) = -1; 83 | LUT(153) = 3; 84 | LUT(155) = 1; 85 | 86 | LUT(157) = 1; 87 | LUT(159) = -1; 88 | LUT(161) = -3; 89 | LUT(163) = -1; 90 | LUT(165) = 3; 91 | LUT(167) = 1; 92 | LUT(169) = 1; 93 | LUT(171) = -1; 94 | LUT(173) = 3; 95 | LUT(175) = 1; 96 | LUT(177) = -1; 97 | LUT(179) = 1; 98 | LUT(181) = 1; 99 | LUT(183) = -1; 100 | LUT(185) = 3; 101 | LUT(187) = 1; 102 | LUT(189) = 1; 103 | LUT(191) = -1; 104 | LUT(193) = -3; 105 | LUT(195) = 3; 106 | LUT(197) = -1; 107 | LUT(199) = 1; 108 | LUT(201) = 1; 109 | LUT(203) = 3; 110 | LUT(205) = -1; 111 | LUT(207) = 1; 112 | 113 | LUT(209) = -1; 114 | LUT(211) = 1; 115 | LUT(213) = 1; 116 | LUT(215) = -1; 117 | LUT(217) = 3; 118 | LUT(219) = 1; 119 | LUT(221) = 1; 120 | LUT(223) = -1; 121 | LUT(225) = 1; 122 | LUT(227) = 3; 123 | LUT(229) = 3; 124 | LUT(231) = 1; 125 | LUT(233) = 5; 126 | LUT(235) = 3; 127 | LUT(237) = 3; 128 | LUT(239) = 1; 129 | LUT(241) = -1; 130 | LUT(243) = 1; 131 | LUT(245) = 1; 132 | LUT(247) = -1; 133 | LUT(249) = 3; 134 | LUT(251) = 1; 135 | LUT(253) = 1; 136 | LUT(255) = -1; 137 | end 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # skeleton3D-matlab: Parallel medial axis thinning of a 3D binary volume 2 | 3 | This code calculates the 3D medial axis skeleton of an arbitrary 3d binary volume. It is an optimized MATLAB implementation of the homotopic thinning algorithm described in [1]. We developed it to quantify the network of cell processes in bone [2], but it should work on images of any tubular or filamentous structures. An example volume (testvol.mat) is included, along with an example script (Test_Skeleton3D.m). Any comments, corrections or suggestions are highly welcome. 4 | 5 | ## Usage: 6 | 7 | `skel = Skeleton3D(bin)` 8 | 9 | where "bin" is a 3D binary image, and "skel" the resulting image containing only the skeleton voxels, or 10 | 11 | `skel = Skeleton3D(bin,mask)` 12 | 13 | to mask all foreground voxels in 'mask' from skeletonization, e.g. to preserve certain structures in a image volume. 14 | 15 | For additional cleanup, e.g. pruning of short branches, please use my Skel2Graph3D package on MATLAB File Exchange. 16 | 17 | This code is inspired by the ITK implementation by Hanno Homann [3] and the Fiji/ImageJ plugin by Ignacio Arganda-Carreras [4]. If you include this in your own work, please cite our publicaton [2]. 18 | 19 | Philip Kollmannsberger 09/2013 20 | philipk@gmx.net 21 | 22 | References: 23 | 24 | [1] Ta-Chih Lee, Rangasami L. Kashyap and Chong-Nam Chu 25 | "Building skeleton models via 3-D medial surface/axis thinning algorithms." 26 | Computer Vision, Graphics, and Image Processing, 56(6):462–478, 1994. 27 | 28 | [2] Kollmannsberger, Kerschnitzki et al., 29 | "The small world of osteocytes: connectomics of the lacuno-canalicular network in bone." 30 | New Journal of Physics 19:073019, 2017. 31 | 32 | [3] http://hdl.handle.net/1926/1292 33 | 34 | [4] http://fiji.sc/wiki/index.php/Skeletonize3D 35 | -------------------------------------------------------------------------------- /Skeleton3D.m: -------------------------------------------------------------------------------- 1 | function skel = Skeleton3D(skel,spare) 2 | % SKELETON3D Calculate the 3D skeleton of an arbitrary binary volume using parallel medial axis thinning. 3 | % 4 | % skel = SKELETON3D(img) returns the skeleton of the binary volume 'img' 5 | % skel = SKELETON3D(img,mask) preserves foreground voxels in 'mask' 6 | % 7 | % MATLAB vectorized implementation of the algorithm by Lee, Kashyap and Chu 8 | % "Building skeleton models via 3-D medial surface/axis thinning algorithms." 9 | % Computer Vision, Graphics, and Image Processing, 56(6):462�478, 1994. 10 | % 11 | % Inspired by the ITK implementation by Hanno Homann 12 | % http://hdl.handle.net/1926/1292 13 | % and the Fiji/ImageJ plugin by Ignacio Arganda-Carreras 14 | % http://fiji.sc/wiki/index.php/Skeletonize3D 15 | % 16 | % Philip Kollmannsberger (philipk@gmx.net) 17 | % 18 | % For more information, see Skeleton3D at the MATLAB File Exchange. 20 | 21 | % pad volume with zeros to avoid edge effects 22 | skel=padarray(skel,[1 1 1]); 23 | 24 | if(nargin==2) 25 | spare=padarray(spare,[1 1 1]); 26 | end 27 | 28 | % fill lookup table 29 | eulerLUT = FillEulerLUT; 30 | 31 | width = size(skel,1); 32 | height = size(skel,2); 33 | depth = size(skel,3); 34 | 35 | unchangedBorders = 0; 36 | 37 | while( unchangedBorders < 6 ) % loop until no change for all six border types 38 | unchangedBorders = 0; 39 | for currentBorder=1:6 % loop over all 6 directions 40 | cands=false(width,height,depth, 'like', skel); 41 | switch currentBorder 42 | case 4 43 | x=2:size(skel,1); % identify border voxels as candidates 44 | cands(x,:,:)=skel(x,:,:) - skel(x-1,:,:); 45 | case 3 46 | x=1:size(skel,1)-1; 47 | cands(x,:,:)=skel(x,:,:) - skel(x+1,:,:); 48 | case 1 49 | y=2:size(skel,2); 50 | cands(:,y,:)=skel(:,y,:) - skel(:,y-1,:); 51 | case 2 52 | y=1:size(skel,2)-1; 53 | cands(:,y,:)=skel(:,y,:) - skel(:,y+1,:); 54 | case 6 55 | z=2:size(skel,3); 56 | cands(:,:,z)=skel(:,:,z) - skel(:,:,z-1); 57 | case 5 58 | z=1:size(skel,3)-1; 59 | cands(:,:,z)=skel(:,:,z) - skel(:,:,z+1); 60 | end 61 | 62 | % if excluded voxels were passed, remove them from candidates 63 | if(nargin==2) 64 | cands = cands & ~spare; 65 | end 66 | 67 | % make sure all candidates are indeed foreground voxels 68 | cands = cands(:)==1 & skel(:)==1; 69 | 70 | noChange = true; 71 | 72 | if any(cands) 73 | cands = find(cands); 74 | % get subscript indices of candidates 75 | [x,y,z]=ind2sub([width height depth],cands); 76 | 77 | % get 26-neighbourhood of candidates in volume 78 | nhood = pk_get_nh(skel,cands); 79 | 80 | % remove all endpoints (exactly one nb) from list 81 | di1 = sum(nhood,2)==2; 82 | nhood(di1,:)=[]; 83 | cands(di1)=[]; 84 | x(di1)=[]; 85 | y(di1)=[]; 86 | z(di1)=[]; 87 | 88 | % remove all non-Euler-invariant points from list 89 | di2 = ~p_EulerInv(nhood, eulerLUT); 90 | nhood(di2,:)=[]; 91 | cands(di2)=[]; 92 | x(di2)=[]; 93 | y(di2)=[]; 94 | z(di2)=[]; 95 | 96 | % remove all non-simple points from list 97 | di3 = ~p_is_simple(nhood); 98 | % nhood(di3,:)=[]; 99 | % cands(di3)=[]; 100 | x(di3)=[]; 101 | y(di3)=[]; 102 | z(di3)=[]; 103 | 104 | 105 | % if any candidates left: divide into 8 independent subvolumes 106 | if (~isempty(x)) 107 | x1 = logical(mod(x,2)); 108 | x2 = ~x1; 109 | y1 = logical(mod(y,2)); 110 | y2 = ~y1; 111 | z1 = logical(mod(z,2)); 112 | z2 = ~z1; 113 | ilst(1).l = x1 & y1 & z1; 114 | ilst(2).l = x2 & y1 & z1; 115 | ilst(3).l = x1 & y2 & z1; 116 | ilst(4).l = x2 & y2 & z1; 117 | ilst(5).l = x1 & y1 & z2; 118 | ilst(6).l = x2 & y1 & z2; 119 | ilst(7).l = x1 & y2 & z2; 120 | ilst(8).l = x2 & y2 & z2; 121 | 122 | % idx = []; 123 | 124 | % do parallel re-checking for all points in each subvolume 125 | % With euler criteria and simple point --> keep topology 126 | for i = 1:8 127 | if any(ilst(i).l) 128 | idx = ilst(i).l; 129 | li = sub2ind([width height depth],x(idx),y(idx),z(idx)); 130 | 131 | skel(li)=0; % remove points 132 | nh = pk_get_nh(skel,li); 133 | di_rc_euler = ~p_EulerInv(nh, eulerLUT); 134 | di_rc = ~p_is_simple(nh); 135 | 136 | % Check for the simple plint criteria 137 | no_single_change = true ; 138 | if any(di_rc) % if topology changed: revert 139 | skel(li(di_rc)) = true; 140 | no_single_change = false; 141 | end 142 | 143 | % Check for the euler criteria 144 | if any(di_rc_euler) 145 | skel(li(di_rc_euler)) = true; 146 | no_single_change = false; 147 | end 148 | 149 | if no_single_change 150 | noChange = false; % at least one voxel removed 151 | end 152 | end 153 | end 154 | end 155 | end 156 | 157 | if( noChange ) 158 | unchangedBorders = unchangedBorders + 1; 159 | end 160 | 161 | end 162 | end 163 | 164 | % get rid of padded zeros 165 | skel = skel(2:end-1,2:end-1,2:end-1); 166 | end 167 | 168 | 169 | -------------------------------------------------------------------------------- /Test_Skeleton3D.m: -------------------------------------------------------------------------------- 1 | clear all; 2 | close all; 3 | 4 | load testvol 5 | 6 | skel = Skeleton3D(testvol); 7 | 8 | figure(); 9 | col=[.7 .7 .8]; 10 | hiso = patch(isosurface(testvol,0),'FaceColor',col,'EdgeColor','none'); 11 | hiso2 = patch(isocaps(testvol,0),'FaceColor',col,'EdgeColor','none'); 12 | axis equal;axis off; 13 | lighting phong; 14 | isonormals(testvol,hiso); 15 | alpha(0.5); 16 | set(gca,'DataAspectRatio',[1 1 1]) 17 | camlight; 18 | hold on; 19 | w=size(skel,1); 20 | l=size(skel,2); 21 | h=size(skel,3); 22 | [x,y,z]=ind2sub([w,l,h],find(skel(:))); 23 | plot3(y,x,z,'square','Markersize',4,'MarkerFaceColor','r','Color','r'); 24 | set(gcf,'Color','white'); 25 | view(140,80) 26 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Philip Kollmannsberger 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the distribution 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /p_EulerInv.m: -------------------------------------------------------------------------------- 1 | function EulerInv = p_EulerInv(img,LUT) 2 | if numel(LUT) > 255 3 | error('skeleton3D:p_EulerInv:LUTwithTooManyElems', 'LUT with 255 elements expected'); 4 | end 5 | % Calculate Euler characteristic for each octant and sum up 6 | eulerChar = zeros(size(img,1),1, 'like', LUT); 7 | % Octant SWU 8 | bitorTable = uint8([128; 64; 32; 16; 8; 4; 2]); 9 | n = ones(size(img,1),1, 'uint8'); 10 | n(img(:,25)==1) = bitor(n(img(:,25)==1), bitorTable(1)); 11 | n(img(:,26)==1) = bitor(n(img(:,26)==1), bitorTable(2)); 12 | n(img(:,16)==1) = bitor(n(img(:,16)==1), bitorTable(3)); 13 | n(img(:,17)==1) = bitor(n(img(:,17)==1), bitorTable(4)); 14 | n(img(:,22)==1) = bitor(n(img(:,22)==1), bitorTable(5)); 15 | n(img(:,23)==1) = bitor(n(img(:,23)==1), bitorTable(6)); 16 | n(img(:,13)==1) = bitor(n(img(:,13)==1), bitorTable(7)); 17 | eulerChar = eulerChar + LUT(n); 18 | % Octant SEU 19 | n = ones(size(img,1),1, 'uint8'); 20 | n(img(:,27)==1) = bitor(n(img(:,27)==1), bitorTable(1)); 21 | n(img(:,24)==1) = bitor(n(img(:,24)==1), bitorTable(2)); 22 | n(img(:,18)==1) = bitor(n(img(:,18)==1), bitorTable(3)); 23 | n(img(:,15)==1) = bitor(n(img(:,15)==1), bitorTable(4)); 24 | n(img(:,26)==1) = bitor(n(img(:,26)==1), bitorTable(5)); 25 | n(img(:,23)==1) = bitor(n(img(:,23)==1), bitorTable(6)); 26 | n(img(:,17)==1) = bitor(n(img(:,17)==1), bitorTable(7)); 27 | eulerChar = eulerChar + LUT(n); 28 | % Octant NWU 29 | n = ones(size(img,1),1, 'uint8'); 30 | n(img(:,19)==1) = bitor(n(img(:,19)==1), bitorTable(1)); 31 | n(img(:,22)==1) = bitor(n(img(:,22)==1), bitorTable(2)); 32 | n(img(:,10)==1) = bitor(n(img(:,10)==1), bitorTable(3)); 33 | n(img(:,13)==1) = bitor(n(img(:,13)==1), bitorTable(4)); 34 | n(img(:,20)==1) = bitor(n(img(:,20)==1), bitorTable(5)); 35 | n(img(:,23)==1) = bitor(n(img(:,23)==1), bitorTable(6)); 36 | n(img(:,11)==1) = bitor(n(img(:,11)==1), bitorTable(7)); 37 | eulerChar = eulerChar + LUT(n); 38 | % Octant NEU 39 | n = ones(size(img,1),1, 'uint8'); 40 | n(img(:,21)==1) = bitor(n(img(:,21)==1), bitorTable(1)); 41 | n(img(:,24)==1) = bitor(n(img(:,24)==1), bitorTable(2)); 42 | n(img(:,20)==1) = bitor(n(img(:,20)==1), bitorTable(3)); 43 | n(img(:,23)==1) = bitor(n(img(:,23)==1), bitorTable(4)); 44 | n(img(:,12)==1) = bitor(n(img(:,12)==1), bitorTable(5)); 45 | n(img(:,15)==1) = bitor(n(img(:,15)==1), bitorTable(6)); 46 | n(img(:,11)==1) = bitor(n(img(:,11)==1), bitorTable(7)); 47 | eulerChar = eulerChar + LUT(n); 48 | % Octant SWB 49 | n = ones(size(img,1),1, 'uint8'); 50 | n(img(:, 7)==1) = bitor(n(img(:, 7)==1), bitorTable(1)); 51 | n(img(:,16)==1) = bitor(n(img(:,16)==1), bitorTable(2)); 52 | n(img(:, 8)==1) = bitor(n(img(:, 8)==1), bitorTable(3)); 53 | n(img(:,17)==1) = bitor(n(img(:,17)==1), bitorTable(4)); 54 | n(img(:, 4)==1) = bitor(n(img(:, 4)==1), bitorTable(5)); 55 | n(img(:,13)==1) = bitor(n(img(:,13)==1), bitorTable(6)); 56 | n(img(:, 5)==1) = bitor(n(img(:, 5)==1), bitorTable(7)); 57 | eulerChar = eulerChar + LUT(n); 58 | % Octant SEB 59 | n = ones(size(img,1),1, 'uint8'); 60 | n(img(:, 9)==1) = bitor(n(img(:, 9)==1), bitorTable(1)); 61 | n(img(:, 8)==1) = bitor(n(img(:, 8)==1), bitorTable(2)); 62 | n(img(:,18)==1) = bitor(n(img(:,18)==1), bitorTable(3)); 63 | n(img(:,17)==1) = bitor(n(img(:,17)==1), bitorTable(4)); 64 | n(img(:, 6)==1) = bitor(n(img(:, 6)==1), bitorTable(5)); 65 | n(img(:, 5)==1) = bitor(n(img(:, 5)==1), bitorTable(6)); 66 | n(img(:,15)==1) = bitor(n(img(:,15)==1), bitorTable(7)); 67 | eulerChar = eulerChar + LUT(n); 68 | % Octant NWB 69 | n = ones(size(img,1),1, 'uint8'); 70 | n(img(:, 1)==1) = bitor(n(img(:, 1)==1), bitorTable(1)); 71 | n(img(:,10)==1) = bitor(n(img(:,10)==1), bitorTable(2)); 72 | n(img(:, 4)==1) = bitor(n(img(:, 4)==1), bitorTable(3)); 73 | n(img(:,13)==1) = bitor(n(img(:,13)==1), bitorTable(4)); 74 | n(img(:, 2)==1) = bitor(n(img(:, 2)==1), bitorTable(5)); 75 | n(img(:,11)==1) = bitor(n(img(:,11)==1), bitorTable(6)); 76 | n(img(:, 5)==1) = bitor(n(img(:, 5)==1), bitorTable(7)); 77 | eulerChar = eulerChar + LUT(n); 78 | % Octant NEB 79 | n = ones(size(img,1),1, 'uint8'); 80 | n(img(:, 3)==1) = bitor(n(img(:, 3)==1), bitorTable(1)); 81 | n(img(:, 2)==1) = bitor(n(img(:, 2)==1), bitorTable(2)); 82 | n(img(:,12)==1) = bitor(n(img(:,12)==1), bitorTable(3)); 83 | n(img(:,11)==1) = bitor(n(img(:,11)==1), bitorTable(4)); 84 | n(img(:, 6)==1) = bitor(n(img(:, 6)==1), bitorTable(5)); 85 | n(img(:, 5)==1) = bitor(n(img(:, 5)==1), bitorTable(6)); 86 | n(img(:,15)==1) = bitor(n(img(:,15)==1), bitorTable(7)); 87 | eulerChar = eulerChar + LUT(n); 88 | 89 | EulerInv = false(size(eulerChar), 'like', img); 90 | EulerInv(eulerChar==0) = true; 91 | 92 | end 93 | -------------------------------------------------------------------------------- /p_is_simple.m: -------------------------------------------------------------------------------- 1 | function is_simple = p_is_simple(N) 2 | 3 | % copy neighbors for labeling 4 | n_p = size(N,1); 5 | is_simple = true(n_p, 1, 'like', N); 6 | 7 | cube = zeros(n_p, 26, 'uint8'); 8 | cube(:, 1:13)=N(:, 1:13); 9 | cube(:, 14:26)=N(:,15:27); 10 | 11 | label = 2*ones(n_p, 1, 'uint8'); 12 | 13 | % for all points in the neighborhood 14 | for i=1:26 15 | 16 | idx = cube(:,i) == 1 & is_simple; 17 | 18 | if any(idx) 19 | 20 | % start recursion with any octant that contains the point i 21 | switch( i ) 22 | 23 | case {1,2,4,5,10,11,13} 24 | cube(idx,:) = p_oct_label(1, label, cube(idx,:) ); 25 | case {3,6,12,14} 26 | cube(idx,:) = p_oct_label(2, label, cube(idx,:) ); 27 | case {7,8,15,16} 28 | cube(idx,:) = p_oct_label(3, label, cube(idx,:) ); 29 | case {9,17} 30 | cube(idx,:) = p_oct_label(4, label, cube(idx,:) ); 31 | case {18,19,21,22} 32 | cube(idx,:) = p_oct_label(5, label, cube(idx,:) ); 33 | case {20,23} 34 | cube(idx,:) = p_oct_label(6, label, cube(idx,:) ); 35 | case {24,25} 36 | cube(idx,:) = p_oct_label(7, label, cube(idx,:) ); 37 | case 26 38 | cube(idx,:) = p_oct_label(8, label, cube(idx,:) ); 39 | end 40 | 41 | label(idx) = label(idx)+1; 42 | del_idx = label>=4; 43 | 44 | if any(del_idx) 45 | is_simple(del_idx) = false; 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /p_oct_label.m: -------------------------------------------------------------------------------- 1 | function cube = p_oct_label(octant, label, cube) 2 | 3 | % check if there are points in the octant with value 1 4 | if( octant==1 ) 5 | 6 | % set points in this octant to current label 7 | % and recurseive labeling of adjacent octants 8 | idx = cube(:,1) == 1; 9 | if any(idx) 10 | cube(idx,1) = label(idx); 11 | end 12 | 13 | idx = cube(:,2) == 1; 14 | if any(idx) 15 | cube(idx,2) = label(idx); 16 | cube(idx,:) = p_oct_label(2,label(idx),cube(idx,:)); 17 | end 18 | 19 | idx = cube(:,4) == 1; 20 | if any(idx) 21 | cube(idx,4) = label(idx); 22 | cube(idx,:) = p_oct_label(3,label(idx),cube(idx,:)); 23 | end 24 | 25 | idx = cube(:,5) == 1; 26 | if any(idx) 27 | cube(idx,5) = label(idx); 28 | cube(idx,:) = p_oct_label(2,label(idx),cube(idx,:)); 29 | cube(idx,:) = p_oct_label(3,label(idx),cube(idx,:)); 30 | cube(idx,:) = p_oct_label(4,label(idx),cube(idx,:)); 31 | end 32 | 33 | idx = cube(:,10) == 1; 34 | if any(idx) 35 | cube(idx,10) = label(idx); 36 | cube(idx,:) = p_oct_label(5,label(idx),cube(idx,:)); 37 | end 38 | 39 | idx = cube(:,11) == 1; 40 | if any(idx) 41 | cube(idx,11) = label(idx); 42 | cube(idx,:) = p_oct_label(2,label(idx),cube(idx,:)); 43 | cube(idx,:) = p_oct_label(5,label(idx),cube(idx,:)); 44 | cube(idx,:) = p_oct_label(6,label(idx),cube(idx,:)); 45 | end 46 | 47 | idx = cube(:,13) == 1; 48 | if any(idx) 49 | cube(idx,13) = label(idx); 50 | cube(idx,:) = p_oct_label(3,label(idx),cube(idx,:)); 51 | cube(idx,:) = p_oct_label(5,label(idx),cube(idx,:)); 52 | cube(idx,:) = p_oct_label(7,label(idx),cube(idx,:)); 53 | end 54 | 55 | end 56 | 57 | if( octant==2 ) 58 | 59 | idx = cube(:,2) == 1; 60 | if any(idx) 61 | cube(idx,2) = label(idx); 62 | cube(idx,:) = p_oct_label(1,label(idx),cube(idx,:)); 63 | end 64 | 65 | idx = cube(:,5) == 1; 66 | if any(idx) 67 | cube(idx,5) = label(idx); 68 | cube(idx,:) = p_oct_label(1,label(idx),cube(idx,:)); 69 | cube(idx,:) = p_oct_label(3,label(idx),cube(idx,:)); 70 | cube(idx,:) = p_oct_label(4,label(idx),cube(idx,:)); 71 | end 72 | 73 | idx = cube(:,11) == 1; 74 | if any(idx) 75 | cube(idx,11) = label(idx); 76 | cube(idx,:) = p_oct_label(1,label(idx),cube(idx,:)); 77 | cube(idx,:) = p_oct_label(5,label(idx),cube(idx,:)); 78 | cube(idx,:) = p_oct_label(6,label(idx),cube(idx,:)); 79 | end 80 | 81 | idx = cube(:,3) == 1; 82 | if any(idx) 83 | cube(idx,3) = label(idx); 84 | end 85 | 86 | idx = cube(:,6) == 1; 87 | if any(idx) 88 | cube(idx,6) = label(idx); 89 | cube(idx,:) = p_oct_label(4,label(idx),cube(idx,:)); 90 | end 91 | 92 | idx = cube(:,12) == 1; 93 | if any(idx) 94 | cube(idx,12) = label(idx); 95 | cube(idx,:) = p_oct_label(6,label(idx),cube(idx,:)); 96 | end 97 | 98 | idx = cube(:,14) == 1; 99 | if any(idx) 100 | cube(idx,14) = label(idx); 101 | cube(idx,:) = p_oct_label(4,label(idx),cube(idx,:)); 102 | cube(idx,:) = p_oct_label(6,label(idx),cube(idx,:)); 103 | cube(idx,:) = p_oct_label(8,label(idx),cube(idx,:)); 104 | end 105 | 106 | end 107 | 108 | if( octant==3 ) 109 | 110 | idx = cube(:,4) == 1; 111 | if any(idx) 112 | cube(idx,4) = label(idx); 113 | cube(idx,:) = p_oct_label(1,label(idx),cube(idx,:)); 114 | end 115 | 116 | idx = cube(:,5) == 1; 117 | if any(idx) 118 | cube(idx,5) = label(idx); 119 | cube(idx,:) = p_oct_label(1,label(idx),cube(idx,:)); 120 | cube(idx,:) = p_oct_label(2,label(idx),cube(idx,:)); 121 | cube(idx,:) = p_oct_label(4,label(idx),cube(idx,:)); 122 | end 123 | 124 | idx = cube(:,13) == 1; 125 | if any(idx) 126 | cube(idx,13) = label(idx); 127 | cube(idx,:) = p_oct_label(1,label(idx),cube(idx,:)); 128 | cube(idx,:) = p_oct_label(5,label(idx),cube(idx,:)); 129 | cube(idx,:) = p_oct_label(7,label(idx),cube(idx,:)); 130 | end 131 | 132 | idx = cube(:,7) == 1; 133 | if any(idx) 134 | cube(idx,7) = label(idx); 135 | end 136 | 137 | idx = cube(:,8) == 1; 138 | if any(idx) 139 | cube(idx,8) = label(idx); 140 | cube(idx,:) = p_oct_label(4,label(idx),cube(idx,:)); 141 | end 142 | 143 | idx = cube(:,15) == 1; 144 | if any(idx) 145 | cube(idx,15) = label(idx); 146 | cube(idx,:) = p_oct_label(7,label(idx),cube(idx,:)); 147 | end 148 | 149 | idx = cube(:,16) == 1; 150 | if any(idx) 151 | cube(idx,16) = label(idx); 152 | cube(idx,:) = p_oct_label(4,label(idx),cube(idx,:)); 153 | cube(idx,:) = p_oct_label(7,label(idx),cube(idx,:)); 154 | cube(idx,:) = p_oct_label(8,label(idx),cube(idx,:)); 155 | end 156 | 157 | end 158 | 159 | if( octant==4 ) 160 | 161 | idx = cube(:,5) == 1; 162 | if any(idx) 163 | cube(idx,5) = label(idx); 164 | cube(idx,:) = p_oct_label(1,label(idx),cube(idx,:)); 165 | cube(idx,:) = p_oct_label(2,label(idx),cube(idx,:)); 166 | cube(idx,:) = p_oct_label(3,label(idx),cube(idx,:)); 167 | end 168 | 169 | idx = cube(:,6) == 1; 170 | if any(idx) 171 | cube(idx,6) = label(idx); 172 | cube(idx,:) = p_oct_label(2,label(idx),cube(idx,:)); 173 | end 174 | 175 | idx = cube(:,14) == 1; 176 | if any(idx) 177 | cube(idx,14) = label(idx); 178 | cube(idx,:) = p_oct_label(2,label(idx),cube(idx,:)); 179 | cube(idx,:) = p_oct_label(6,label(idx),cube(idx,:)); 180 | cube(idx,:) = p_oct_label(8,label(idx),cube(idx,:)); 181 | end 182 | 183 | idx = cube(:,8) == 1; 184 | if any(idx) 185 | cube(idx,8) = label(idx); 186 | cube(idx,:) = p_oct_label(3,label(idx),cube(idx,:)); 187 | end 188 | 189 | idx = cube(:,16) == 1; 190 | if any(idx) 191 | cube(idx,16) = label(idx); 192 | cube(idx,:) = p_oct_label(3,label(idx),cube(idx,:)); 193 | cube(idx,:) = p_oct_label(7,label(idx),cube(idx,:)); 194 | cube(idx,:) = p_oct_label(8,label(idx),cube(idx,:)); 195 | end 196 | 197 | idx = cube(:,9) == 1; 198 | if any(idx) 199 | cube(idx,9) = label(idx); 200 | end 201 | 202 | idx = cube(:,17) == 1; 203 | if any(idx) 204 | cube(idx,17) = label(idx); 205 | cube(idx,:) = p_oct_label(8,label(idx),cube(idx,:)); 206 | end 207 | 208 | end 209 | 210 | if( octant==5 ) 211 | 212 | idx = cube(:,10) == 1; 213 | if any(idx) 214 | cube(idx,10) = label(idx); 215 | cube(idx,:) = p_oct_label(1,label(idx),cube(idx,:)); 216 | end 217 | 218 | idx = cube(:,11) == 1; 219 | if any(idx) 220 | cube(idx,11) = label(idx); 221 | cube(idx,:) = p_oct_label(1,label(idx),cube(idx,:)); 222 | cube(idx,:) = p_oct_label(2,label(idx),cube(idx,:)); 223 | cube(idx,:) = p_oct_label(6,label(idx),cube(idx,:)); 224 | end 225 | 226 | idx = cube(:,13) == 1; 227 | if any(idx) 228 | cube(idx,13) = label(idx); 229 | cube(idx,:) = p_oct_label(1,label(idx),cube(idx,:)); 230 | cube(idx,:) = p_oct_label(3,label(idx),cube(idx,:)); 231 | cube(idx,:) = p_oct_label(7,label(idx),cube(idx,:)); 232 | end 233 | 234 | idx = cube(:,18) == 1; 235 | if any(idx) 236 | cube(idx,18) = label(idx); 237 | end 238 | 239 | idx = cube(:,19) == 1; 240 | if any(idx) 241 | cube(idx,19) = label(idx); 242 | cube(idx,:) = p_oct_label(6,label(idx),cube(idx,:)); 243 | end 244 | 245 | idx = cube(:,21) == 1; 246 | if any(idx) 247 | cube(idx,21) = label(idx); 248 | cube(idx,:) = p_oct_label(7,label(idx),cube(idx,:)); 249 | end 250 | 251 | idx = cube(:,22) == 1; 252 | if any(idx) 253 | cube(idx,22) = label(idx); 254 | cube(idx,:) = p_oct_label(6,label(idx),cube(idx,:)); 255 | cube(idx,:) = p_oct_label(7,label(idx),cube(idx,:)); 256 | cube(idx,:) = p_oct_label(8,label(idx),cube(idx,:)); 257 | end 258 | 259 | end 260 | 261 | if( octant==6 ) 262 | 263 | idx = cube(:,11) == 1; 264 | if any(idx) 265 | cube(idx,11) = label(idx); 266 | cube(idx,:) = p_oct_label(1,label(idx),cube(idx,:)); 267 | cube(idx,:) = p_oct_label(2,label(idx),cube(idx,:)); 268 | cube(idx,:) = p_oct_label(5,label(idx),cube(idx,:)); 269 | end 270 | 271 | idx = cube(:,12) == 1; 272 | if any(idx) 273 | cube(idx,12) = label(idx); 274 | cube(idx,:) = p_oct_label(2,label(idx),cube(idx,:)); 275 | end 276 | 277 | idx = cube(:,14) == 1; 278 | if any(idx) 279 | cube(idx,14) = label(idx); 280 | cube(idx,:) = p_oct_label(2,label(idx),cube(idx,:)); 281 | cube(idx,:) = p_oct_label(4,label(idx),cube(idx,:)); 282 | cube(idx,:) = p_oct_label(8,label(idx),cube(idx,:)); 283 | end 284 | 285 | idx = cube(:,19) == 1; 286 | if any(idx) 287 | cube(idx,19) = label(idx); 288 | cube(idx,:) = p_oct_label(5,label(idx),cube(idx,:)); 289 | end 290 | 291 | 292 | idx = cube(:,22) == 1; 293 | if any(idx) 294 | cube(idx,22) = label(idx); 295 | cube(idx,:) = p_oct_label(5,label(idx),cube(idx,:)); 296 | cube(idx,:) = p_oct_label(7,label(idx),cube(idx,:)); 297 | cube(idx,:) = p_oct_label(8,label(idx),cube(idx,:)); 298 | end 299 | 300 | idx = cube(:,20) == 1; 301 | if any(idx) 302 | cube(idx,20) = label(idx); 303 | end 304 | 305 | idx = cube(:,23) == 1; 306 | if any(idx) 307 | cube(idx,23) = label(idx); 308 | cube(idx,:) = p_oct_label(8,label(idx),cube(idx,:)); 309 | end 310 | 311 | end 312 | 313 | if( octant==7 ) 314 | 315 | idx = cube(:,13) == 1; 316 | if any(idx) 317 | cube(idx,13) = label(idx); 318 | cube(idx,:) = p_oct_label(1,label(idx),cube(idx,:)); 319 | cube(idx,:) = p_oct_label(3,label(idx),cube(idx,:)); 320 | cube(idx,:) = p_oct_label(5,label(idx),cube(idx,:)); 321 | end 322 | 323 | idx = cube(:,15) == 1; 324 | if any(idx) 325 | cube(idx,15) = label(idx); 326 | cube(idx,:) = p_oct_label(3,label(idx),cube(idx,:)); 327 | end 328 | 329 | idx = cube(:,16) == 1; 330 | if any(idx) 331 | cube(idx,16) = label(idx); 332 | cube(idx,:) = p_oct_label(3,label(idx),cube(idx,:)); 333 | cube(idx,:) = p_oct_label(4,label(idx),cube(idx,:)); 334 | cube(idx,:) = p_oct_label(8,label(idx),cube(idx,:)); 335 | end 336 | 337 | idx = cube(:,21) == 1; 338 | if any(idx) 339 | cube(idx,21) = label(idx); 340 | cube(idx,:) = p_oct_label(5,label(idx),cube(idx,:)); 341 | end 342 | 343 | idx = cube(:,22) == 1; 344 | if any(idx) 345 | cube(idx,22) = label(idx); 346 | cube(idx,:) = p_oct_label(5,label(idx),cube(idx,:)); 347 | cube(idx,:) = p_oct_label(6,label(idx),cube(idx,:)); 348 | cube(idx,:) = p_oct_label(8,label(idx),cube(idx,:)); 349 | end 350 | 351 | idx = cube(:,24) == 1; 352 | if any(idx) 353 | cube(idx,24) = label(idx); 354 | end 355 | 356 | idx = cube(:,25) == 1; 357 | if any(idx) 358 | cube(idx,25) = label(idx); 359 | cube(idx,:) = p_oct_label(8,label(idx),cube(idx,:)); 360 | end 361 | end 362 | 363 | if( octant==8 ) 364 | 365 | idx = cube(:,14) == 1; 366 | if any(idx) 367 | cube(idx,14) = label(idx); 368 | cube(idx,:) = p_oct_label(2,label(idx),cube(idx,:)); 369 | cube(idx,:) = p_oct_label(4,label(idx),cube(idx,:)); 370 | cube(idx,:) = p_oct_label(6,label(idx),cube(idx,:)); 371 | end 372 | 373 | idx = cube(:,16) == 1; 374 | if any(idx) 375 | cube(idx,16) = label(idx); 376 | cube(idx,:) = p_oct_label(3,label(idx),cube(idx,:)); 377 | cube(idx,:) = p_oct_label(4,label(idx),cube(idx,:)); 378 | cube(idx,:) = p_oct_label(7,label(idx),cube(idx,:)); 379 | end 380 | 381 | idx = cube(:,17) == 1; 382 | if any(idx) 383 | cube(idx,17) = label(idx); 384 | cube(idx,:) = p_oct_label(4,label(idx),cube(idx,:)); 385 | end 386 | 387 | idx = cube(:,22) == 1; 388 | if any(idx) 389 | cube(idx,22) = label(idx); 390 | cube(idx,:) = p_oct_label(5,label(idx),cube(idx,:)); 391 | cube(idx,:) = p_oct_label(6,label(idx),cube(idx,:)); 392 | cube(idx,:) = p_oct_label(7,label(idx),cube(idx,:)); 393 | end 394 | 395 | idx = cube(:,23) == 1; 396 | if any(idx) 397 | cube(idx,23) = label(idx); 398 | cube(idx,:) = p_oct_label(6,label(idx),cube(idx,:)); 399 | end 400 | 401 | idx = cube(:,25) == 1; 402 | if any(idx) 403 | cube(idx,25) = label(idx); 404 | cube(idx,:) = p_oct_label(7,label(idx),cube(idx,:)); 405 | end 406 | 407 | idx = cube(:,26) == 1; 408 | if any(idx) 409 | cube(idx,26) = label(idx); 410 | end 411 | end 412 | end 413 | -------------------------------------------------------------------------------- /pk_get_nh.m: -------------------------------------------------------------------------------- 1 | function nhood = pk_get_nh(img,i) 2 | 3 | width = size(img,1); 4 | height = size(img,2); 5 | depth = size(img,3); 6 | 7 | [x,y,z]=ind2sub([width height depth],i); 8 | 9 | nhood = false(length(i),27, 'like', img); 10 | 11 | for xx=1:3 12 | for yy=1:3 13 | for zz=1:3 14 | w=sub2ind([3 3 3],xx,yy,zz); 15 | idx = sub2ind([width height depth],x+xx-2,y+yy-2,z+zz-2); 16 | nhood(:,w)=img(idx); 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /pk_get_nh_idx.m: -------------------------------------------------------------------------------- 1 | function nhood = pk_get_nh_idx(img,i) 2 | 3 | width = size(img,1); 4 | height = size(img,2); 5 | depth = size(img,3); 6 | 7 | [x,y,z]=ind2sub([width height depth],i); 8 | 9 | nhood = zeros(length(i),27); 10 | 11 | for xx=1:3 12 | for yy=1:3 13 | for zz=1:3 14 | w=sub2ind([3 3 3],xx,yy,zz); 15 | nhood(:,w) = sub2ind([width height depth],x+xx-2,y+yy-2,z+zz-2); 16 | end; 17 | end; 18 | end; -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phi-max/skeleton3d-matlab/de4d1fb01b839cd4645b6583f535dd818fd23dd9/readme.txt -------------------------------------------------------------------------------- /testvol.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phi-max/skeleton3d-matlab/de4d1fb01b839cd4645b6583f535dd818fd23dd9/testvol.mat --------------------------------------------------------------------------------