{"id":7532,"date":"2016-07-01T09:09:39","date_gmt":"2016-07-01T13:09:39","guid":{"rendered":"https:\/\/blogs.mathworks.com\/pick\/?p=7532"},"modified":"2016-07-03T12:19:15","modified_gmt":"2016-07-03T16:19:15","slug":"skeletonizing-an-image-in-3d","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/pick\/2016\/07\/01\/skeletonizing-an-image-in-3d\/","title":{"rendered":"Skeletonizing an Image in 3D"},"content":{"rendered":"<div class=\"content\"><!--introduction--><!--\/introduction--><p><a href=\"https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/authors\/911\">Brett<\/a>'s Pick this week is <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/43400-skeleton3d\"><tt>Skeleton3D<\/tt><\/a>, by <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/profile\/authors\/4556277-philip-kollmannsberger\">Philip Kollmansberger<\/a>.<\/p><p>A couple years ago, I put together a demo to show how to use MATLAB to calculate the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Tortuosity\">tortuosity<\/a>, or &#8220;twistedness,&#8221; of blood vessels. There are different formulas for measuring that property, but perhaps the easiest way of determining tortuosity of a path is using the arc-chord ratio&#8212;i.e., the length of the curve divided by the distance between its endpoints.<\/p><p>In my demo, I assumed the vessels were 2-dimensional, and then used some <a href=\"https:\/\/www.mathworks.com\/products\/image\/?requestedDomain=www.mathworks.com\">Image Processing Toolbox<\/a> functionality to: 1) <a href=\"https:\/\/en.wikipedia.org\/wiki\/Topological_skeleton\">skeletonize<\/a> the vessels of interest using <a href=\"https:\/\/www.mathworks.com\/help\/images\/ref\/bwmorph.html\"><tt>bwmorph<\/tt>\/skel<\/a>, and 2) determine the endpoints of the segment (facilitated by a call to <a href=\"https:\/\/www.mathworks.com\/help\/images\/ref\/bwmorph.html\"><tt>bwmorph<\/tt>\/endpoints<\/a>. Then, using one or both endpoints of the segment, respectively, to 3) calculate the geodesic distance transform of the vessel (<a href=\"https:\/\/www.mathworks.com\/help\/images\/ref\/bwdistgeodesic.html\"><tt>bwdistgeodesic<\/tt><\/a>), the maximum value of which provides the arc-length; and 4) calculate the distance the endpoints. (You can find a discussion of this approach in a <a href=\"https:\/\/www.mathworks.com\/videos\/medical-image-processing-with-matlab-81890.html?s_tid=srchtitle\">recorded webinar<\/a> at mathworks.com.)<\/p><p>This approach is reasonably straightforward when you assume planarity, but in 3D, it gets much more challenging&#8212;because bwmorph doesn&#8217;t currently support the analysis of 3D images. That leaves us struggling to do the skeletonization- and endpoint-detection steps.<\/p><p>Enter Philip&#8217;s <tt>Skeleton3D<\/tt> function. Using an algorithm (properly cited in the code) based on &#8220;3-D medial surface\/axis thinning algorithms,&#8221; <tt>Skeleton3D<\/tt> finds axial values that can be subsequently analyzed for the determination of tortuosity.<\/p><pre class=\"language-matlab\">load <span class=\"string\">testvol<\/span>\r\ninds = find(testvol);\r\n[y,x,z] = ind2sub(size(testvol),inds);\r\ns = scatter3(x,y,z,100,<span class=\"keyword\">...<\/span>\r\n  <span class=\"string\">'Marker'<\/span>,<span class=\"string\">'o'<\/span>,<span class=\"keyword\">...<\/span>\r\n  <span class=\"string\">'MarkerEdgeColor'<\/span>,<span class=\"string\">'none'<\/span>,<span class=\"keyword\">...<\/span>\r\n  <span class=\"string\">'MarkerFaceAlpha'<\/span>,0.3,<span class=\"keyword\">...<\/span>\r\n  <span class=\"string\">'MarkerFaceColor'<\/span>,<span class=\"string\">'c'<\/span>);\r\nxlabel(<span class=\"string\">'x'<\/span>);\r\nylabel(<span class=\"string\">'y'<\/span>);\r\nzlabel(<span class=\"string\">'z'<\/span>);\r\ntitle(<span class=\"string\">'Test Volume'<\/span>)\r\n<\/pre><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/pick\/files\/testvol.png\" alt=\"\"> <\/p><p>Now we can extract and display points along the skeleton of this test volume:<\/p><pre class=\"language-matlab\">skel = Skeleton3D(testvol);\r\ninds = find(skel);\r\n[yskel,xskel,zskel] = ind2sub(size(skel),inds);\r\nhold <span class=\"string\">on<\/span>\r\np2 = scatter3(xskel,yskel,zskel,20,<span class=\"keyword\">...<\/span>\r\n    <span class=\"string\">'Marker'<\/span>,<span class=\"string\">'o'<\/span>,<span class=\"keyword\">...<\/span>\r\n    <span class=\"string\">'MarkerEdgeColor'<\/span>,<span class=\"string\">'none'<\/span>,<span class=\"keyword\">...<\/span>\r\n    <span class=\"string\">'MarkerFaceColor'<\/span>,<span class=\"string\">'r'<\/span>);\r\n<\/pre><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/pick\/files\/testvolWithSkeleton.png\" alt=\"\"> <\/p><p>For tortuosity determinations, there's still some more work to be done--we still need to detect endpoints and arclengths, for instance. Those are topics for a different day.<\/p><p>For Philip, an implementation comment: <tt>Skeleton3D<\/tt> is considerably slower than it needs to be. It relies on looping over indices where <a href=\"https:\/\/www.mathworks.com\/help\/matlab\/matlab_prog\/vectorization.html\">vectorized approaches<\/a> can be much faster. And converting to <a href=\"https:\/\/www.mathworks.com\/company\/newsletters\/articles\/matrix-indexing-in-matlab.html\">logical indexing<\/a> for comparisons can also speed things up. For instance:<\/p><p>In the determination of \"candidate foreground voxels,\" you can replace:<\/p><pre class=\"language-matlab\">cands = intersect(find(cands(:)==1),find(skel(:)==1));\r\n<\/pre><p>with<\/p><pre class=\"language-matlab\">cands = find(cands &amp; skel);\r\n<\/pre><p>(Admittedly, that buys just a little improvement in performance, but I find it more elegant.)<\/p><p>More pointedly, if you were to <a href=\"https:\/\/www.mathworks.com\/help\/matlab\/ref\/profile.html\">profile<\/a> your code, you'd find a large percentage of time spent calculating the skeleton of your testvol reflects nearly 87,000 calls to <tt>p_oct_label<\/tt>. That code could be greatly sped up by replacing successive indx(row) = <tt>find(cube(row,:)==1)<\/tt> calls in the analysis of 'cube' with something along the lines of<\/p><pre class=\"language-matlab\">[row,col] = ind2sub(size(cube),cube==1);\r\nindx(row) = col(row == 1);\r\n<\/pre><p>and iterating over values of <tt>row<\/tt>.<\/p><p>Nonetheless, this provides a good start to anyone needing to skeletonize structures in 3D. Thanks, Philip, for this very nice contribution!<\/p><p>As always, I welcome your <a href=\"https:\/\/blogs.mathworks.com\/pick\/?p=7532#respond\">thoughts and comments<\/a>.<\/p><script language=\"JavaScript\"> <!-- \r\n    function grabCode_5b2d57c7399a4344aaa519f3ff9a595e() {\r\n        \/\/ Remember the title so we can use it in the new page\r\n        title = document.title;\r\n\r\n        \/\/ Break up these strings so that their presence\r\n        \/\/ in the Javascript doesn't mess up the search for\r\n        \/\/ the MATLAB code.\r\n        t1='5b2d57c7399a4344aaa519f3ff9a595e ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' 5b2d57c7399a4344aaa519f3ff9a595e';\r\n    \r\n        b=document.getElementsByTagName('body')[0];\r\n        i1=b.innerHTML.indexOf(t1)+t1.length;\r\n        i2=b.innerHTML.indexOf(t2);\r\n \r\n        code_string = b.innerHTML.substring(i1, i2);\r\n        code_string = code_string.replace(\/REPLACE_WITH_DASH_DASH\/g,'--');\r\n\r\n        \/\/ Use \/x3C\/g instead of the less-than character to avoid errors \r\n        \/\/ in the XML parser.\r\n        \/\/ Use '\\x26#60;' instead of '<' so that the XML parser\r\n        \/\/ doesn't go ahead and substitute the less-than character. \r\n        code_string = code_string.replace(\/\\x3C\/g, '\\x26#60;');\r\n\r\n        copyright = 'Copyright 2016 The MathWorks, Inc.';\r\n\r\n        w = window.open();\r\n        d = w.document;\r\n        d.write('<pre>\\n');\r\n        d.write(code_string);\r\n\r\n        \/\/ Add copyright line at the bottom if specified.\r\n        if (copyright.length > 0) {\r\n            d.writeln('');\r\n            d.writeln('%%');\r\n            if (copyright.length > 0) {\r\n                d.writeln('% _' + copyright + '_');\r\n            }\r\n        }\r\n\r\n        d.write('<\/pre>\\n');\r\n\r\n        d.title = title + ' (MATLAB code)';\r\n        d.close();\r\n    }   \r\n     --> <\/script><p style=\"text-align: right; font-size: xx-small; font-weight:lighter;   font-style: italic; color: gray\"><br><a href=\"javascript:grabCode_5b2d57c7399a4344aaa519f3ff9a595e()\"><span style=\"font-size: x-small;        font-style: italic;\">Get \r\n      the MATLAB code <noscript>(requires JavaScript)<\/noscript><\/span><\/a><br><br>\r\n      Published with MATLAB&reg; R2016a<br><\/p><\/div><!--\r\n5b2d57c7399a4344aaa519f3ff9a595e ##### SOURCE BEGIN #####\r\n%% Skeletonizing an Image in 3D\r\n%% \r\n% <https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/authors\/911 Brett>'s Pick this week\r\n% is\r\n% <https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/43400-skeleton3d |Skeleton3D|>, \r\n% by <https:\/\/www.mathworks.com\/matlabcentral\/profile\/authors\/4556277-philip-kollmannsberger Philip Kollmansberger>.\r\n%\r\n% A couple years ago, I put together a demo to show how to use MATLAB to\r\n% calculate the <https:\/\/en.wikipedia.org\/wiki\/Tortuosity tortuosity>, or\r\n% \u00e2\u20ac\u0153twistedness,\u00e2\u20ac\ufffd of blood vessels. There are different formulas for\r\n% measuring that property, but perhaps the easiest way of determining\r\n% tortuosity of a path is using the arc-chord ratio\u00e2\u20ac\u201di.e., the length of the\r\n% curve divided by the distance between its endpoints.\r\n%\r\n% In my demo, I assumed the vessels were 2-dimensional, and then used some\r\n% <https:\/\/www.mathworks.com\/products\/image\/?requestedDomain=www.mathworks.com Image Processing Toolbox>\r\n% functionality to: 1) <https:\/\/en.wikipedia.org\/wiki\/Topological_skeleton skeletonize> the vessels of\r\n% interest using <https:\/\/www.mathworks.com\/help\/images\/ref\/bwmorph.html |bwmorph|\/skel>, and 2) determine the endpoints of the\r\n% segment (facilitated by a call to <https:\/\/www.mathworks.com\/help\/images\/ref\/bwmorph.html |bwmorph|\/endpoints>. Then, using one or\r\n% both endpoints of the segment, respectively, to 3) calculate the geodesic\r\n% distance transform of the vessel (<https:\/\/www.mathworks.com\/help\/images\/ref\/bwdistgeodesic.html |bwdistgeodesic|>), \r\n% the maximum value of\r\n% which provides the arc-length; and 4) calculate the distance the\r\n% endpoints. (You can find a discussion of this approach in a \r\n% <https:\/\/www.mathworks.com\/videos\/medical-image-processing-with-matlab-81890.html?s_tid=srchtitle recorded webinar>\r\n% at mathworks.com.)\r\n%\r\n% This approach is reasonably straightforward when you assume planarity, \r\n% but in 3D, it gets much more challenging\u00e2\u20ac\u201dbecause bwmorph\r\n% doesn\u00e2\u20ac\u2122t currently support the analysis of 3D images. That leaves us\r\n% struggling to do the skeletonization- and endpoint-detection steps.\r\n%\r\n% Enter Philip\u00e2\u20ac\u2122s |Skeleton3D| function. Using an algorithm (properly cited\r\n% in the code) based on \u00e2\u20ac\u01533-D medial surface\/axis thinning algorithms,\u00e2\u20ac\ufffd\r\n% |Skeleton3D| finds axial values that can be subsequently analyzed for the\r\n% determination of tortuosity. \r\n%\r\n%%\r\n%   load testvol\r\n%   inds = find(testvol);\r\n%   [y,x,z] = ind2sub(size(testvol),inds);\r\n%   s = scatter3(x,y,z,100,...\r\n%     'Marker','o',...\r\n%     'MarkerEdgeColor','none',...\r\n%     'MarkerFaceAlpha',0.3,...\r\n%     'MarkerFaceColor','c');\r\n%   xlabel('x');\r\n%   ylabel('y');\r\n%   zlabel('z');\r\n%   title('Test Volume')\r\n\r\n%%\r\n% \r\n% <<https:\/\/blogs.mathworks.com\/pick\/files\/testvol.png>>\r\n% \r\n\r\n%% \r\n% Now we can extract and display points along the skeleton of this test\r\n% volume:\r\n\r\n%%\r\n%   skel = Skeleton3D(testvol);\r\n%   inds = find(skel);\r\n%   [yskel,xskel,zskel] = ind2sub(size(skel),inds);\r\n%   hold on\r\n%   p2 = scatter3(xskel,yskel,zskel,20,...\r\n%       'Marker','o',...\r\n%       'MarkerEdgeColor','none',...\r\n%       'MarkerFaceColor','r');\r\n\r\n%%\r\n% \r\n% <<https:\/\/blogs.mathworks.com\/pick\/files\/testvolWithSkeleton.png>>\r\n% \r\n\r\n%% \r\n% For tortuosity determinations, there's still some more work to be\r\n% doneREPLACE_WITH_DASH_DASHwe still need to detect endpoints and arclengths, for instance.\r\n% Those are topics for a different day.\r\n\r\n%%\r\n% For Philip, an implementation comment: |Skeleton3D| is considerably slower\r\n% than it needs to be. It relies on looping over indices where <https:\/\/www.mathworks.com\/help\/matlab\/matlab_prog\/vectorization.html vectorized approaches>\r\n% can be much faster. And converting to <https:\/\/www.mathworks.com\/company\/newsletters\/articles\/matrix-indexing-in-matlab.html logical indexing> for\r\n% comparisons can also speed things up. For instance:\r\n%\r\n% In the determination of \"candidate foreground voxels,\" you can replace:\r\n%\r\n%   cands = intersect(find(cands(:)==1),find(skel(:)==1));\r\n% \r\n% with\r\n% \r\n%   cands = find(cands & skel);\r\n\r\n%%\r\n% (Admittedly, that buys just a little improvement in performance, but I find it\r\n% more elegant.) \r\n\r\n%%\r\n% More pointedly, if you were to <https:\/\/www.mathworks.com\/help\/matlab\/ref\/profile.html profile> your code, you'd find a large\r\n% percentage of time spent calculating the skeleton of your testvol\r\n% reflects nearly 87,000 calls to |p_oct_label|. That code could be greatly\r\n% sped up by replacing successive indx(row) = |find(cube(row,:)==1)| calls in the analysis of\r\n% 'cube' with something along the lines of \r\n%%\r\n%   [row,col] = ind2sub(size(cube),cube==1);\r\n%   indx(row) = col(row == 1); \r\n%   \r\n% and iterating over values of |row|.\r\n\r\n%% \r\n% Nonetheless, this provides a good start to anyone needing to skeletonize\r\n% structures in 3D. Thanks, Philip, for this very nice contribution!\r\n\r\n%%\r\n% As always, I welcome your\r\n% <https:\/\/blogs.mathworks.com\/pick\/?p=7532#respond thoughts and comments>.\r\n##### SOURCE END ##### 5b2d57c7399a4344aaa519f3ff9a595e\r\n-->","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img decoding=\"async\"  class=\"img-responsive\" src=\"https:\/\/blogs.mathworks.com\/pick\/files\/testvol.png\" onError=\"this.style.display ='none';\" \/><\/div><p>Brett's Pick this week is Skeleton3D, by Philip Kollmansberger.A couple years ago, I put together a demo to show how to use MATLAB to calculate the tortuosity, or &#8220;twistedness,&#8221; of blood... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/pick\/2016\/07\/01\/skeletonizing-an-image-in-3d\/\">read more >><\/a><\/p>","protected":false},"author":34,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[16],"tags":[],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/posts\/7532"}],"collection":[{"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/users\/34"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/comments?post=7532"}],"version-history":[{"count":13,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/posts\/7532\/revisions"}],"predecessor-version":[{"id":7584,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/posts\/7532\/revisions\/7584"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/media?parent=7532"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/categories?post=7532"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/tags?post=7532"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}