{"id":3210,"date":"2019-04-09T07:00:57","date_gmt":"2019-04-09T11:00:57","guid":{"rendered":"https:\/\/blogs.mathworks.com\/steve\/?p=3210"},"modified":"2019-11-01T22:38:37","modified_gmt":"2019-11-02T02:38:37","slug":"multiresolution-image-pyramids-and-impyramid-part-2","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/steve\/2019\/04\/09\/multiresolution-image-pyramids-and-impyramid-part-2\/","title":{"rendered":"Multiresolution image pyramids and impyramid &#8211; part 2"},"content":{"rendered":"<div class=\"content\"><p>Last time, I talked about the function <tt>impyramid<\/tt> and how I have been dissatisfied with it. (Confession: I designed it.) Today I want to present an alternative approach to creating a multiresolution pyramid.<\/p><p>Here are some of my wishes:<\/p><div><ul><li>Don't make me think about how many levels to specify. Find a good heuristic rule and apply it by default.<\/li><li>Compute all the levels at once. Don't make me write a loop or initialize a place to keep the levels.<\/li><li>Handle all the picky details like how to handle reducing a level with odd dimensions.<\/li><li>Use a better resampling filter.<\/li><li>Make the sizes match when you reduce a level and then expand it.<\/li><\/ul><\/div><p>To start off, then, how many levels should we compute by default? Another way to ask that question is: how small do we want to let the last level get? Should we avoid letting the images get smaller than a certain size, such as 32x32? Or should we compute the levels all the way down to a scalar?<\/p><p>I don't know what is best. To answer the question, I would want to experiment with practical applications of multiresolution pyramids to find a heuristic rule that works well. For now, I'll go with a lower size limit of 32x32. Let's tinker with some code based on that rule.<\/p><pre class=\"codeinput\">A = imread(<span class=\"string\">'https:\/\/blogs.mathworks.com\/steve\/files\/IMG_9968.jpg'<\/span>);\r\nimshow(A)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/multiresolution_pyramids_2_01.png\" alt=\"\"> <pre class=\"codeinput\">M = size(A,1);\r\nN = size(A,2);\r\n[M N]\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n        1360        1904\r\n\r\n<\/pre><pre class=\"codeinput\">lower_limit = 32;\r\n<\/pre><p>Each level of the pyramid reduces the size by a factor of two, so the <tt>log2<\/tt> function seems relevant.<\/p><pre class=\"codeinput\">log2([M N])\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n   10.4094   10.8948\r\n\r\n<\/pre><pre class=\"codeinput\">ans - log2(lower_limit)\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n    5.4094    5.8948\r\n\r\n<\/pre><p>If we don't divide by 2 more than the above number of times, then the smallest image size in the pyramid will be no smaller than <tt>lower_limit<\/tt>. Clearly, the number of size reduction steps has to be an integer.<\/p><pre class=\"codeinput\">num_levels = min(floor(log2([M N]) - log2(lower_limit)))\r\n<\/pre><pre class=\"codeoutput\">\r\nnum_levels =\r\n\r\n     5\r\n\r\n<\/pre><p>To handle degenerate cases smoothly, as well as to make visualizations easier, I want to include the original image as the first level in the pyramid, so ...<\/p><pre class=\"codeinput\">num_levels = num_levels + 1\r\n<\/pre><pre class=\"codeoutput\">\r\nnum_levels =\r\n\r\n     6\r\n\r\n<\/pre><p>Next, let's consider how to avoid odd image size dimensions when reducing each pyramid level. Our example image is 1360x1904, and if you just divide those numbers by 2 five times, you end up with [42.5 59.5]. Let's take the point of view that we want the smallest image in the pyramid to have an integer number of rows and columns. And then let's figure out how much to pad the original image so that we don't end up with any fractional rows and columns anywhere.<\/p><pre class=\"codeinput\">smallest_size = [M N] \/ 2^(num_levels - 1)\r\n<\/pre><pre class=\"codeoutput\">\r\nsmallest_size =\r\n\r\n   42.5000   59.5000\r\n\r\n<\/pre><pre class=\"codeinput\">smallest_size = ceil(smallest_size)\r\n<\/pre><pre class=\"codeoutput\">\r\nsmallest_size =\r\n\r\n    43    60\r\n\r\n<\/pre><pre class=\"codeinput\">padded_size = smallest_size * 2^(num_levels - 1)\r\n<\/pre><pre class=\"codeoutput\">\r\npadded_size =\r\n\r\n        1376        1920\r\n\r\n<\/pre><p>I can imagine different ways to pad the original image, each of which has pros and cons. I'm inclined to do something simple, like pad to the right and bottom of the image by replicating the border pixels.<\/p><pre class=\"codeinput\">A_padded = padarray(A,padded_size - [M N],<span class=\"string\">'replicate'<\/span>,<span class=\"string\">'post'<\/span>);\r\n<\/pre><p>Now I need to pick a method for shrinking images by a factor of two to form each level. I propose to use <tt>imresize<\/tt> with the Lanczos3 interpolating kernel. (The <tt>lanczos3<\/tt> function is at the bottom of this script.<\/p><pre class=\"codeinput\">figure\r\nfplot(@lanczos3,[-4 4],<span class=\"string\">'LineWidth'<\/span>,1.5)\r\ntitle(<span class=\"string\">'Lanczos3 interpolating kernel'<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/multiresolution_pyramids_2_02.png\" alt=\"\"> <p>You can see that this is similar to a sinc function but nonzero only in the interval $|x| \\leq 3$. The kernel has reasonably good sharpness and antialiasing behavior.<\/p><p>Now we're ready to shrink the original image (as padded) several times to form the multiresolution pyramid.<\/p><pre class=\"codeinput\">pyramid = cell(1,num_levels);\r\npyramid{1} = A_padded;\r\n<span class=\"keyword\">for<\/span> k = 2:num_levels\r\n    pyramid{k} = imresize(pyramid{k-1},0.5,<span class=\"string\">'lanczos3'<\/span>);\r\n<span class=\"keyword\">end<\/span>\r\n<\/pre><p>One final fix-up step: let's use the original (unpadded) image as level 1. One advantage of doing that is that we don't have to keep track of the original image size as a separate piece of data.<\/p><pre class=\"codeinput\">pyramid{1} = A;\r\n<\/pre><p>The function <tt>multiresolutionPyramid<\/tt>, included at the bottom of this post, includes the logic and computations that I have described above. I have included another function, <tt>visualizePyramid<\/tt>, for visualizing the pyramid.<\/p><pre class=\"codeinput\">pyramid = multiresolutionPyramid(A);\r\nvisualizePyramid(pyramid)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/multiresolution_pyramids_2_03.png\" alt=\"\"> <p>Next time, I expect to talk about Laplacian pyramids.<\/p><pre class=\"codeinput\"><span class=\"keyword\">function<\/span> f = lanczos3(x)\r\n<span class=\"comment\">% See Graphics Gems, Andrew S. Glasser (ed), Morgan Kaufman, 1990,<\/span>\r\n<span class=\"comment\">% pp. 157-158.<\/span>\r\n\r\nf = (sin(pi*x) .* sin(pi*x\/3) + eps) .\/ ((pi^2 * x.^2 \/ 3) + eps);\r\nf = f .* (abs(x) &lt; 3);\r\n<span class=\"keyword\">end<\/span>\r\n\r\n<span class=\"keyword\">function<\/span> mrp = multiresolutionPyramid(A,num_levels)\r\n<span class=\"comment\">%multiresolutionPyramid(A,numlevels)<\/span>\r\n<span class=\"comment\">%   mrp = multiresolutionPyramid(A,numlevels) returns a multiresolution<\/span>\r\n<span class=\"comment\">%   pyramd from the input image, A. The output, mrp, is a 1-by-numlevels<\/span>\r\n<span class=\"comment\">%   cell array. The first element of mrp, mrp{1}, is the input image.<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%   If numlevels is not specified, then it is automatically computed to<\/span>\r\n<span class=\"comment\">%   keep the smallest level in the pyramid at least 32-by-32.<\/span>\r\n\r\n<span class=\"comment\">%   Steve Eddins<\/span>\r\n<span class=\"comment\">%   MathWorks<\/span>\r\n\r\nA = im2double(A);\r\n\r\nM = size(A,1);\r\nN = size(A,2);\r\n\r\n<span class=\"keyword\">if<\/span> nargin &lt; 2\r\n    lower_limit = 32;\r\n    num_levels = min(floor(log2([M N]) - log2(lower_limit))) + 1;\r\n<span class=\"keyword\">else<\/span>\r\n    num_levels = min(num_levels, min(floor(log2([M N]))) + 2);\r\n<span class=\"keyword\">end<\/span>\r\n\r\nmrp = cell(1,num_levels);\r\n\r\nsmallest_size = [M N] \/ 2^(num_levels - 1);\r\nsmallest_size = ceil(smallest_size);\r\npadded_size = smallest_size * 2^(num_levels - 1);\r\n\r\nAp = padarray(A,padded_size - [M N],<span class=\"string\">'replicate'<\/span>,<span class=\"string\">'post'<\/span>);\r\n\r\nmrp{1} = Ap;\r\n<span class=\"keyword\">for<\/span> k = 2:num_levels\r\n    mrp{k} = imresize(mrp{k-1},0.5,<span class=\"string\">'lanczos3'<\/span>);\r\n<span class=\"keyword\">end<\/span>\r\n\r\nmrp{1} = A;\r\n<span class=\"keyword\">end<\/span>\r\n\r\n<span class=\"keyword\">function<\/span> tiles_out = visualizePyramid(p)\r\n<span class=\"comment\">% Steve Eddins<\/span>\r\n<span class=\"comment\">% MathWorks<\/span>\r\n\r\nM = size(p{1},1);\r\nN = size(p{1},2);\r\n\r\n<span class=\"keyword\">for<\/span> k = 1:numel(p)\r\n    Mk = size(p{k},1);\r\n    Nk = size(p{k},2);\r\n    Mpad1 = ceil((M - Mk)\/2);\r\n    Mpad2 = M - Mk - Mpad1;\r\n    Npad1 = ceil((N - Nk)\/2);\r\n    Npad2 = N - Nk - Npad1;\r\n\r\n    A = p{k};\r\n    A = padarray(A,[Mpad1 Npad1],0.5,<span class=\"string\">'pre'<\/span>);\r\n    A = padarray(A,[Mpad2 Npad2],0.5,<span class=\"string\">'post'<\/span>);\r\n    p{k} = A;\r\n<span class=\"keyword\">end<\/span>\r\n\r\ntiles = imtile(p,<span class=\"string\">'GridSize'<\/span>,[NaN 2],<span class=\"string\">'BorderSize'<\/span>,20,<span class=\"string\">'BackgroundColor'<\/span>,[0.3 0.3 0.3]);\r\nimshow(tiles)\r\n\r\n<span class=\"keyword\">if<\/span> nargout &gt; 0\r\n    tiles_out = tiles;\r\n<span class=\"keyword\">end<\/span>\r\n<span class=\"keyword\">end<\/span>\r\n<\/pre><script language=\"JavaScript\"> <!-- \r\n    function grabCode_71267ffabfec489892af3385815f3764() {\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='71267ffabfec489892af3385815f3764 ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' 71267ffabfec489892af3385815f3764';\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 2019 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_71267ffabfec489892af3385815f3764()\"><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; R2019a<br><\/p><\/div><!--\r\n71267ffabfec489892af3385815f3764 ##### SOURCE BEGIN #####\r\n%%\r\n% Last time, I talked about the function |impyramid| and how I have been\r\n% dissatisfied with it. (Confession: I designed it.) Today I want to\r\n% present an alternative approach to creating a multiresolution pyramid.\r\n%\r\n% Here are some of my wishes:\r\n%\r\n% * Don't make me think about how many levels to specify. Find a good\r\n% heuristic rule and apply it by default.\r\n% * Compute all the levels at once. Don't make me write a loop or\r\n% initialize a place to keep the levels.\r\n% * Handle all the picky details like how to handle reducing a level with\r\n% odd dimensions.\r\n% * Use a better resampling filter.\r\n% * Make the sizes match when you reduce a level and then expand it.\r\n%\r\n% To start off, then, how many levels should we compute by default? Another\r\n% way to ask that question is: how small do we want to let the last level\r\n% get? Should we avoid letting the images get smaller than a certain size,\r\n% such as 32x32? Or should we compute the levels all the way down to a\r\n% scalar?\r\n%\r\n% I don't know what is best. To answer the question, I would want to\r\n% experiment with practical applications of multiresolution pyramids to\r\n% find a heuristic rule that works well. For now, I'll go with a lower size\r\n% limit of 32x32. Let's tinker with some code based on that rule.\r\n\r\nA = imread('https:\/\/blogs.mathworks.com\/steve\/files\/IMG_9968.jpg');\r\nimshow(A)\r\n\r\n%%\r\nM = size(A,1);\r\nN = size(A,2);\r\n[M N]\r\n\r\n%%\r\nlower_limit = 32;\r\n\r\n%%\r\n% Each level of the pyramid reduces the size by a factor of two, so the\r\n% |log2| function seems relevant.\r\n\r\nlog2([M N])\r\n\r\n%%\r\nans - log2(lower_limit)\r\n\r\n%%\r\n% If we don't divide by 2 more than the above number of times, then the\r\n% smallest image size in the pyramid will be no smaller than |lower_limit|.\r\n% Clearly, the number of size reduction steps has to be an integer.\r\n\r\nnum_levels = min(floor(log2([M N]) - log2(lower_limit)))\r\n\r\n%%\r\n% To handle degenerate cases smoothly, as well as to make visualizations\r\n% easier, I want to include the original image as the first level in the\r\n% pyramid, so ...\r\n\r\nnum_levels = num_levels + 1\r\n\r\n%%\r\n% Next, let's consider how to avoid odd image size dimensions when reducing\r\n% each pyramid level. Our example image is 1360x1904, and if you just\r\n% divide those numbers by 2 five times, you end up with [42.5 59.5]. Let's\r\n% take the point of view that we want the smallest image in the pyramid to\r\n% have an integer number of rows and columns. And then let's figure out how\r\n% much to pad the original image so that we don't end up with any\r\n% fractional rows and columns anywhere.\r\n\r\nsmallest_size = [M N] \/ 2^(num_levels - 1)\r\n\r\n%%\r\n\r\nsmallest_size = ceil(smallest_size)\r\n\r\n%%\r\n\r\npadded_size = smallest_size * 2^(num_levels - 1)\r\n\r\n%%\r\n% I can imagine different ways to pad the original image, each of which has\r\n% pros and cons. I'm inclined to do something simple, like pad to the right\r\n% and bottom of the image by replicating the border pixels.\r\n\r\nA_padded = padarray(A,padded_size - [M N],'replicate','post');\r\n\r\n%%\r\n% Now I need to pick a method for shrinking images by a factor of two to\r\n% form each level. I propose to use |imresize| with the Lanczos3\r\n% interpolating kernel. (The |lanczos3| function is at the bottom of this\r\n% script.\r\n\r\nfigure\r\nfplot(@lanczos3,[-4 4],'LineWidth',1.5)\r\ntitle('Lanczos3 interpolating kernel')\r\n\r\n%%\r\n% You can see that this is similar to a sinc function but nonzero only in\r\n% the interval $|x| \\leq 3$. The kernel has reasonably good sharpness and\r\n% antialiasing behavior.\r\n%\r\n% Now we're ready to shrink the original image (as padded) several times to\r\n% form the multiresolution pyramid.\r\n\r\npyramid = cell(1,num_levels);\r\npyramid{1} = A_padded;\r\nfor k = 2:num_levels\r\n    pyramid{k} = imresize(pyramid{k-1},0.5,'lanczos3');\r\nend\r\n\r\n%%\r\n% One final fix-up step: let's use the original (unpadded) image as level\r\n% 1. One advantage of doing that is that we don't have to keep track of the\r\n% original image size as a separate piece of data.\r\n\r\npyramid{1} = A;\r\n\r\n%%\r\n% The function |multiresolutionPyramid|, included at the bottom of this\r\n% post, includes the logic and computations that I have described above. I\r\n% have included another function, |visualizePyramid|, for visualizing the\r\n% pyramid.\r\n\r\npyramid = multiresolutionPyramid(A);\r\nvisualizePyramid(pyramid)\r\n\r\n%%\r\n% Next time, I expect to talk about Laplacian pyramids.\r\n\r\n%%\r\nfunction f = lanczos3(x)\r\n% See Graphics Gems, Andrew S. Glasser (ed), Morgan Kaufman, 1990,\r\n% pp. 157-158.\r\n\r\nf = (sin(pi*x) .* sin(pi*x\/3) + eps) .\/ ((pi^2 * x.^2 \/ 3) + eps);\r\nf = f .* (abs(x) < 3);\r\nend\r\n\r\nfunction mrp = multiresolutionPyramid(A,num_levels)\r\n%multiresolutionPyramid(A,numlevels)\r\n%   mrp = multiresolutionPyramid(A,numlevels) returns a multiresolution\r\n%   pyramd from the input image, A. The output, mrp, is a 1-by-numlevels\r\n%   cell array. The first element of mrp, mrp{1}, is the input image.\r\n%\r\n%   If numlevels is not specified, then it is automatically computed to\r\n%   keep the smallest level in the pyramid at least 32-by-32.\r\n\r\n%   Steve Eddins\r\n%   MathWorks\r\n\r\nA = im2double(A);\r\n\r\nM = size(A,1);\r\nN = size(A,2);\r\n\r\nif nargin < 2\r\n    lower_limit = 32;\r\n    num_levels = min(floor(log2([M N]) - log2(lower_limit))) + 1;\r\nelse\r\n    num_levels = min(num_levels, min(floor(log2([M N]))) + 2);\r\nend\r\n\r\nmrp = cell(1,num_levels);\r\n\r\nsmallest_size = [M N] \/ 2^(num_levels - 1);\r\nsmallest_size = ceil(smallest_size);\r\npadded_size = smallest_size * 2^(num_levels - 1);\r\n\r\nAp = padarray(A,padded_size - [M N],'replicate','post');\r\n\r\nmrp{1} = Ap;\r\nfor k = 2:num_levels\r\n    mrp{k} = imresize(mrp{k-1},0.5,'lanczos3');\r\nend\r\n\r\nmrp{1} = A;\r\nend\r\n\r\nfunction tiles_out = visualizePyramid(p)\r\n% Steve Eddins\r\n% MathWorks\r\n\r\nM = size(p{1},1);\r\nN = size(p{1},2);\r\n\r\nfor k = 1:numel(p)\r\n    Mk = size(p{k},1);\r\n    Nk = size(p{k},2);\r\n    Mpad1 = ceil((M - Mk)\/2);\r\n    Mpad2 = M - Mk - Mpad1;\r\n    Npad1 = ceil((N - Nk)\/2);\r\n    Npad2 = N - Nk - Npad1;\r\n    \r\n    A = p{k};\r\n    A = padarray(A,[Mpad1 Npad1],0.5,'pre');\r\n    A = padarray(A,[Mpad2 Npad2],0.5,'post');\r\n    p{k} = A;\r\nend\r\n\r\ntiles = imtile(p,'GridSize',[NaN 2],'BorderSize',20,'BackgroundColor',[0.3 0.3 0.3]);\r\nimshow(tiles)\r\n\r\nif nargout > 0\r\n    tiles_out = tiles;\r\nend\r\nend\r\n\r\n##### SOURCE END ##### 71267ffabfec489892af3385815f3764\r\n-->","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img decoding=\"async\"  class=\"img-responsive\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/multiresolution_pyramids_2_03.png\" onError=\"this.style.display ='none';\" \/><\/div><p>Last time, I talked about the function impyramid and how I have been dissatisfied with it. (Confession: I designed it.) Today I want to present an alternative approach to creating a multiresolution... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/steve\/2019\/04\/09\/multiresolution-image-pyramids-and-impyramid-part-2\/\">read more >><\/a><\/p>","protected":false},"author":42,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[208,539,504,286,725,252,1179,390,1237,76,156,36,1235,1239,380,575,162,344,532,34,190,52],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/3210"}],"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=3210"}],"version-history":[{"count":2,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/3210\/revisions"}],"predecessor-version":[{"id":3222,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/3210\/revisions\/3222"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media?parent=3210"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/categories?post=3210"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/tags?post=3210"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}