{"id":9918,"date":"2018-07-06T09:00:53","date_gmt":"2018-07-06T13:00:53","guid":{"rendered":"https:\/\/blogs.mathworks.com\/pick\/?p=9918"},"modified":"2018-07-05T16:20:18","modified_gmt":"2018-07-05T20:20:18","slug":"a-couple-of-my-favorite-new-image-processing-toolbox-functions","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/pick\/2018\/07\/06\/a-couple-of-my-favorite-new-image-processing-toolbox-functions\/","title":{"rendered":"A couple of my favorite new Image Processing Toolbox functions"},"content":{"rendered":"<div class=\"content\"><!--introduction--><!--\/introduction--><\/p>\n<p>In July, 2016, I selected <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/profile\/authors\/4556277-philip-kollmannsberger\">Philip Kollmansberger<\/a>&#8217;s useful File Exchange contribution <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/43400-skeleton3d\">Skeleton3D<\/a> as the Pick of the Week. I&#8217;d like to revisit that particular blog post today, if I may. For those of you who don&#8217;t yet have access to R2018a, Phillip&#8217;s contribution is still perhaps your best option for skeletonizing volumetric imagery. But a new function added to the <a href=\"https:\/\/www.mathworks.com\/products\/image.html\">Image Processing Toolbox<\/a> in the current release (that is, R2018a) gives you provides an alternative. In the Release Notes documenting modifications to the Toolbox, we learn that there is a new function called <a href=\"https:\/\/www.mathworks.com\/help\/images\/ref\/bwskel.html\"><tt>bwskel<\/tt><\/a> that supports skeletonization of 2D and 3D images. This is big!<\/p>\n<p>In light of this new function, it is interesting to take a fresh look at the 3D skeletonization of my earlier Pick.<\/p>\n<p>Consider:<\/p>\n<pre class=\"language-matlab\"><span class=\"comment\">%% Load and display sample 3D data<\/span>\r\nload <span class=\"string\">spiralVol.mat<\/span>;\r\nstepSize = 25;\r\n<span class=\"comment\">% Detect all spiral points:<\/span>\r\ninds = find(spiralVol);\r\n<span class=\"comment\">% Note the order reversal for x,y (col\/row):<\/span>\r\n[y,x,z] = ind2sub(size(spiralVol),inds);\r\nsubplot(2,2,1)\r\nmyTitle = sprintf(<span class=\"string\">'Full Spiral (Downsampled):\\n %i Points'<\/span>,<span class=\"keyword\">...<\/span>\r\n    numel(inds));\r\nrefreshSpiralVisualization(x,y,z,stepSize,myTitle);\r\n<\/pre>\n<pre class=\"language-matlab\"><span class=\"comment\">%% First, skeletonization using Skeleton3D (grayscale):<\/span>\r\ntic\r\nskel = Skeleton3D(spiralVol);\r\nt = toc;\r\ninds = find(skel);\r\nmyTitle = sprintf(<span class=\"string\">'Using Skeleton3D:\\n%i \"Skeleton\" Points; t = %0.2f sec '<\/span>,<span class=\"keyword\">...<\/span>\r\n    numel(inds),t);\r\ndisp(<span class=\"string\">'Using Skeleton3D, grayscale'<\/span>)\r\nsubplot(2,2,2)\r\nrefreshSpiralVisualization(x,y,z,stepSize,myTitle);\r\nhold <span class=\"string\">on<\/span>\r\n[yskel,xskel,zskel] = ind2sub(size(skel),inds);\r\nscatter3(xskel,yskel,zskel,5,<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>\n<pre class=\"language-matlab\"><span class=\"comment\">%% Next, skeletonization using Skeleton3D (binary):<\/span>\r\ntic\r\nspiralVolBinary = imbinarize(spiralVol);\r\nskel = Skeleton3D(spiralVolBinary);\r\nt = toc;\r\ninds = find(skel);\r\nmyTitle = sprintf(<span class=\"string\">'Using Skeleton3D (Binary):\\n%i \"Skeleton\" Points; t = %0.2f sec '<\/span>,<span class=\"keyword\">...<\/span>\r\n    numel(inds),t);\r\n[yskel,xskel,zskel] = ind2sub(size(skel),inds);\r\nsubplot(2,2,3)\r\nrefreshSpiralVisualization(x,y,z,stepSize,myTitle);\r\nhold <span class=\"string\">on<\/span>\r\nscatter3(xskel,yskel,zskel,5,<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>\n<pre class=\"language-matlab\"><span class=\"comment\">%% Finally, using |bwskel()| (Binary Only)<\/span>\r\ntic\r\nspiralVolLogical = imbinarize(spiralVol);\r\nskel = bwskel(spiralVolLogical);\r\nt = toc;\r\ninds = find(skel);\r\nmyTitle = sprintf(<span class=\"string\">'Using bwskel() (Binary):\\n%i \"Skeleton\" Points; t = %0.2f sec '<\/span>,<span class=\"keyword\">...<\/span>\r\n    numel(inds),t);\r\n[yskel,xskel,zskel] = ind2sub(size(skel),inds);\r\nsubplot(2,2,4)\r\nrefreshSpiralVisualization(x,y,z,stepSize,myTitle);\r\nhold <span class=\"string\">on<\/span>\r\nscatter3(xskel,yskel,zskel,5,<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>\n<pre class=\"language-matlab\"><span class=\"comment\">%%<\/span>\r\n<span class=\"keyword\">function<\/span> refreshSpiralVisualization(x,y,z,stepVal,myTitle)\r\nscatter3(x(1:stepVal:end),y(1:stepVal:end),z(1:stepVal:end),0.1,<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\">'c'<\/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\nset(gca,<span class=\"string\">'view'<\/span>,[-26.94  23.6])\r\ntitle(myTitle)\r\n<span class=\"keyword\">end<\/span>\r\n<\/pre>\n<p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/pick\/files\/screenCap3DSkel-1.png\" alt=\"\"> <\/p>\n<p>A few things come become apparent looking at these results. First, <tt>Skeleton3D()<\/tt> operates without error or warning on grayscale images (upper right axes), but appears to label every nonzero input pixel as a &#8220;skeleton&#8221; point. More usefully, as suggested by Phillip&#8217;s description of the algorithm, the function returns more reasonable skeleton points when operating on a binarization of the input data (lower left axes). However, that calculation took over 25 seconds (including binarization) on my computer. In contrast, <tt>bwskel()<\/tt> (lower right) errors (usefully, in my opinion) if you try to operate directly on the grayscale data, and takes less than a second (including binarization) operating on the segmented data. (The outputs of the two functions are not identical, but they both seem reasonable.)<\/p>\n<p>In the 2016 post, I also mentioned that the calculation of <a href=\"https:\/\/blogs.mathworks.com\/pick\/2016\/07\/01\/skeletonizing-an-image-in-3d\/\">blood vessel tortuosity<\/a> in 3D was compounded by the challenge of determining endpoints of 3D skeletons. That difficulty, too, has fallen by the wayside. The new function <a href=\"https:\/\/www.mathworks.com\/help\/images\/ref\/bwmorph3.html\"><tt>bwmorph3<\/tt><\/a> (also in the Image Processing Toolbox) now facilitates the calculation of a half-dozen operations on 3D binary images; fortunately, that list of operations includes endpoint-detection. The net result: if we had a volumetric representation of the vasculature we wanted to evaluate, we could now do so relatively easily.<\/p>\n<p>One final comment: note in the code above that the script contains a local function, or subfunction. The capacity to include subfunctions in scripts was added in R2016b.<\/p>\n<p>As always, I welcome your <a href=\"http:\/\/blogs.mathworks.com\/pick\/?p=9918#respond\">thoughts and comments<\/a>.<\/p>\n<p><script language=\"JavaScript\"> <!-- \n    function grabCode_17bdf88d7c7b49d0889568a6a2d8ff87() {\n        \/\/ Remember the title so we can use it in the new page\n        title = document.title;\n\n        \/\/ Break up these strings so that their presence\n        \/\/ in the Javascript doesn't mess up the search for\n        \/\/ the MATLAB code.\n        t1='17bdf88d7c7b49d0889568a6a2d8ff87 ' + '##### ' + 'SOURCE BEGIN' + ' #####';\n        t2='##### ' + 'SOURCE END' + ' #####' + ' 17bdf88d7c7b49d0889568a6a2d8ff87';\n    \n        b=document.getElementsByTagName('body')[0];\n        i1=b.innerHTML.indexOf(t1)+t1.length;\n        i2=b.innerHTML.indexOf(t2);\n \n        code_string = b.innerHTML.substring(i1, i2);\n        code_string = code_string.replace(\/REPLACE_WITH_DASH_DASH\/g,'--');\n\n        \/\/ Use \/x3C\/g instead of the less-than character to avoid errors \n        \/\/ in the XML parser.\n        \/\/ Use '\\x26#60;' instead of '<' so that the XML parser\n        \/\/ doesn't go ahead and substitute the less-than character. \n        code_string = code_string.replace(\/\\x3C\/g, '\\x26#60;');\n\n        copyright = 'Copyright 2018 The MathWorks, Inc.';\n\n        w = window.open();\n        d = w.document;\n        d.write('\n\n<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\n\\n');\n\n        d.title = title + ' (MATLAB code)';\n        d.close();\n    }   \n     --> <\/script><\/p>\n<p style=\"text-align: right; font-size: xx-small; font-weight:lighter;   font-style: italic; color: gray\"><a href=\"javascript:grabCode_17bdf88d7c7b49d0889568a6a2d8ff87()\"><span style=\"font-size: x-small;        font-style: italic;\">Get<br \/>\n      the MATLAB code <noscript>(requires JavaScript)<\/noscript><\/span><\/a><\/p>\n<p>      Published with MATLAB&reg; R2018a<\/p>\n<\/div>\n<p><!--\n17bdf88d7c7b49d0889568a6a2d8ff87 ##### SOURCE BEGIN #####\n%% A couple particularly useful new Image Processing Toolbox functions\n%%\n% In July, 2016, I selected <https:\/\/www.mathworks.com\/matlabcentral\/profile\/authors\/4556277-philip-kollmannsberger Philip Kollmansberger>\u00e2\u20ac&#x2122;s useful File Exchange\n% contribution <https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/43400-skeleton3d Skeleton3D> as the Pick of the Week. I\u00e2\u20ac&#x2122;d like to revisit that\n% particular blog post today, if I may. For those of you who don\u00e2\u20ac&#x2122;t yet have\n% access to R2018a, Phillip\u00e2\u20ac&#x2122;s contribution is still perhaps your best\n% option for skeletonizing volumetric imagery. But a new function added to\n% the <https:\/\/www.mathworks.com\/products\/image.html Image Processing Toolbox> in the current release (that is, R2018a) gives\n% you provides an alternative. In the Release Notes documenting modifications to the\n% Toolbox, we learn that there is a new function called <https:\/\/www.mathworks.com\/help\/images\/ref\/bwskel.html |bwskel|> that\n% supports skeletonization of 2D and 3D images. This is big!\n%\n% In light of this new function, it is interesting to take a fresh look at\n% the 3D skeletonization of my earlier Pick.\n%\n% Consider:\n%%\n%   %% Load and display sample 3D data\n%   load spiralVol.mat;\n%   stepSize = 25;\n%   % Detect all spiral points:\n%   inds = find(spiralVol);\n%   % Note the order reversal for x,y (col\/row):\n%   [y,x,z] = ind2sub(size(spiralVol),inds);\n%   subplot(2,2,1)\n%   myTitle = sprintf('Full Spiral (Downsampled):\\n %i Points',...\n%       numel(inds));\n%   refreshSpiralVisualization(x,y,z,stepSize,myTitle);\n%    \n%   %% First, skeletonization using Skeleton3D (grayscale):\n%   tic\n%   skel = Skeleton3D(spiralVol);\n%   t = toc;\n%   inds = find(skel);\n%   myTitle = sprintf('Using Skeleton3D:\\n%i \"Skeleton\" Points; t = %0.2f sec ',...\n%       numel(inds),t);\n%   disp('Using Skeleton3D, grayscale')\n%   subplot(2,2,2)\n%   refreshSpiralVisualization(x,y,z,stepSize,myTitle);\n%   hold on\n%   [yskel,xskel,zskel] = ind2sub(size(skel),inds);\n%   scatter3(xskel,yskel,zskel,5,...\n%       'Marker','o',...\n%       'MarkerEdgeColor','none',...\n%       'MarkerFaceColor','r');\n%   \n%   %% Next, skeletonization using Skeleton3D (binary):\n%   tic\n%   spiralVolBinary = imbinarize(spiralVol);\n%   skel = Skeleton3D(spiralVolBinary);\n%   t = toc;\n%   inds = find(skel);\n%   myTitle = sprintf('Using Skeleton3D (Binary):\\n%i \"Skeleton\" Points; t = %0.2f sec ',...\n%       numel(inds),t);\n%   [yskel,xskel,zskel] = ind2sub(size(skel),inds);\n%   subplot(2,2,3)\n%   refreshSpiralVisualization(x,y,z,stepSize,myTitle);\n%   hold on\n%   scatter3(xskel,yskel,zskel,5,...\n%       'Marker','o',...\n%       'MarkerEdgeColor','none',...\n%       'MarkerFaceColor','r');\n%    \n%   %% Finally, using |bwskel()| (Binary Only)\n%   tic\n%   spiralVolLogical = imbinarize(spiralVol);\n%   skel = bwskel(spiralVolLogical);\n%   t = toc;\n%   inds = find(skel);\n%   myTitle = sprintf('Using bwskel() (Binary):\\n%i \"Skeleton\" Points; t = %0.2f sec ',...\n%       numel(inds),t);\n%   [yskel,xskel,zskel] = ind2sub(size(skel),inds);\n%   subplot(2,2,4)\n%   refreshSpiralVisualization(x,y,z,stepSize,myTitle);\n%   hold on\n%   scatter3(xskel,yskel,zskel,5,...\n%       'Marker','o',...\n%       'MarkerEdgeColor','none',...\n%       'MarkerFaceColor','r');\n%    \n%   %%\n%   function refreshSpiralVisualization(x,y,z,stepVal,myTitle)\n%   scatter3(x(1:stepVal:end),y(1:stepVal:end),z(1:stepVal:end),0.1,...\n%       'Marker','o',...\n%       'MarkerEdgeColor','c',...\n%      'MarkerFaceAlpha',0.3,...\n%       'MarkerFaceColor','c');\n%   xlabel('x');\n%   ylabel('y');\n%   zlabel('z');\n%   set(gca,'view',[-26.94  23.6])\n%   title(myTitle)\n%   end\n%%\n% \n% <<https:\/\/blogs.mathworks.com\/pick\/files\/screenCap3DSkel-1.png>>\n% \n\n%%   \n% A few things come become apparent looking at these results. First,\n% |Skeleton3D()| operates without error or warning on grayscale images (upper\n% right axes), but appears to label every nonzero input pixel as a\n% \u00e2\u20ac\u0153skeleton\u00e2\u20ac\ufffd point. More usefully, as suggested by Phillip\u00e2\u20ac&#x2122;s description of\n% the algorithm, the function returns more reasonable skeleton points when\n% operating on a binarization of the input data (lower left axes). However,\n% that calculation took over 25 seconds (including binarization) on my\n% computer. In contrast, |bwskel()| (lower right) errors (usefully, in my\n% opinion) if you try to operate directly on the grayscale data, and takes\n% less than a second (including binarization) operating on the segmented\n% data. (The outputs of the two functions are not identical, but they both\n% seem reasonable.)\n%\n% In the 2016 post, I also mentioned that the calculation\n% of <https:\/\/blogs.mathworks.com\/pick\/2016\/07\/01\/skeletonizing-an-image-in-3d\/ blood vessel tortuosity> in 3D was compounded by the challenge of determining\n% endpoints of 3D skeletons. That difficulty, too, has fallen by the\n% wayside. The new function <https:\/\/www.mathworks.com\/help\/images\/ref\/bwmorph3.html |bwmorph3|> (also in the Image Processing Toolbox)\n% now facilitates the calculation of a half-dozen operations on 3D binary\n% images; fortunately, that list of operations includes endpoint-detection.\n% The net result: if we had a volumetric representation of the vasculature\n% we wanted to evaluate, we could now do so relatively easily.\n%\n% One final comment: note in the code above that the script contains a\n% local function, or subfunction. The capacity to include subfunctions in\n% scripts was added in R2016b.\n\n%%\n% As always, I welcome your\n% <http:\/\/blogs.mathworks.com\/pick\/?p=9918#respond thoughts and comments>.\n##### SOURCE END ##### 17bdf88d7c7b49d0889568a6a2d8ff87\n--><\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img decoding=\"async\"  class=\"img-responsive\" src=\"https:\/\/blogs.mathworks.com\/pick\/files\/screenCap3DSkel-1.png\" onError=\"this.style.display ='none';\" \/><\/div>\n<p>\nIn July, 2016, I selected Philip Kollmansberger&#8217;s useful File Exchange contribution Skeleton3D as the Pick of the Week. I&#8217;d like to revisit that particular blog post today, if I may. For&#8230; <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/pick\/2018\/07\/06\/a-couple-of-my-favorite-new-image-processing-toolbox-functions\/\">read more >><\/a><\/p>\n","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\/9918"}],"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=9918"}],"version-history":[{"count":6,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/posts\/9918\/revisions"}],"predecessor-version":[{"id":9936,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/posts\/9918\/revisions\/9936"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/media?parent=9918"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/categories?post=9918"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/tags?post=9918"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}