{"id":215,"date":"2008-06-13T13:41:31","date_gmt":"2008-06-13T17:41:31","guid":{"rendered":"https:\/\/blogs.mathworks.com\/steve\/2008\/06\/13\/performance-optimization-for-applylut\/"},"modified":"2019-10-28T09:33:14","modified_gmt":"2019-10-28T13:33:14","slug":"performance-optimization-for-applylut","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/steve\/2008\/06\/13\/performance-optimization-for-applylut\/","title":{"rendered":"Performance optimization for applylut"},"content":{"rendered":"<div class=\"content\">\n<p>I thought I would finish my discussion of <tt>applylut<\/tt> and <tt>makelut<\/tt> by describing a performance optimization we implemented for version 6 (R2007b) of the Image Processing Toolbox.<\/p>\n<p>A couple of years ago, a developer on our team wanted to know the details of the 'thin' option of <tt>bwmorph<\/tt>. This syntax \"thins\" connected strokes in a binary image. For example:<\/p>\n<pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid #c8c8c8;\">bw = imread(<span style=\"color: #a020f0;\">'text.png'<\/span>);\r\nimshow(bw)<\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/blogs.mathworks.com\/images\/steve\/2008\/applylut_4_01.png\" hspace=\"5\" vspace=\"5\" \/><\/p>\n<pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid #c8c8c8;\">bwt = bwmorph(bw, <span style=\"color: #a020f0;\">'thin'<\/span>);\r\nimshow(bwt)<\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/blogs.mathworks.com\/images\/steve\/2008\/applylut_4_02.png\" hspace=\"5\" vspace=\"5\" \/><\/p>\n<p>With the syntax <tt>bwmorph(bw, 'thin', N)<\/tt>, you can tell <tt>bwmorph<\/tt> to repeat the thinning operation N times. If you use <tt>bwmorph(bw, 'thin', Inf)<\/tt>, then <tt>bwmorph<\/tt> will repeat the operation until the image stops changing.<\/p>\n<pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid #c8c8c8;\">bwt = bwmorph(bw, <span style=\"color: #a020f0;\">'thin'<\/span>, Inf);\r\nimshow(bwt)<\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/blogs.mathworks.com\/images\/steve\/2008\/applylut_4_03.png\" hspace=\"5\" vspace=\"5\" \/><\/p>\n<p>So, in this long-ago conversation about <tt>bwmorph<\/tt>, I explained that many of the operations supported by <tt>bwmorph<\/tt> are implemented using <tt>applylut<\/tt>. About halfway through explaining <tt>applylut<\/tt>, I had a light bulb moment. It dawned on me that the thinning operation of <tt>bwmorph<\/tt> <b>never<\/b> changes a 0-valued pixel to a 1! So for any 0-valued input pixel, it is kind of pointless to examine the surrounding 3-by-3<br \/>\nneighborhood, which is what <tt>applylut<\/tt> does. One could just immediately determine that the corresponding output pixel should be 0.<\/p>\n<p>There are other operations ('thicken', for example) that never change a 1-valued pixel to a 0.<\/p>\n<p>After some further discussion, we realized that <tt>applylut<\/tt> could examine any lookup table to determine whether it had one of these properties. So that's what we did. Starting in version<br \/>\n6 of the toolbox, <tt>applylut<\/tt> examines the input lookup table to see if it always transforms a 0-valued pixel to 0, or a 1-valued pixel to 1. If so, special-case<br \/>\ncode is used that skips the neighborhood lookup step for pixels that are 0 (or 1).<\/p>\n<p>Here's the time required on my Windows laptop to apply the thinning operation to text.png. (You can find the function <tt>timeit<\/tt> on the MATLAB Central File Exchange.)<\/p>\n<pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid #c8c8c8;\">f = @() bwmorph(bw, <span style=\"color: #a020f0;\">'thin'<\/span>, Inf);\r\ntimeit(f)<\/pre>\n<pre style=\"font-style: oblique;\">ans =\r\n\r\n    0.0044\r\n\r\n<\/pre>\n<p>Running with an older version of the Image Processing Toolbox, the same operation takes about 104 milliseconds. That's about<br \/>\na factor of 23 reduction in execution time!<\/p>\n<p><em>[Note added June 16, 2008:]<\/em> The <tt>applylut<\/tt> optimization was not the only change made to speed up the <tt>'thin'<\/tt> option of bwmorph. We also realized that work being done using several lookup tables could be combined in a single lookup table.<\/p>\n<p>These optimizations went into the product in R2007b, which shipped in September 2007. Your mileage will certainly vary depending<br \/>\non your particular data. The speedup you see depends on how many 0-valued (or 1-valued) pixels the image contains.<\/p>\n<p>For your reference, here are my previous posts on <tt>applylut<\/tt> and <tt>makelut<\/tt>:<\/p>\n<div>\n<ul>\n<li><a href=\"https:\/\/blogs.mathworks.com\/steve\/2013\/08\/28\/introduction-to-spatial-referencing\/\">Introduction<\/a><\/li>\n<li>use of <tt>applylut<\/tt> and <tt>makelut<\/tt><\/li>\n<li><a href=\"https:\/\/blogs.mathworks.com\/steve\/2010\/02\/16\/sampling-a-cosine\/\">Game of Life<\/a><\/li>\n<\/ul>\n<\/div>\n<p><script>\/\/ <![CDATA[\nfunction grabCode_91c4564aa03845f588c3405075b87b17() {\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='91c4564aa03845f588c3405075b87b17 ' + '##### ' + 'SOURCE BEGIN' + ' #####';\n        t2='##### ' + 'SOURCE END' + ' #####' + ' 91c4564aa03845f588c3405075b87b17';\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        author = 'Steve Eddins';\n        copyright = 'Copyright 2008 The MathWorks, Inc.';\n\n        w = window.open();\n        d = w.document;\n        d.write('\n\n\n\n<pre>\\n');\r\n        d.write(code_string);\r\n\r\n        \/\/ Add author and copyright lines at the bottom if specified.\r\n        if ((author.length > 0) || (copyright.length > 0)) {\r\n            d.writeln('');\r\n            d.writeln('%%');\r\n            if (author.length > 0) {\r\n                d.writeln('% _' + author + '_');\r\n            }\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\\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;\">\n<a><span style=\"font-size: x-small; font-style: italic;\">Get<br \/>\nthe MATLAB code<br \/>\n<noscript>(requires JavaScript)<\/noscript><\/span><\/a><\/p>\n<p>Published with MATLAB\u00ae 7.6<\/p>\n<\/div>\n<p><!--\n91c4564aa03845f588c3405075b87b17 ##### SOURCE BEGIN #####\n%%\n% I thought I would finish my discussion of |applylut| and |makelut|\n% by describing a performance optimization we implemented for\n% version 6 (R2007b) of the Image Processing Toolbox.\n%\n% A couple of years ago, a developer on our team wanted to know the\n% details of the 'thin' option of |bwmorph|. This syntax \"thins\"\n% connected strokes in a binary image. For example:\n\nbw = imread('text.png');\nimshow(bw)\n\n%%\n\nbwt = bwmorph(bw, 'thin');\nimshow(bwt)\n\n%%\n% With the syntax |bwmorph(bw, 'thin', N)|, you can tell |bwmorph|\n% to repeat the thinning operation N times.  If you use |bwmorph(bw,\n% 'thin', Inf)|, then |bwmorph| will repeat the operation until the\n% image stops changing.\n\nbwt = bwmorph(bw, 'thin', Inf);\nimshow(bwt)\n\n%%\n% So, in this long-ago conversation about |bwmorph|, I explained\n% that many of the operations supported by |bwmorph| are implemented\n% using |applylut|.  About halfway through explaining |applylut|, I\n% had a light bulb moment.  It dawned on me that the thinning\n% operation of |bwmorph| *never* changes a 0-valued pixel to a 1!\n% So for any 0-valued input pixel, it is kind of pointless to\n% examine the surrounding 3-by-3 neighborhood, which is what\n% |applylut| does. One could just immediately determine that the\n% corresponding output pixel should be 0.\n%\n% There are other operations ('thicken', for example) that never\n% change a 1-valued pixel to a 0.\n%\n% After some further discussion, we realized that |applylut| could\n% examine any lookup table to determine whether it had one of these\n% properties. So that's what we did. Starting in version 6 of the\n% toolbox,\n% |applylut| examines the input lookup table to see if it always\n% transforms a 0-valued pixel to 0, or a 1-valued pixel to 1. If so,\n% special-case code is used that skips the neighborhood lookup step\n% for pixels that are 0 (or 1).\n%\n% Here's the time required on my Windows laptop to apply the\n% thinning operation to text.png.  (You can the function\n% <https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/loadFile.do?objectId=18798&objectType=FILE % |timeit|> on the MATLAB Central File Exchange.)\n\nf = @() bwmorph(bw, 'thin', Inf);\ntimeit(f)\n\n%%\n% Running with an older version of the Image Processing Toolbox, the\n% same operation takes about 104 milliseconds. That's about a factor\n% of 23 reduction in execution time!\n%\n% This optimization went into the product in R2007b, which shipped\n% in September 2007. Your mileage will certainly vary depending on\n% your particular data. The speedup you see depends on how many\n% 0-valued (or 1-valued) pixels the image contains.\n%\n% For your reference, here are my previous posts on |applylut| and\n% |makelut|:\n%\n% * <Introduction>\n% * <Basic use of |applylut| and |makelut|>\n% * <Conway's Game of Life>\n##### SOURCE END ##### 91c4564aa03845f588c3405075b87b17\n--><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\nI thought I would finish my discussion of applylut and makelut by describing a performance optimization we implemented for version 6 (R2007b) of the Image Processing Toolbox.<br \/>\nA couple of years ago,... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/steve\/2008\/06\/13\/performance-optimization-for-applylut\/\">read more >><\/a><\/p>\n","protected":false},"author":42,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[494,86,76,36,492,474],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/215"}],"collection":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/users\/42"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/comments?post=215"}],"version-history":[{"count":3,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/215\/revisions"}],"predecessor-version":[{"id":2631,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/215\/revisions\/2631"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media?parent=215"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/categories?post=215"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/tags?post=215"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}