<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Cris’ Image Analysis Blog - algorithms</title><link href="https://www.crisluengo.net/" rel="alternate"></link><link href="https://www.crisluengo.net/feeds/algorithms.atom.xml" rel="self"></link><id>https://www.crisluengo.net/</id><updated>2026-02-10T13:27:00-07:00</updated><subtitle>theory, methods, algorithms, applications</subtitle><entry><title>Robust estimation of shift, scale and rotation</title><link href="https://www.crisluengo.net/archives/1148" rel="alternate"></link><published>2026-02-10T13:27:00-07:00</published><updated>2026-02-10T13:27:00-07:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2026-02-10:/archives/1148</id><summary type="html">&lt;p&gt;A frequent problem in image processing is aligning two or more images together. Maybe you have a series of
images that partially overlap and need to stitch them into a single, larger image. Maybe you have a
video sequence and need to estimate and remove the movement. Maybe you have …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A frequent problem in image processing is aligning two or more images together. Maybe you have a series of
images that partially overlap and need to stitch them into a single, larger image. Maybe you have a
video sequence and need to estimate and remove the movement. Maybe you have images of the same subject
but in different modalities that you need to combine. In all these cases, the transformation you need
to apply to one image to match it to the other is only translation (or shift), rotation, and maybe scaling.
(You might also need to apply a correction for lense distortion, but that is outside the scope of this
post.)&lt;/p&gt;
&lt;p&gt;The most common methods for finding the alignment are all based on markers: First you find a set of distinct
points (or markers) in the two images (corners, dots, etc.), then you find the correspondences (figure out which marker
in image A corresponds to each marker in image B), then you compute the transformation from those correspondences.
And this often works well. The hardest part here is finding the correspondences. When fitting the transformation,
some number of mismatches can be accounted for, but most matches need to be correct. &lt;/p&gt;
&lt;p&gt;Here I wanted to explain a simpler method to align two images, that is not as widely taught in image processing
courses as it should be. It is an efficient, accurate and robust method that directly compares the full images,
rather than individual markers. It is based on the cross-correlation, which can be used to find the shift between
two images, and the &lt;em&gt;Fourier-Mellin&lt;/em&gt; transform, which disregards translation, and converts scaling and rotation
to shifts. This method is due to &lt;a href="https://doi.org/10.1109/83.506761"&gt;Reddy and Chatterji (1996)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s explore the method step by step, and explain concepts as we go along.&lt;/p&gt;
&lt;h2&gt;The Fourier Transform&lt;/h2&gt;
&lt;p&gt;The first step in the method is to compute the magnitude of the Fourier Transform (FT). We&amp;rsquo;re actually computing
the Discrete Fourier Transform (DFT), which is distinct but strongly related to the Fourier Transform, which is
defined in the continuous domain. The DFT is computed efficiently using the Fast Fourier Transform algorithm (FFT).
But we&amp;rsquo;ll refer to it simply as the FT. &lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll assume we all know what the FT is, but I&amp;rsquo;ll summarize really quickly here: The FT is a reversible transformation
of the image that is useful to study linear systems. The FT maps a complex-valued function to another complex-valued
function. Complex values can be interpreted as having a real and an imaginary component, or, equivalently, a magnitude
and a phase.&lt;/p&gt;
&lt;p&gt;A shift in the image is encoded solely in the phase of the FT (images on the right are the magnitude of the FT of the
images on their left):&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="xxx" src="/images/align_fourier_mellin_shift.png"&gt;&lt;/p&gt;
&lt;p&gt;So, taking the magnitude of the FT, we&amp;rsquo;ve removed the shift from the problem. But what happened to the scaling and
the rotation?
A scaling of the image results in an inverse scaling of the FT:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="xxx" src="/images/align_fourier_mellin_scale.png"&gt;&lt;/p&gt;
&lt;p&gt;And a rotation of the image results in an equal rotation of the FT:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="xxx" src="/images/align_fourier_mellin_rotate.png"&gt;&lt;/p&gt;
&lt;p&gt;However, because the input image was real-valued, the FT is symmetric about the origin, and therefore we conflated
the rotation with &amp;phi; and the rotation with &amp;phi;+&amp;pi;. We are no longer able to distinguish a rotation of 180&amp;deg;.&lt;/p&gt;
&lt;p&gt;After this first transformation, we have simplified the problem of estimating shift, rotation and scaling
to a problem of estimating only rotation and scaling. Next, we apply a log-polar mapping. This is a mapping where
the image is deformed in a particular way, resulting in the x-axis of the output being the logarithm of the distance
to the center of the input image, and the y-axis being the angle around this center:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="xxx" src="/images/align_fourier_mellin_log_polar_transform.png"&gt;&lt;/p&gt;
&lt;p&gt;A rotation around the center of the image corresponds to a vertical shift of the log-polar mapped image. And a scaling
will correspond to a horizontal shift of the log-polar mapped image. We thus have converted the rotation and scaling
to a simple translation. This is the &lt;a href="https://en.wikipedia.org/wiki/Mellin_transform"&gt;Mellin transform&lt;/a&gt; part of the
Fourier-Mellin transform. &lt;/p&gt;
&lt;h2&gt;Cross-Correlation&lt;/h2&gt;
&lt;p&gt;Finding a shift between two images is fairly straight-forward using the cross-correlation. In short, we multiply
the two images together and compute the sum of the result, yielding a single value. We do this for every possible
shift of one image w.r.t. the other. This can be computed efficiently using the FFT. The shift for which the
cross-correlation function is largest is the shift that makes the two images most similar.
By fitting a parabola to the 3&amp;times;3 neighborhood of the pixel with the largest value, we can determine its
location with sub-pixel precision.&lt;/p&gt;
&lt;p&gt;There are different ways to normalize the cross-correlation to increase the sharpness of the peak, and thus improve
the localization accuracy. To learn more about these normalization methods,
see &lt;a href="https://diplib.org/diplib-docs/analysis.html#dip-CrossCorrelationFT-Image-CL-Image-CL-Image-L-String-CL-String-CL-String-CL-String-CL"&gt;the DIPlib documentation for &lt;code&gt;dip::CrossCorrelationFT()&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Once we found the shift in the log-polar mapped FT image, we can calculate the rotation and scaling for the one image
to match the other. Except that we still need to disambiguate the 180&amp;deg; rotation!&lt;/p&gt;
&lt;h2&gt;What about the shift?&lt;/h2&gt;
&lt;p&gt;So now we can transform one of the two images to match the other with only a shift and a possible 180&amp;deg; rotation left.
We can compute this partially-aligned image, then use the cross-correlation again to find this missing shift.
We can apply this cross-correlation twice: once with the transformed image, and once with the transformed image rotated
by 180&amp;deg;. One of these two will yield a higher cross-correlation peak, because it will match the other image better.&lt;/p&gt;
&lt;h2&gt;Example&lt;/h2&gt;
&lt;p&gt;An example will make all of the above more clear. I&amp;rsquo;ll be using DIPlib in Python.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say we have these two input images, &lt;code&gt;img_a&lt;/code&gt; and &lt;code&gt;img_b&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;diplib&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;dip&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;np&lt;/span&gt;

&lt;span class="n"&gt;img_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ImageRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;image A&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;img_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ImageRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;image B&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p class="centering"&gt;&lt;img alt="xxx" src="/images/align_fourier_mellin_inputs.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;img_b&lt;/code&gt; (right) is a crop of some original image. &lt;code&gt;img_a&lt;/code&gt; (left) is a different crop obtained after rotating
the original image clockwise by &amp;pi;/8 radian and scaling it by a factor of 0.7.&lt;/p&gt;
&lt;p&gt;For simplicity, we&amp;rsquo;ll consider only the green channel, which has good contrast across the image:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;gray_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;img_a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;gray_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;img_b&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We can apply the method to the color images, but we won&amp;rsquo;t gain anything. After finding the transformation needed
to align the gray-scale images, we can apply that transformation to the color image.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll start by computing the FTs of the two images, after applying a windowing function. We discard
the phase, and apply a log mapping to improve local contrast:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;ft_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FourierTransform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplyWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gray_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;GaussianTukey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;ft_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FourierTransform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplyWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gray_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;GaussianTukey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;lm_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ft_a&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;lm_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ft_b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p class="centering"&gt;&lt;img alt="xxx" src="/images/align_fourier_mellin_fts.png"&gt;&lt;/p&gt;
&lt;p&gt;The windowing function is important because without it, the FT will be corrupted by the edge effects. Image edges
usually show up as a sharp horizontal and vertical line through the origin. These two lines will cause a peak in
the cross-correlation at the origin, which will be identical for the two images, giving us a chance to erroneously
find a shift of 0. &lt;/p&gt;
&lt;p&gt;Note also that &lt;code&gt;dip.FourierTransform()&lt;/code&gt; computes the full DFT with the origin in the middle of the output image.
When using a different FFT implementation, you&amp;rsquo;ll need to apply a function such as &lt;code&gt;np.fft.fftshift()&lt;/code&gt; to get the
origin to be in the middle of the image.&lt;/p&gt;
&lt;p&gt;Next, we apply the log-polar transformation to the two images:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;lpt_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LogPolarTransform2D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lm_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;linear&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;lpt_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LogPolarTransform2D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lm_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;linear&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p class="centering"&gt;&lt;img alt="xxx" src="/images/align_fourier_mellin_lpts.png"&gt;&lt;/p&gt;
&lt;p&gt;The cross-correlation between these two images gives a sharp peak, whose location gives the shift:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;find_shift_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;NCC&amp;quot;&lt;/span&gt;  &lt;span class="c1"&gt;# or &amp;quot;PC&amp;quot; or &amp;quot;CC&amp;quot;, depending on which normalization you want to use&lt;/span&gt;
&lt;span class="n"&gt;shift&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FindShift&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lpt_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lpt_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;find_shift_method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;LPT shift = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;shift&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We get a shift of -32.67 along the x-axis and 224.05 along the y-axis. From that we can calculate the rotation
and scaling, simply by inverting the computations applied in the log-polar transform:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;maxr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gray_a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sizes&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lpt_a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;maxr&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;theta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;  &lt;span class="c1"&gt;# 180 degree ambiguity!&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;scale = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, theta = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We get a scale of 0.7015 and a rotation of -2.749 radian. We expected a scale of 0.7000, and a rotation of
0.3927 (&amp;pi;/8) radian. -2.749 is &amp;pi;/8 - &amp;pi;, so we are off by exactly 180&amp;deg;. Remember that with the FT,
we cannot distinguish between these two rotations. We happen to have found the wrong rotation, but fret not,
we&amp;rsquo;ll fix this soon.&lt;/p&gt;
&lt;p&gt;We can apply the scale and rotation to the image, which will leave us with a shifted (and rotated by 180&amp;deg;)
version of the final result:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;matrix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;gray_b_transformed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AffineTransform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gray_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;linear&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p class="centering"&gt;&lt;img alt="xxx" src="/images/align_fourier_mellin_corrected_intermediate.png"&gt;&lt;/p&gt;
&lt;p&gt;Using this transformed image, we can figure out what shift to apply using cross-correlation again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;cc_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;CC&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;don&amp;#39;t normalize&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;NCC&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;normalize&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;PC&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;phase&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}[&lt;/span&gt;&lt;span class="n"&gt;find_shift_method&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;cross&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CrossCorrelationFT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gray_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gray_b_transformed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cc_method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;loc_1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubpixelLocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaximumPixel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;loc_1: coordinates = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;loc_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coordinates&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, value = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;loc_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But because we have two possible rotations, we should compute the cross-correlation with &lt;code&gt;gray_b_transformed&lt;/code&gt; and
also with that image rotated by 180&amp;deg;. The height of the peak will tell us which of the two rotations is the
correct one (a larger cross-correlation result means the images are more similar).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;gray_b_transformed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Rotation90&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cross&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CrossCorrelationFT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gray_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gray_b_transformed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cc_method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;loc_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubpixelLocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaximumPixel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;loc_2: coordinates = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;loc_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coordinates&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, value = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;loc_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;loc_1&lt;/code&gt; has a &lt;code&gt;value&lt;/code&gt; of 0.0261, and &lt;code&gt;loc_2&lt;/code&gt; of 0.2920. It is clear that the rotation used for &lt;code&gt;loc_2&lt;/code&gt; is the better
one. The peak for &lt;code&gt;loc_2&lt;/code&gt; is at (197.78, 344.04). The shift we&amp;rsquo;re looking for is the distance from the origin (the
middle of the image) to this peak:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;loc_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;loc_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# We use theta as the rotation&lt;/span&gt;
    &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loc_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loc_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# We use theta + 180 as the rotation&lt;/span&gt;
    &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loc_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loc_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Using this transformation matrix, we can finally compute the transformed version of &lt;code&gt;img_b&lt;/code&gt; that matches &lt;code&gt;img_a&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AffineTransform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;linear&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p class="centering"&gt;&lt;img alt="xxx" src="/images/align_fourier_mellin_corrected_final.png"&gt;&lt;/p&gt;
&lt;p&gt;This whole process is implemented in DIPlib as &lt;a href="https://diplib.org/diplib-docs/analysis.html#dip-FourierMellinMatch2D-Image-CL-Image-CL-Image-L-String-CL-String-CL"&gt;&lt;code&gt;dip::FourierMellinMatch2D()&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p&gt;You can find the complete code I used to generate all the images in this post &lt;a href="/code/align_fourier_mellin.py"&gt;right here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;</content><category term="algorithms"></category><category term="Fourier-Mellin"></category><category term="Fourier transform"></category><category term="cross-correlation"></category><category term="alignment"></category><category term="registration"></category><category term="scale"></category><category term="rotation"></category><category term="shift"></category></entry><entry><title>Median filtering</title><link href="https://www.crisluengo.net/archives/1138" rel="alternate"></link><published>2022-06-27T00:00:00-06:00</published><updated>2022-06-27T00:00:00-06:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2022-06-27:/archives/1138</id><summary type="html">&lt;p&gt;The median filter is a non-liner smoothing (blurring) filter where an output pixel is
the median value of its neighborhood in the input image. It can be generalized to a percentile filter,
where the median is the 50&lt;sup&gt;th&lt;/sup&gt; percentile.&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="Result of applying percentile filter with varying parameter" src="/images/median_filter_examples.jpg"&gt;&lt;/p&gt;
&lt;p class="caption"&gt;Noisy &amp;ldquo;trui&amp;rdquo; image filtered with a percentile filter, from left …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The median filter is a non-liner smoothing (blurring) filter where an output pixel is
the median value of its neighborhood in the input image. It can be generalized to a percentile filter,
where the median is the 50&lt;sup&gt;th&lt;/sup&gt; percentile.&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="Result of applying percentile filter with varying parameter" src="/images/median_filter_examples.jpg"&gt;&lt;/p&gt;
&lt;p class="caption"&gt;Noisy &amp;ldquo;trui&amp;rdquo; image filtered with a percentile filter, from left to right: 100&lt;sup&gt;th&lt;/sup&gt; percentile (dilation),
95&lt;sup&gt;th&lt;/sup&gt; percentile, 75&lt;sup&gt;th&lt;/sup&gt; percentile, 50&lt;sup&gt;th&lt;/sup&gt; percentile (median), 25&lt;sup&gt;th&lt;/sup&gt; percentile, 5&lt;sup&gt;th&lt;/sup&gt; percentile,
0&lt;sup&gt;th&lt;/sup&gt; percentile (erosion).&lt;/p&gt;
&lt;p&gt;In Mathematical Morphology one talks about a rank filter. Mathematical Morphology is based on the maximum
and minimum values of sets (actually the supremum and infimum values, but the difference is just a technicality).
For noisy images, it sometimes is advantageous to compute the maximum or minimum ignoring a few values, in fact
computing a rank order statistic. So a rank 3 filter is like a maximum filter (a dilation), but ignoring the two
largest values in the neighborhood. The equivalence between the rank filter and the percentile filter is obvious.&lt;/p&gt;
&lt;p&gt;In this blog post I want to discuss implementations of the median filter (and, by extension, the percentile
and rank filters). Because computing the median is a non-linear operation, we cannot use the Fourier transform
to aid us. There is also no way to split up the median computation.
We can compute the mean of a set by dividing up the set into smaller sets, compute the mean of each subset,
then compute the mean of the means. We can do the same for the maximum or minimum computation. But for the
median this is not possible. This means that reducing the algorithmic complexity of the median filter is not
a trivial exercise.&lt;/p&gt;
&lt;h2&gt;Naive algorithm&lt;/h2&gt;
&lt;p&gt;The basic algorithm to compute a median filter would be to compute the median over the neighborhood independently
for each output pixel. This is the algorithm implemented in scikit-image (which actually uses the implementation
in SciPy&amp;rsquo;s ndimage), as well as the one in DIPlib up to version 3.3.0 (which is the current release, the next
release of DIPlib will include a more efficient algorithm, discussed later).&lt;/p&gt;
&lt;p&gt;Computing the median cannot be done in-place. One needs to sort the values, so the simplest approach is to copy
them over into a temporary buffer, sort the buffer, and then find the median value. Sorting &lt;span class="math"&gt;&lt;svg style="width: 0.565em; height: 0.536em; vertical-align: -0.011em; " viewBox=".06 -5.25 5.65 5.36"&gt;
&lt;title&gt;
\(n\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq1-g1-61' d='M5.65-1.24L5.48-1.39C5.4-1.29 5.31-1.18 5.23-1.08C4.88-.64 4.67-.45 4.5-.45C4.39-.45 4.31-.54 4.31-.64C4.31-.74 4.36-.96 4.48-1.39L5.16-3.85C5.23-4.1 5.26-4.36 5.26-4.53C5.26-4.95 4.94-5.25 4.48-5.25C3.72-5.25 2.97-4.53 1.74-2.63L2.54-5.23L2.5-5.25L.57-4.88V-4.69C1.18-4.68 1.33-4.61 1.33-4.37C1.33-4.3 1.32-4.23 1.31-4.17L.17 0H1.06C1.62-1.88 1.73-2.14 2.25-2.95C2.97-4.06 3.57-4.65 4.01-4.65C4.19-4.65 4.3-4.51 4.3-4.3C4.3-4.16 4.23-3.76 4.13-3.39L3.61-1.43C3.45-.81 3.42-.66 3.42-.54C3.42-.08 3.59 .11 3.98 .11C4.51 .11 4.81-.13 5.65-1.24Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq1-page1'&gt;
&lt;use x='.06' y='0' xlink:href='#eq1-g1-61'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; values takes
&lt;span class="math"&gt;&lt;svg style="width: 4.911em; height: 1.114em; vertical-align: -0.260em; " viewBox=".3 -8.54 49.11 11.14"&gt;
&lt;title&gt;
\(\mathcal{O}(n \log n)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq2-g8-103' d='M5.6-4.62V-5.09H4.68C4.44-5.09 4.26-5.12 4.03-5.2L3.76-5.3C3.44-5.42 3.12-5.48 2.81-5.48C1.7-5.48 .82-4.62 .82-3.54C.82-2.79 1.14-2.33 1.93-1.94L1.42-1.46C1.02-1.12 .87-.88 .87-.64C.87-.39 1.01-.25 1.5-.01C.66 .61 .33 1 .33 1.44C.33 2.07 1.26 2.6 2.39 2.6C3.29 2.6 4.22 2.29 4.84 1.79C5.29 1.42 5.49 1.04 5.49 .58C5.49-.15 4.93-.66 4.05-.69L2.51-.76C1.88-.79 1.58-.89 1.58-1.08C1.58-1.32 1.98-1.74 2.3-1.83C2.41-1.82 2.49-1.81 2.53-1.81C2.75-1.79 2.91-1.77 2.98-1.77C3.42-1.77 3.89-1.95 4.26-2.27C4.66-2.61 4.84-3.03 4.84-3.62C4.84-3.97 4.78-4.24 4.61-4.62H5.6ZM5.16 .76C5.16 1.45 4.25 1.92 2.91 1.92C1.86 1.92 1.17 1.57 1.17 1.05C1.17 .77 1.25 .62 1.75 .02C2.14 .11 3.1 .18 3.68 .18C4.76 .18 5.16 .33 5.16 .76ZM3.92-3.16C3.92-2.49 3.57-2.07 3.03-2.07C2.31-2.07 1.81-2.85 1.81-3.99V-4.03C1.81-4.73 2.14-5.15 2.69-5.15C3.06-5.15 3.37-4.94 3.56-4.59C3.78-4.17 3.92-3.62 3.92-3.16Z'/&gt;
&lt;path id='eq2-g8-108' d='M3.06 0V-.18C2.3-.23 2.17-.35 2.17-1V-8.11L2.12-8.13C1.5-7.93 1.05-7.81 .23-7.61V-7.42H.3C.43-7.43 .57-7.44 .67-7.44C1.05-7.44 1.17-7.28 1.17-6.72V-1.04C1.17-.39 1-.24 .25-.18V0H3.06Z'/&gt;
&lt;path id='eq2-g8-111' d='M5.6-2.79C5.6-4.35 4.5-5.48 2.98-5.48C1.43-5.48 .35-4.34 .35-2.69C.35-1.08 1.45 .12 2.95 .12S5.6-1.14 5.6-2.79ZM4.53-2.37C4.53-1.02 3.99-.21 3.1-.21C2.63-.21 2.19-.5 1.94-.98C1.61-1.6 1.42-2.43 1.42-3.28C1.42-4.41 1.98-5.15 2.82-5.15C3.82-5.15 4.53-4 4.53-2.37Z'/&gt;
&lt;path id='eq2-g4-61' d='M5.65-1.24L5.48-1.39C5.4-1.29 5.31-1.18 5.23-1.08C4.88-.64 4.67-.45 4.5-.45C4.39-.45 4.31-.54 4.31-.64C4.31-.74 4.36-.96 4.48-1.39L5.16-3.85C5.23-4.1 5.26-4.36 5.26-4.53C5.26-4.95 4.94-5.25 4.48-5.25C3.72-5.25 2.97-4.53 1.74-2.63L2.54-5.23L2.5-5.25L.57-4.88V-4.69C1.18-4.68 1.33-4.61 1.33-4.37C1.33-4.3 1.32-4.23 1.31-4.17L.17 0H1.06C1.62-1.88 1.73-2.14 2.25-2.95C2.97-4.06 3.57-4.65 4.01-4.65C4.19-4.65 4.3-4.51 4.3-4.3C4.3-4.16 4.23-3.76 4.13-3.39L3.61-1.43C3.45-.81 3.42-.66 3.42-.54C3.42-.08 3.59 .11 3.98 .11C4.51 .11 4.81-.13 5.65-1.24Z'/&gt;
&lt;path id='eq2-g1-79' d='M8.29-5.48C8.29-7.02 7.34-8.52 5.67-8.52C4.94-8.52 4.2-8.33 3.66-7.84C3.94-7.9 4.23-7.91 4.53-7.91C5.94-7.91 7.12-7.13 7.12-5.61C7.12-3.78 5.42-.2 3.29-.2C2.02-.2 1.39-1.37 1.39-2.5C1.39-4.54 3.1-7.06 5.22-7.35C4.9-7.5 4.59-7.53 4.23-7.53C2.18-7.53 .08-4.6 .08-2.69C.08-.73 1.41 .3 3.29 .3C6.21 .3 8.29-2.78 8.29-5.48Z'/&gt;
&lt;path id='eq2-g1-185' d='M3.51-8.54C1.79-7.42 .57-5.49 .57-3.06C.57-.85 1.83 1.39 3.48 2.41L3.62 2.22C2.05 .98 1.6-.46 1.6-3.1C1.6-5.74 2.08-7.11 3.62-8.35L3.51-8.54Z'/&gt;
&lt;path id='eq2-g1-186' d='M.45-8.54L.35-8.35C1.88-7.11 2.37-5.74 2.37-3.1C2.37-.46 1.92 .98 .35 2.22L.49 2.41C2.13 1.39 3.39-.85 3.39-3.06C3.39-5.49 2.18-7.42 .45-8.54Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq2-page1'&gt;
&lt;use x='.3' y='0' xlink:href='#eq2-g1-79'/&gt;
&lt;use x='9.91' y='0' xlink:href='#eq2-g1-185'/&gt;
&lt;use x='14.43' y='0' xlink:href='#eq2-g4-61'/&gt;
&lt;use x='22.56' y='0' xlink:href='#eq2-g8-108'/&gt;
&lt;use x='25.88' y='0' xlink:href='#eq2-g8-111'/&gt;
&lt;use x='31.86' y='0' xlink:href='#eq2-g8-103'/&gt;
&lt;use x='39.89' y='0' xlink:href='#eq2-g4-61'/&gt;
&lt;use x='46.02' y='0' xlink:href='#eq2-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;. We can speed up the process by not sorting the array fully, just enough to get the median value
in its place. For example, using &lt;a href="https://en.wikipedia.org/wiki/Quicksort"&gt;the Quicksort algorithm&lt;/a&gt; for sorting,
we can always skip one of the two sub-arrays. This is called partial sorting, and in C++ is implemented as
&lt;a href="https://en.cppreference.com/w/cpp/algorithm/nth_element"&gt;&lt;code&gt;std::nth_element&lt;/code&gt;&lt;/a&gt;. Partial sorting has a complexity
of &lt;span class="math"&gt;&lt;svg style="width: 2.366em; height: 1.095em; vertical-align: -0.241em; " viewBox=".3 -8.54 23.66 10.95"&gt;
&lt;title&gt;
\(\mathcal{O}(n)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq3-g4-61' d='M5.65-1.24L5.48-1.39C5.4-1.29 5.31-1.18 5.23-1.08C4.88-.64 4.67-.45 4.5-.45C4.39-.45 4.31-.54 4.31-.64C4.31-.74 4.36-.96 4.48-1.39L5.16-3.85C5.23-4.1 5.26-4.36 5.26-4.53C5.26-4.95 4.94-5.25 4.48-5.25C3.72-5.25 2.97-4.53 1.74-2.63L2.54-5.23L2.5-5.25L.57-4.88V-4.69C1.18-4.68 1.33-4.61 1.33-4.37C1.33-4.3 1.32-4.23 1.31-4.17L.17 0H1.06C1.62-1.88 1.73-2.14 2.25-2.95C2.97-4.06 3.57-4.65 4.01-4.65C4.19-4.65 4.3-4.51 4.3-4.3C4.3-4.16 4.23-3.76 4.13-3.39L3.61-1.43C3.45-.81 3.42-.66 3.42-.54C3.42-.08 3.59 .11 3.98 .11C4.51 .11 4.81-.13 5.65-1.24Z'/&gt;
&lt;path id='eq3-g1-79' d='M8.29-5.48C8.29-7.02 7.34-8.52 5.67-8.52C4.94-8.52 4.2-8.33 3.66-7.84C3.94-7.9 4.23-7.91 4.53-7.91C5.94-7.91 7.12-7.13 7.12-5.61C7.12-3.78 5.42-.2 3.29-.2C2.02-.2 1.39-1.37 1.39-2.5C1.39-4.54 3.1-7.06 5.22-7.35C4.9-7.5 4.59-7.53 4.23-7.53C2.18-7.53 .08-4.6 .08-2.69C.08-.73 1.41 .3 3.29 .3C6.21 .3 8.29-2.78 8.29-5.48Z'/&gt;
&lt;path id='eq3-g1-185' d='M3.51-8.54C1.79-7.42 .57-5.49 .57-3.06C.57-.85 1.83 1.39 3.48 2.41L3.62 2.22C2.05 .98 1.6-.46 1.6-3.1C1.6-5.74 2.08-7.11 3.62-8.35L3.51-8.54Z'/&gt;
&lt;path id='eq3-g1-186' d='M.45-8.54L.35-8.35C1.88-7.11 2.37-5.74 2.37-3.1C2.37-.46 1.92 .98 .35 2.22L.49 2.41C2.13 1.39 3.39-.85 3.39-3.06C3.39-5.49 2.18-7.42 .45-8.54Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq3-page1'&gt;
&lt;use x='.3' y='0' xlink:href='#eq3-g1-79'/&gt;
&lt;use x='9.91' y='0' xlink:href='#eq3-g1-185'/&gt;
&lt;use x='14.43' y='0' xlink:href='#eq3-g4-61'/&gt;
&lt;use x='20.56' y='0' xlink:href='#eq3-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; on average.&lt;/p&gt;
&lt;p&gt;For integer values, sorting complexity is &lt;span class="math"&gt;&lt;svg style="width: 2.366em; height: 1.095em; vertical-align: -0.241em; " viewBox=".3 -8.54 23.66 10.95"&gt;
&lt;title&gt;
\(\mathcal{O}(n)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq4-g4-61' d='M5.65-1.24L5.48-1.39C5.4-1.29 5.31-1.18 5.23-1.08C4.88-.64 4.67-.45 4.5-.45C4.39-.45 4.31-.54 4.31-.64C4.31-.74 4.36-.96 4.48-1.39L5.16-3.85C5.23-4.1 5.26-4.36 5.26-4.53C5.26-4.95 4.94-5.25 4.48-5.25C3.72-5.25 2.97-4.53 1.74-2.63L2.54-5.23L2.5-5.25L.57-4.88V-4.69C1.18-4.68 1.33-4.61 1.33-4.37C1.33-4.3 1.32-4.23 1.31-4.17L.17 0H1.06C1.62-1.88 1.73-2.14 2.25-2.95C2.97-4.06 3.57-4.65 4.01-4.65C4.19-4.65 4.3-4.51 4.3-4.3C4.3-4.16 4.23-3.76 4.13-3.39L3.61-1.43C3.45-.81 3.42-.66 3.42-.54C3.42-.08 3.59 .11 3.98 .11C4.51 .11 4.81-.13 5.65-1.24Z'/&gt;
&lt;path id='eq4-g1-79' d='M8.29-5.48C8.29-7.02 7.34-8.52 5.67-8.52C4.94-8.52 4.2-8.33 3.66-7.84C3.94-7.9 4.23-7.91 4.53-7.91C5.94-7.91 7.12-7.13 7.12-5.61C7.12-3.78 5.42-.2 3.29-.2C2.02-.2 1.39-1.37 1.39-2.5C1.39-4.54 3.1-7.06 5.22-7.35C4.9-7.5 4.59-7.53 4.23-7.53C2.18-7.53 .08-4.6 .08-2.69C.08-.73 1.41 .3 3.29 .3C6.21 .3 8.29-2.78 8.29-5.48Z'/&gt;
&lt;path id='eq4-g1-185' d='M3.51-8.54C1.79-7.42 .57-5.49 .57-3.06C.57-.85 1.83 1.39 3.48 2.41L3.62 2.22C2.05 .98 1.6-.46 1.6-3.1C1.6-5.74 2.08-7.11 3.62-8.35L3.51-8.54Z'/&gt;
&lt;path id='eq4-g1-186' d='M.45-8.54L.35-8.35C1.88-7.11 2.37-5.74 2.37-3.1C2.37-.46 1.92 .98 .35 2.22L.49 2.41C2.13 1.39 3.39-.85 3.39-3.06C3.39-5.49 2.18-7.42 .45-8.54Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq4-page1'&gt;
&lt;use x='.3' y='0' xlink:href='#eq4-g1-79'/&gt;
&lt;use x='9.91' y='0' xlink:href='#eq4-g1-185'/&gt;
&lt;use x='14.43' y='0' xlink:href='#eq4-g4-61'/&gt;
&lt;use x='20.56' y='0' xlink:href='#eq4-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;, but there is no reduction in complexity for partial
sort in this case.&lt;/p&gt;
&lt;p&gt;For a square 2D kernel of &lt;span class="math"&gt;&lt;svg style="width: 2.516em; height: 0.827em; vertical-align: -0.014em; " viewBox=".49 -8.13 25.16 8.27"&gt;
&lt;title&gt;
\(k \times k\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq5-g1-2' d='M6.91-.42L4.25-3.06L6.91-5.72L6.43-6.19L3.79-3.54L1.14-6.19L.67-5.72L3.31-3.06L.67-.42L1.14 .06L3.79-2.6L6.43 .06L6.91-.42Z'/&gt;
&lt;path id='eq5-g4-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq5-page1'&gt;
&lt;use x='.49' y='0' xlink:href='#eq5-g4-58'/&gt;
&lt;use x='9.41' y='0' xlink:href='#eq5-g1-2'/&gt;
&lt;use x='20.16' y='0' xlink:href='#eq5-g4-58'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; pixels, this implementation thus costs &lt;span class="math"&gt;&lt;svg style="width: 2.910em; height: 1.265em; vertical-align: -0.241em; " viewBox=".3 -10.24 29.1 12.65"&gt;
&lt;title&gt;
\(\mathcal{O}(k^2)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq6-g8-50' d='M4.15-1.2L4.04-1.24C3.71-.74 3.6-.66 3.21-.66H1.12L2.59-2.2C3.36-3.01 3.7-3.68 3.7-4.36C3.7-5.23 3-5.9 2.09-5.9C1.61-5.9 1.15-5.71 .83-5.36C.55-5.07 .42-4.79 .27-4.17L.45-4.12C.8-4.98 1.12-5.26 1.72-5.26C2.45-5.26 2.95-4.76 2.95-4.03C2.95-3.35 2.55-2.53 1.82-1.76L.26-.1V0H3.67L4.15-1.2Z'/&gt;
&lt;path id='eq6-g4-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;path id='eq6-g1-79' d='M8.29-5.48C8.29-7.02 7.34-8.52 5.67-8.52C4.94-8.52 4.2-8.33 3.66-7.84C3.94-7.9 4.23-7.91 4.53-7.91C5.94-7.91 7.12-7.13 7.12-5.61C7.12-3.78 5.42-.2 3.29-.2C2.02-.2 1.39-1.37 1.39-2.5C1.39-4.54 3.1-7.06 5.22-7.35C4.9-7.5 4.59-7.53 4.23-7.53C2.18-7.53 .08-4.6 .08-2.69C.08-.73 1.41 .3 3.29 .3C6.21 .3 8.29-2.78 8.29-5.48Z'/&gt;
&lt;path id='eq6-g1-185' d='M3.51-8.54C1.79-7.42 .57-5.49 .57-3.06C.57-.85 1.83 1.39 3.48 2.41L3.62 2.22C2.05 .98 1.6-.46 1.6-3.1C1.6-5.74 2.08-7.11 3.62-8.35L3.51-8.54Z'/&gt;
&lt;path id='eq6-g1-186' d='M.45-8.54L.35-8.35C1.88-7.11 2.37-5.74 2.37-3.1C2.37-.46 1.92 .98 .35 2.22L.49 2.41C2.13 1.39 3.39-.85 3.39-3.06C3.39-5.49 2.18-7.42 .45-8.54Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq6-page1'&gt;
&lt;use x='.3' y='0' xlink:href='#eq6-g1-79'/&gt;
&lt;use x='9.91' y='0' xlink:href='#eq6-g1-185'/&gt;
&lt;use x='14.86' y='0' xlink:href='#eq6-g4-58'/&gt;
&lt;use x='21.12' y='-4.34' xlink:href='#eq6-g8-50'/&gt;
&lt;use x='26.01' y='0' xlink:href='#eq6-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; per pixel.
This is actually a quite efficient algorithm for small kernels, but because of the poor scaling, gets quite expensive
for larger ones.&lt;/p&gt;
&lt;h2&gt;Histogram-based algorithm&lt;/h2&gt;
&lt;p&gt;Because the median computation cannot be split up, as I mentioned earlier, the only way to reduce the algorithmic
complexity of the filter is to somehow update the data in the temporary buffer (remove the pixels exiting the kernel as
it is shifted over one pixel, and add the pixels entering), and do so in a manner that makes finding the median
cheaper, in a manner that allows re-using the earlier work.&lt;/p&gt;
&lt;p&gt;In the case of 8-bit images, sorting can be done efficiently by computing a histogram of the values to be sorted.
Finding the median in the histogram involves summing up the bins of the histogram, the bin where this sum reaches
&lt;span class="math"&gt;&lt;svg style="width: 2.230em; height: 1.254em; vertical-align: -0.230em; " viewBox=".49 -10.24 22.3 12.54"&gt;
&lt;title&gt;
\(k^2/2\)
&lt;/title&gt;
&lt;defs&gt;
&lt;use id='eq7-g11-50' xlink:href='#eq7-g8-50' transform='scale(1.36)'/&gt;
&lt;path id='eq7-g1-157' d='M.74 2.3H1.36L4.74-8.42H4.12L.74 2.3Z'/&gt;
&lt;path id='eq7-g8-50' d='M4.15-1.2L4.04-1.24C3.71-.74 3.6-.66 3.21-.66H1.12L2.59-2.2C3.36-3.01 3.7-3.68 3.7-4.36C3.7-5.23 3-5.9 2.09-5.9C1.61-5.9 1.15-5.71 .83-5.36C.55-5.07 .42-4.79 .27-4.17L.45-4.12C.8-4.98 1.12-5.26 1.72-5.26C2.45-5.26 2.95-4.76 2.95-4.03C2.95-3.35 2.55-2.53 1.82-1.76L.26-.1V0H3.67L4.15-1.2Z'/&gt;
&lt;path id='eq7-g4-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq7-page1'&gt;
&lt;use x='.49' y='0' xlink:href='#eq7-g4-58'/&gt;
&lt;use x='6.75' y='-4.34' xlink:href='#eq7-g8-50'/&gt;
&lt;use x='11.64' y='0' xlink:href='#eq7-g1-157'/&gt;
&lt;use x='17.14' y='0' xlink:href='#eq7-g11-50'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; is the median.
Updating the histogram as we shift the kernel by one pixel costs &lt;span class="math"&gt;&lt;svg style="width: 2.422em; height: 1.095em; vertical-align: -0.241em; " viewBox=".3 -8.54 24.22 10.95"&gt;
&lt;title&gt;
\(\mathcal{O}(k)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq8-g4-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;path id='eq8-g1-79' d='M8.29-5.48C8.29-7.02 7.34-8.52 5.67-8.52C4.94-8.52 4.2-8.33 3.66-7.84C3.94-7.9 4.23-7.91 4.53-7.91C5.94-7.91 7.12-7.13 7.12-5.61C7.12-3.78 5.42-.2 3.29-.2C2.02-.2 1.39-1.37 1.39-2.5C1.39-4.54 3.1-7.06 5.22-7.35C4.9-7.5 4.59-7.53 4.23-7.53C2.18-7.53 .08-4.6 .08-2.69C.08-.73 1.41 .3 3.29 .3C6.21 .3 8.29-2.78 8.29-5.48Z'/&gt;
&lt;path id='eq8-g1-185' d='M3.51-8.54C1.79-7.42 .57-5.49 .57-3.06C.57-.85 1.83 1.39 3.48 2.41L3.62 2.22C2.05 .98 1.6-.46 1.6-3.1C1.6-5.74 2.08-7.11 3.62-8.35L3.51-8.54Z'/&gt;
&lt;path id='eq8-g1-186' d='M.45-8.54L.35-8.35C1.88-7.11 2.37-5.74 2.37-3.1C2.37-.46 1.92 .98 .35 2.22L.49 2.41C2.13 1.39 3.39-.85 3.39-3.06C3.39-5.49 2.18-7.42 .45-8.54Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq8-page1'&gt;
&lt;use x='.3' y='0' xlink:href='#eq8-g1-79'/&gt;
&lt;use x='9.91' y='0' xlink:href='#eq8-g1-185'/&gt;
&lt;use x='14.86' y='0' xlink:href='#eq8-g4-58'/&gt;
&lt;use x='21.12' y='0' xlink:href='#eq8-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; operations, and finding the median
is a fixed number of operations. Thus, for these images, the median filter has a complexity of &lt;span class="math"&gt;&lt;svg style="width: 2.422em; height: 1.095em; vertical-align: -0.241em; " viewBox=".3 -8.54 24.22 10.95"&gt;
&lt;title&gt;
\(\mathcal{O}(k)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq9-g4-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;path id='eq9-g1-79' d='M8.29-5.48C8.29-7.02 7.34-8.52 5.67-8.52C4.94-8.52 4.2-8.33 3.66-7.84C3.94-7.9 4.23-7.91 4.53-7.91C5.94-7.91 7.12-7.13 7.12-5.61C7.12-3.78 5.42-.2 3.29-.2C2.02-.2 1.39-1.37 1.39-2.5C1.39-4.54 3.1-7.06 5.22-7.35C4.9-7.5 4.59-7.53 4.23-7.53C2.18-7.53 .08-4.6 .08-2.69C.08-.73 1.41 .3 3.29 .3C6.21 .3 8.29-2.78 8.29-5.48Z'/&gt;
&lt;path id='eq9-g1-185' d='M3.51-8.54C1.79-7.42 .57-5.49 .57-3.06C.57-.85 1.83 1.39 3.48 2.41L3.62 2.22C2.05 .98 1.6-.46 1.6-3.1C1.6-5.74 2.08-7.11 3.62-8.35L3.51-8.54Z'/&gt;
&lt;path id='eq9-g1-186' d='M.45-8.54L.35-8.35C1.88-7.11 2.37-5.74 2.37-3.1C2.37-.46 1.92 .98 .35 2.22L.49 2.41C2.13 1.39 3.39-.85 3.39-3.06C3.39-5.49 2.18-7.42 .45-8.54Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq9-page1'&gt;
&lt;use x='.3' y='0' xlink:href='#eq9-g1-79'/&gt;
&lt;use x='9.91' y='0' xlink:href='#eq9-g1-185'/&gt;
&lt;use x='14.86' y='0' xlink:href='#eq9-g4-58'/&gt;
&lt;use x='21.12' y='0' xlink:href='#eq9-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;,
much better than the naive implementation.&lt;/p&gt;
&lt;p&gt;Note that this algorithm works for kernels of any shape; &lt;span class="math"&gt;&lt;svg style="width: 0.549em; height: 0.827em; vertical-align: -0.014em; " viewBox=".49 -8.13 5.49 8.27"&gt;
&lt;title&gt;
\(k\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq10-g1-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq10-page1'&gt;
&lt;use x='.49' y='0' xlink:href='#eq10-g1-58'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; is then  the number of pixels that exit and enter
the kernel when shifting it by one pixel. For a compact 2D kernel this is the height of the kernel,
for a non-compact kernel this can be much larger. In 3D it is the height times the depth of the kernel (for example,
for a &lt;span class="math"&gt;&lt;svg style="width: 4.483em; height: 0.827em; vertical-align: -0.014em; " viewBox=".49 -8.13 44.83 8.27"&gt;
&lt;title&gt;
\(k \times k \times k\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq11-g1-2' d='M6.91-.42L4.25-3.06L6.91-5.72L6.43-6.19L3.79-3.54L1.14-6.19L.67-5.72L3.31-3.06L.67-.42L1.14 .06L3.79-2.6L6.43 .06L6.91-.42Z'/&gt;
&lt;path id='eq11-g4-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq11-page1'&gt;
&lt;use x='.49' y='0' xlink:href='#eq11-g4-58'/&gt;
&lt;use x='9.41' y='0' xlink:href='#eq11-g1-2'/&gt;
&lt;use x='20.16' y='0' xlink:href='#eq11-g4-58'/&gt;
&lt;use x='29.08' y='0' xlink:href='#eq11-g1-2'/&gt;
&lt;use x='39.83' y='0' xlink:href='#eq11-g4-58'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; cubic kernel, the complexity is &lt;span class="math"&gt;&lt;svg style="width: 2.910em; height: 1.265em; vertical-align: -0.241em; " viewBox=".3 -10.24 29.1 12.65"&gt;
&lt;title&gt;
\(\mathcal{O}(k^3)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq12-g8-51' d='M3.77-1.91C3.77-2.36 3.63-2.77 3.38-3.04C3.21-3.23 3.04-3.34 2.66-3.5C3.26-3.91 3.48-4.24 3.48-4.71C3.48-5.42 2.92-5.9 2.11-5.9C1.68-5.9 1.29-5.76 .98-5.48C.72-5.24 .59-5.01 .39-4.49L.52-4.45C.88-5.09 1.28-5.38 1.83-5.38C2.39-5.38 2.79-5 2.79-4.45C2.79-4.13 2.66-3.82 2.44-3.6C2.17-3.34 1.93-3.21 1.34-3V-2.88C1.85-2.88 2.05-2.86 2.26-2.79C2.8-2.59 3.14-2.1 3.14-1.49C3.14-.76 2.65-.19 2-.19C1.76-.19 1.59-.25 1.27-.46C1-.62 .86-.68 .71-.68C.51-.68 .38-.56 .38-.38C.38-.07 .75 .12 1.36 .12C2.04 .12 2.73-.1 3.14-.46S3.77-1.33 3.77-1.91Z'/&gt;
&lt;path id='eq12-g4-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;path id='eq12-g1-79' d='M8.29-5.48C8.29-7.02 7.34-8.52 5.67-8.52C4.94-8.52 4.2-8.33 3.66-7.84C3.94-7.9 4.23-7.91 4.53-7.91C5.94-7.91 7.12-7.13 7.12-5.61C7.12-3.78 5.42-.2 3.29-.2C2.02-.2 1.39-1.37 1.39-2.5C1.39-4.54 3.1-7.06 5.22-7.35C4.9-7.5 4.59-7.53 4.23-7.53C2.18-7.53 .08-4.6 .08-2.69C.08-.73 1.41 .3 3.29 .3C6.21 .3 8.29-2.78 8.29-5.48Z'/&gt;
&lt;path id='eq12-g1-185' d='M3.51-8.54C1.79-7.42 .57-5.49 .57-3.06C.57-.85 1.83 1.39 3.48 2.41L3.62 2.22C2.05 .98 1.6-.46 1.6-3.1C1.6-5.74 2.08-7.11 3.62-8.35L3.51-8.54Z'/&gt;
&lt;path id='eq12-g1-186' d='M.45-8.54L.35-8.35C1.88-7.11 2.37-5.74 2.37-3.1C2.37-.46 1.92 .98 .35 2.22L.49 2.41C2.13 1.39 3.39-.85 3.39-3.06C3.39-5.49 2.18-7.42 .45-8.54Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq12-page1'&gt;
&lt;use x='.3' y='0' xlink:href='#eq12-g1-79'/&gt;
&lt;use x='9.91' y='0' xlink:href='#eq12-g1-185'/&gt;
&lt;use x='14.86' y='0' xlink:href='#eq12-g4-58'/&gt;
&lt;use x='21.12' y='-4.34' xlink:href='#eq12-g8-51'/&gt;
&lt;use x='26.01' y='0' xlink:href='#eq12-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; for the naive algorithm, and
&lt;span class="math"&gt;&lt;svg style="width: 2.910em; height: 1.265em; vertical-align: -0.241em; " viewBox=".3 -10.24 29.1 12.65"&gt;
&lt;title&gt;
\(\mathcal{O}(k^2)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq13-g8-50' d='M4.15-1.2L4.04-1.24C3.71-.74 3.6-.66 3.21-.66H1.12L2.59-2.2C3.36-3.01 3.7-3.68 3.7-4.36C3.7-5.23 3-5.9 2.09-5.9C1.61-5.9 1.15-5.71 .83-5.36C.55-5.07 .42-4.79 .27-4.17L.45-4.12C.8-4.98 1.12-5.26 1.72-5.26C2.45-5.26 2.95-4.76 2.95-4.03C2.95-3.35 2.55-2.53 1.82-1.76L.26-.1V0H3.67L4.15-1.2Z'/&gt;
&lt;path id='eq13-g4-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;path id='eq13-g1-79' d='M8.29-5.48C8.29-7.02 7.34-8.52 5.67-8.52C4.94-8.52 4.2-8.33 3.66-7.84C3.94-7.9 4.23-7.91 4.53-7.91C5.94-7.91 7.12-7.13 7.12-5.61C7.12-3.78 5.42-.2 3.29-.2C2.02-.2 1.39-1.37 1.39-2.5C1.39-4.54 3.1-7.06 5.22-7.35C4.9-7.5 4.59-7.53 4.23-7.53C2.18-7.53 .08-4.6 .08-2.69C.08-.73 1.41 .3 3.29 .3C6.21 .3 8.29-2.78 8.29-5.48Z'/&gt;
&lt;path id='eq13-g1-185' d='M3.51-8.54C1.79-7.42 .57-5.49 .57-3.06C.57-.85 1.83 1.39 3.48 2.41L3.62 2.22C2.05 .98 1.6-.46 1.6-3.1C1.6-5.74 2.08-7.11 3.62-8.35L3.51-8.54Z'/&gt;
&lt;path id='eq13-g1-186' d='M.45-8.54L.35-8.35C1.88-7.11 2.37-5.74 2.37-3.1C2.37-.46 1.92 .98 .35 2.22L.49 2.41C2.13 1.39 3.39-.85 3.39-3.06C3.39-5.49 2.18-7.42 .45-8.54Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq13-page1'&gt;
&lt;use x='.3' y='0' xlink:href='#eq13-g1-79'/&gt;
&lt;use x='9.91' y='0' xlink:href='#eq13-g1-185'/&gt;
&lt;use x='14.86' y='0' xlink:href='#eq13-g4-58'/&gt;
&lt;use x='21.12' y='-4.34' xlink:href='#eq13-g8-50'/&gt;
&lt;use x='26.01' y='0' xlink:href='#eq13-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; for the histogram-based algorithm).&lt;/p&gt;
&lt;p&gt;This algorithm is due to T.S. Huang and colleagues&amp;nbsp;&lt;a href="#ref-huang"&gt;[1]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But even for 16-bit images, a histogram becomes unwieldy, because finding the median now is not &lt;span class="math"&gt;&lt;svg style="width: 5.331em; height: 1.254em; vertical-align: -0.230em; " viewBox="0 -10.24 53.31 12.54"&gt;
&lt;title&gt;
\(2^8 / 2 = 128\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq14-g1-61' d='M6.57-3.93V-4.6H.48V-3.93H6.57ZM6.57-1.51V-2.18H.48V-1.51H6.57Z'/&gt;
&lt;path id='eq14-g4-157' d='M.74 2.3H1.36L4.74-8.42H4.12L.74 2.3Z'/&gt;
&lt;path id='eq14-g8-49' d='M3.44 0V-.13C2.75-.14 2.61-.23 2.61-.65V-5.89L2.54-5.9L.97-5.11V-4.99C1.07-5.03 1.17-5.07 1.21-5.08C1.36-5.14 1.51-5.18 1.6-5.18C1.78-5.18 1.86-5.05 1.86-4.77V-.81C1.86-.52 1.79-.32 1.65-.24C1.52-.17 1.4-.14 1.03-.13V0H3.44Z'/&gt;
&lt;path id='eq14-g8-50' d='M4.15-1.2L4.04-1.24C3.71-.74 3.6-.66 3.21-.66H1.12L2.59-2.2C3.36-3.01 3.7-3.68 3.7-4.36C3.7-5.23 3-5.9 2.09-5.9C1.61-5.9 1.15-5.71 .83-5.36C.55-5.07 .42-4.79 .27-4.17L.45-4.12C.8-4.98 1.12-5.26 1.72-5.26C2.45-5.26 2.95-4.76 2.95-4.03C2.95-3.35 2.55-2.53 1.82-1.76L.26-.1V0H3.67L4.15-1.2Z'/&gt;
&lt;path id='eq14-g8-56' d='M3.89-1.35C3.89-2.03 3.59-2.45 2.53-3.24C3.4-3.7 3.7-4.07 3.7-4.66C3.7-5.38 3.07-5.9 2.2-5.9C1.25-5.9 .54-5.32 .54-4.52C.54-3.96 .71-3.7 1.62-2.9C.68-2.18 .49-1.91 .49-1.32C.49-.47 1.18 .12 2.17 .12C3.21 .12 3.89-.45 3.89-1.35ZM3.22-1.08C3.22-.52 2.83-.12 2.26-.12C1.6-.12 1.15-.63 1.15-1.39C1.15-1.95 1.35-2.31 1.85-2.73L2.38-2.34C3.01-1.89 3.22-1.57 3.22-1.08ZM3.1-4.67C3.1-4.18 2.86-3.78 2.36-3.45C2.31-3.42 2.31-3.42 2.28-3.4C1.5-3.9 1.19-4.31 1.19-4.8C1.19-5.3 1.58-5.66 2.13-5.66C2.73-5.66 3.1-5.28 3.1-4.67Z'/&gt;
&lt;use id='eq14-g11-49' xlink:href='#eq14-g8-49' transform='scale(1.36)'/&gt;
&lt;use id='eq14-g11-50' xlink:href='#eq14-g8-50' transform='scale(1.36)'/&gt;
&lt;use id='eq14-g11-56' xlink:href='#eq14-g8-56' transform='scale(1.36)'/&gt;
&lt;/defs&gt;
&lt;g id='eq14-page1'&gt;
&lt;use x='0' y='0' xlink:href='#eq14-g11-50'/&gt;
&lt;use x='5.98' y='-4.34' xlink:href='#eq14-g8-56'/&gt;
&lt;use x='10.86' y='0' xlink:href='#eq14-g4-157'/&gt;
&lt;use x='16.36' y='0' xlink:href='#eq14-g11-50'/&gt;
&lt;use x='25.66' y='0' xlink:href='#eq14-g1-61'/&gt;
&lt;use x='36.06' y='0' xlink:href='#eq14-g11-49'/&gt;
&lt;use x='42.03' y='0' xlink:href='#eq14-g11-50'/&gt;
&lt;use x='48.01' y='0' xlink:href='#eq14-g11-56'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;
additions on average, but &lt;span class="math"&gt;&lt;svg style="width: 7.297em; height: 1.261em; vertical-align: -0.230em; " viewBox="0 -10.31 72.97 12.61"&gt;
&lt;title&gt;
\(2^{16} / 2 = 32\;768\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq15-g1-61' d='M6.57-3.93V-4.6H.48V-3.93H6.57ZM6.57-1.51V-2.18H.48V-1.51H6.57Z'/&gt;
&lt;path id='eq15-g8-49' d='M3.44 0V-.13C2.75-.14 2.61-.23 2.61-.65V-5.89L2.54-5.9L.97-5.11V-4.99C1.07-5.03 1.17-5.07 1.21-5.08C1.36-5.14 1.51-5.18 1.6-5.18C1.78-5.18 1.86-5.05 1.86-4.77V-.81C1.86-.52 1.79-.32 1.65-.24C1.52-.17 1.4-.14 1.03-.13V0H3.44Z'/&gt;
&lt;path id='eq15-g8-50' d='M4.15-1.2L4.04-1.24C3.71-.74 3.6-.66 3.21-.66H1.12L2.59-2.2C3.36-3.01 3.7-3.68 3.7-4.36C3.7-5.23 3-5.9 2.09-5.9C1.61-5.9 1.15-5.71 .83-5.36C.55-5.07 .42-4.79 .27-4.17L.45-4.12C.8-4.98 1.12-5.26 1.72-5.26C2.45-5.26 2.95-4.76 2.95-4.03C2.95-3.35 2.55-2.53 1.82-1.76L.26-.1V0H3.67L4.15-1.2Z'/&gt;
&lt;path id='eq15-g8-51' d='M3.77-1.91C3.77-2.36 3.63-2.77 3.38-3.04C3.21-3.23 3.04-3.34 2.66-3.5C3.26-3.91 3.48-4.24 3.48-4.71C3.48-5.42 2.92-5.9 2.11-5.9C1.68-5.9 1.29-5.76 .98-5.48C.72-5.24 .59-5.01 .39-4.49L.52-4.45C.88-5.09 1.28-5.38 1.83-5.38C2.39-5.38 2.79-5 2.79-4.45C2.79-4.13 2.66-3.82 2.44-3.6C2.17-3.34 1.93-3.21 1.34-3V-2.88C1.85-2.88 2.05-2.86 2.26-2.79C2.8-2.59 3.14-2.1 3.14-1.49C3.14-.76 2.65-.19 2-.19C1.76-.19 1.59-.25 1.27-.46C1-.62 .86-.68 .71-.68C.51-.68 .38-.56 .38-.38C.38-.07 .75 .12 1.36 .12C2.04 .12 2.73-.1 3.14-.46S3.77-1.33 3.77-1.91Z'/&gt;
&lt;path id='eq15-g8-54' d='M4.09-1.91C4.09-3.03 3.45-3.74 2.45-3.74C2.06-3.74 1.88-3.68 1.33-3.35C1.56-4.66 2.54-5.61 3.91-5.83L3.9-5.97C2.9-5.89 2.39-5.72 1.76-5.28C.81-4.6 .3-3.61 .3-2.44C.3-1.68 .53-.91 .91-.47C1.24-.09 1.71 .12 2.25 .12C3.34 .12 4.09-.71 4.09-1.91ZM3.3-1.62C3.3-.66 2.96-.12 2.35-.12C1.58-.12 1.11-.94 1.11-2.3C1.11-2.74 1.18-2.99 1.35-3.12C1.54-3.26 1.81-3.34 2.11-3.34C2.86-3.34 3.3-2.71 3.3-1.62Z'/&gt;
&lt;path id='eq15-g8-55' d='M3.92-5.64V-5.78H.69L.17-4.5L.32-4.43C.7-5.02 .86-5.14 1.34-5.14H3.23L1.5 .07H2.07L3.92-5.64Z'/&gt;
&lt;path id='eq15-g8-56' d='M3.89-1.35C3.89-2.03 3.59-2.45 2.53-3.24C3.4-3.7 3.7-4.07 3.7-4.66C3.7-5.38 3.07-5.9 2.2-5.9C1.25-5.9 .54-5.32 .54-4.52C.54-3.96 .71-3.7 1.62-2.9C.68-2.18 .49-1.91 .49-1.32C.49-.47 1.18 .12 2.17 .12C3.21 .12 3.89-.45 3.89-1.35ZM3.22-1.08C3.22-.52 2.83-.12 2.26-.12C1.6-.12 1.15-.63 1.15-1.39C1.15-1.95 1.35-2.31 1.85-2.73L2.38-2.34C3.01-1.89 3.22-1.57 3.22-1.08ZM3.1-4.67C3.1-4.18 2.86-3.78 2.36-3.45C2.31-3.42 2.31-3.42 2.28-3.4C1.5-3.9 1.19-4.31 1.19-4.8C1.19-5.3 1.58-5.66 2.13-5.66C2.73-5.66 3.1-5.28 3.1-4.67Z'/&gt;
&lt;path id='eq15-g4-157' d='M.74 2.3H1.36L4.74-8.42H4.12L.74 2.3Z'/&gt;
&lt;use id='eq15-g11-50' xlink:href='#eq15-g8-50' transform='scale(1.36)'/&gt;
&lt;use id='eq15-g11-51' xlink:href='#eq15-g8-51' transform='scale(1.36)'/&gt;
&lt;use id='eq15-g11-54' xlink:href='#eq15-g8-54' transform='scale(1.36)'/&gt;
&lt;use id='eq15-g11-55' xlink:href='#eq15-g8-55' transform='scale(1.36)'/&gt;
&lt;use id='eq15-g11-56' xlink:href='#eq15-g8-56' transform='scale(1.36)'/&gt;
&lt;/defs&gt;
&lt;g id='eq15-page1'&gt;
&lt;use x='0' y='0' xlink:href='#eq15-g11-50'/&gt;
&lt;use x='5.98' y='-4.34' xlink:href='#eq15-g8-49'/&gt;
&lt;use x='10.36' y='-4.34' xlink:href='#eq15-g8-54'/&gt;
&lt;use x='15.24' y='0' xlink:href='#eq15-g4-157'/&gt;
&lt;use x='20.74' y='0' xlink:href='#eq15-g11-50'/&gt;
&lt;use x='30.04' y='0' xlink:href='#eq15-g1-61'/&gt;
&lt;use x='40.44' y='0' xlink:href='#eq15-g11-51'/&gt;
&lt;use x='46.42' y='0' xlink:href='#eq15-g11-50'/&gt;
&lt;use x='55.71' y='0' xlink:href='#eq15-g11-55'/&gt;
&lt;use x='61.69' y='0' xlink:href='#eq15-g11-54'/&gt;
&lt;use x='67.67' y='0' xlink:href='#eq15-g11-56'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;. The constant cost becomes significant compared to the linear
cost for reasonable values of &lt;span class="math"&gt;&lt;svg style="width: 0.549em; height: 0.827em; vertical-align: -0.014em; " viewBox=".49 -8.13 5.49 8.27"&gt;
&lt;title&gt;
\(k\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq16-g1-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq16-page1'&gt;
&lt;use x='.49' y='0' xlink:href='#eq16-g1-58'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;It has been proposed to augment the 16-bit histogram with smaller histograms with wider bins. For example, each
subsequent scale combining 16 bins into one. Updating such a multi-scale histogram is a bit more expensive, but
finding the median becomes much cheaper. Still, this does not allow for an algorithm that can handle floating-point
images efficiently.&lt;/p&gt;
&lt;h2&gt;Binary tree algorithm&lt;/h2&gt;
&lt;p&gt;The histogram here was used as a data structure that makes it easy to compute the median of a set of values, and
allows this set to be updated (removing and adding values).
A &lt;a href="https://en.wikipedia.org/wiki/Binary_search_tree"&gt;binary search tree&lt;/a&gt; can perform the same function but does
not rely on values being small integers. A binary search tree is a binary tree where each node contains a value,
and the nodes are sorted such that a node&amp;rsquo;s left child (and all of its descendants) has a smaller value than the node,
and the node&amp;rsquo;s right child has a larger value. Inserting a new value into the tree takes &lt;span class="math"&gt;&lt;svg style="width: 4.093em; height: 1.114em; vertical-align: -0.260em; " viewBox=".3 -8.54 40.93 11.14"&gt;
&lt;title&gt;
\(\mathcal{O}(\log n)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq17-g4-61' d='M5.65-1.24L5.48-1.39C5.4-1.29 5.31-1.18 5.23-1.08C4.88-.64 4.67-.45 4.5-.45C4.39-.45 4.31-.54 4.31-.64C4.31-.74 4.36-.96 4.48-1.39L5.16-3.85C5.23-4.1 5.26-4.36 5.26-4.53C5.26-4.95 4.94-5.25 4.48-5.25C3.72-5.25 2.97-4.53 1.74-2.63L2.54-5.23L2.5-5.25L.57-4.88V-4.69C1.18-4.68 1.33-4.61 1.33-4.37C1.33-4.3 1.32-4.23 1.31-4.17L.17 0H1.06C1.62-1.88 1.73-2.14 2.25-2.95C2.97-4.06 3.57-4.65 4.01-4.65C4.19-4.65 4.3-4.51 4.3-4.3C4.3-4.16 4.23-3.76 4.13-3.39L3.61-1.43C3.45-.81 3.42-.66 3.42-.54C3.42-.08 3.59 .11 3.98 .11C4.51 .11 4.81-.13 5.65-1.24Z'/&gt;
&lt;path id='eq17-g8-103' d='M5.6-4.62V-5.09H4.68C4.44-5.09 4.26-5.12 4.03-5.2L3.76-5.3C3.44-5.42 3.12-5.48 2.81-5.48C1.7-5.48 .82-4.62 .82-3.54C.82-2.79 1.14-2.33 1.93-1.94L1.42-1.46C1.02-1.12 .87-.88 .87-.64C.87-.39 1.01-.25 1.5-.01C.66 .61 .33 1 .33 1.44C.33 2.07 1.26 2.6 2.39 2.6C3.29 2.6 4.22 2.29 4.84 1.79C5.29 1.42 5.49 1.04 5.49 .58C5.49-.15 4.93-.66 4.05-.69L2.51-.76C1.88-.79 1.58-.89 1.58-1.08C1.58-1.32 1.98-1.74 2.3-1.83C2.41-1.82 2.49-1.81 2.53-1.81C2.75-1.79 2.91-1.77 2.98-1.77C3.42-1.77 3.89-1.95 4.26-2.27C4.66-2.61 4.84-3.03 4.84-3.62C4.84-3.97 4.78-4.24 4.61-4.62H5.6ZM5.16 .76C5.16 1.45 4.25 1.92 2.91 1.92C1.86 1.92 1.17 1.57 1.17 1.05C1.17 .77 1.25 .62 1.75 .02C2.14 .11 3.1 .18 3.68 .18C4.76 .18 5.16 .33 5.16 .76ZM3.92-3.16C3.92-2.49 3.57-2.07 3.03-2.07C2.31-2.07 1.81-2.85 1.81-3.99V-4.03C1.81-4.73 2.14-5.15 2.69-5.15C3.06-5.15 3.37-4.94 3.56-4.59C3.78-4.17 3.92-3.62 3.92-3.16Z'/&gt;
&lt;path id='eq17-g8-108' d='M3.06 0V-.18C2.3-.23 2.17-.35 2.17-1V-8.11L2.12-8.13C1.5-7.93 1.05-7.81 .23-7.61V-7.42H.3C.43-7.43 .57-7.44 .67-7.44C1.05-7.44 1.17-7.28 1.17-6.72V-1.04C1.17-.39 1-.24 .25-.18V0H3.06Z'/&gt;
&lt;path id='eq17-g8-111' d='M5.6-2.79C5.6-4.35 4.5-5.48 2.98-5.48C1.43-5.48 .35-4.34 .35-2.69C.35-1.08 1.45 .12 2.95 .12S5.6-1.14 5.6-2.79ZM4.53-2.37C4.53-1.02 3.99-.21 3.1-.21C2.63-.21 2.19-.5 1.94-.98C1.61-1.6 1.42-2.43 1.42-3.28C1.42-4.41 1.98-5.15 2.82-5.15C3.82-5.15 4.53-4 4.53-2.37Z'/&gt;
&lt;path id='eq17-g1-79' d='M8.29-5.48C8.29-7.02 7.34-8.52 5.67-8.52C4.94-8.52 4.2-8.33 3.66-7.84C3.94-7.9 4.23-7.91 4.53-7.91C5.94-7.91 7.12-7.13 7.12-5.61C7.12-3.78 5.42-.2 3.29-.2C2.02-.2 1.39-1.37 1.39-2.5C1.39-4.54 3.1-7.06 5.22-7.35C4.9-7.5 4.59-7.53 4.23-7.53C2.18-7.53 .08-4.6 .08-2.69C.08-.73 1.41 .3 3.29 .3C6.21 .3 8.29-2.78 8.29-5.48Z'/&gt;
&lt;path id='eq17-g1-185' d='M3.51-8.54C1.79-7.42 .57-5.49 .57-3.06C.57-.85 1.83 1.39 3.48 2.41L3.62 2.22C2.05 .98 1.6-.46 1.6-3.1C1.6-5.74 2.08-7.11 3.62-8.35L3.51-8.54Z'/&gt;
&lt;path id='eq17-g1-186' d='M.45-8.54L.35-8.35C1.88-7.11 2.37-5.74 2.37-3.1C2.37-.46 1.92 .98 .35 2.22L.49 2.41C2.13 1.39 3.39-.85 3.39-3.06C3.39-5.49 2.18-7.42 .45-8.54Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq17-page1'&gt;
&lt;use x='.3' y='0' xlink:href='#eq17-g1-79'/&gt;
&lt;use x='9.91' y='0' xlink:href='#eq17-g1-185'/&gt;
&lt;use x='14.37' y='0' xlink:href='#eq17-g8-108'/&gt;
&lt;use x='17.69' y='0' xlink:href='#eq17-g8-111'/&gt;
&lt;use x='23.67' y='0' xlink:href='#eq17-g8-103'/&gt;
&lt;use x='31.7' y='0' xlink:href='#eq17-g4-61'/&gt;
&lt;use x='37.83' y='0' xlink:href='#eq17-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;,
and so does removing a value. But computing the median still requires to access half the nodes in order,
which takes &lt;span class="math"&gt;&lt;svg style="width: 2.366em; height: 1.095em; vertical-align: -0.241em; " viewBox=".3 -8.54 23.66 10.95"&gt;
&lt;title&gt;
\(\mathcal{O}(n)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq18-g4-61' d='M5.65-1.24L5.48-1.39C5.4-1.29 5.31-1.18 5.23-1.08C4.88-.64 4.67-.45 4.5-.45C4.39-.45 4.31-.54 4.31-.64C4.31-.74 4.36-.96 4.48-1.39L5.16-3.85C5.23-4.1 5.26-4.36 5.26-4.53C5.26-4.95 4.94-5.25 4.48-5.25C3.72-5.25 2.97-4.53 1.74-2.63L2.54-5.23L2.5-5.25L.57-4.88V-4.69C1.18-4.68 1.33-4.61 1.33-4.37C1.33-4.3 1.32-4.23 1.31-4.17L.17 0H1.06C1.62-1.88 1.73-2.14 2.25-2.95C2.97-4.06 3.57-4.65 4.01-4.65C4.19-4.65 4.3-4.51 4.3-4.3C4.3-4.16 4.23-3.76 4.13-3.39L3.61-1.43C3.45-.81 3.42-.66 3.42-.54C3.42-.08 3.59 .11 3.98 .11C4.51 .11 4.81-.13 5.65-1.24Z'/&gt;
&lt;path id='eq18-g1-79' d='M8.29-5.48C8.29-7.02 7.34-8.52 5.67-8.52C4.94-8.52 4.2-8.33 3.66-7.84C3.94-7.9 4.23-7.91 4.53-7.91C5.94-7.91 7.12-7.13 7.12-5.61C7.12-3.78 5.42-.2 3.29-.2C2.02-.2 1.39-1.37 1.39-2.5C1.39-4.54 3.1-7.06 5.22-7.35C4.9-7.5 4.59-7.53 4.23-7.53C2.18-7.53 .08-4.6 .08-2.69C.08-.73 1.41 .3 3.29 .3C6.21 .3 8.29-2.78 8.29-5.48Z'/&gt;
&lt;path id='eq18-g1-185' d='M3.51-8.54C1.79-7.42 .57-5.49 .57-3.06C.57-.85 1.83 1.39 3.48 2.41L3.62 2.22C2.05 .98 1.6-.46 1.6-3.1C1.6-5.74 2.08-7.11 3.62-8.35L3.51-8.54Z'/&gt;
&lt;path id='eq18-g1-186' d='M.45-8.54L.35-8.35C1.88-7.11 2.37-5.74 2.37-3.1C2.37-.46 1.92 .98 .35 2.22L.49 2.41C2.13 1.39 3.39-.85 3.39-3.06C3.39-5.49 2.18-7.42 .45-8.54Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq18-page1'&gt;
&lt;use x='.3' y='0' xlink:href='#eq18-g1-79'/&gt;
&lt;use x='9.91' y='0' xlink:href='#eq18-g1-185'/&gt;
&lt;use x='14.43' y='0' xlink:href='#eq18-g4-61'/&gt;
&lt;use x='20.56' y='0' xlink:href='#eq18-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;An &lt;a href="https://en.wikipedia.org/wiki/Order_statistic_tree"&gt;order statistic tree&lt;/a&gt; is a binary search tree where each
node additionally records its size, which is given by the number of nodes in the subtree rooted at that node.
That is, a node&amp;rsquo;s size is the sum of the sizes of the two children plus one. Armed with this additional information,
finding the median (or the node at any given index) takes only &lt;span class="math"&gt;&lt;svg style="width: 4.093em; height: 1.114em; vertical-align: -0.260em; " viewBox=".3 -8.54 40.93 11.14"&gt;
&lt;title&gt;
\(\mathcal{O}(\log n)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq19-g4-61' d='M5.65-1.24L5.48-1.39C5.4-1.29 5.31-1.18 5.23-1.08C4.88-.64 4.67-.45 4.5-.45C4.39-.45 4.31-.54 4.31-.64C4.31-.74 4.36-.96 4.48-1.39L5.16-3.85C5.23-4.1 5.26-4.36 5.26-4.53C5.26-4.95 4.94-5.25 4.48-5.25C3.72-5.25 2.97-4.53 1.74-2.63L2.54-5.23L2.5-5.25L.57-4.88V-4.69C1.18-4.68 1.33-4.61 1.33-4.37C1.33-4.3 1.32-4.23 1.31-4.17L.17 0H1.06C1.62-1.88 1.73-2.14 2.25-2.95C2.97-4.06 3.57-4.65 4.01-4.65C4.19-4.65 4.3-4.51 4.3-4.3C4.3-4.16 4.23-3.76 4.13-3.39L3.61-1.43C3.45-.81 3.42-.66 3.42-.54C3.42-.08 3.59 .11 3.98 .11C4.51 .11 4.81-.13 5.65-1.24Z'/&gt;
&lt;path id='eq19-g8-103' d='M5.6-4.62V-5.09H4.68C4.44-5.09 4.26-5.12 4.03-5.2L3.76-5.3C3.44-5.42 3.12-5.48 2.81-5.48C1.7-5.48 .82-4.62 .82-3.54C.82-2.79 1.14-2.33 1.93-1.94L1.42-1.46C1.02-1.12 .87-.88 .87-.64C.87-.39 1.01-.25 1.5-.01C.66 .61 .33 1 .33 1.44C.33 2.07 1.26 2.6 2.39 2.6C3.29 2.6 4.22 2.29 4.84 1.79C5.29 1.42 5.49 1.04 5.49 .58C5.49-.15 4.93-.66 4.05-.69L2.51-.76C1.88-.79 1.58-.89 1.58-1.08C1.58-1.32 1.98-1.74 2.3-1.83C2.41-1.82 2.49-1.81 2.53-1.81C2.75-1.79 2.91-1.77 2.98-1.77C3.42-1.77 3.89-1.95 4.26-2.27C4.66-2.61 4.84-3.03 4.84-3.62C4.84-3.97 4.78-4.24 4.61-4.62H5.6ZM5.16 .76C5.16 1.45 4.25 1.92 2.91 1.92C1.86 1.92 1.17 1.57 1.17 1.05C1.17 .77 1.25 .62 1.75 .02C2.14 .11 3.1 .18 3.68 .18C4.76 .18 5.16 .33 5.16 .76ZM3.92-3.16C3.92-2.49 3.57-2.07 3.03-2.07C2.31-2.07 1.81-2.85 1.81-3.99V-4.03C1.81-4.73 2.14-5.15 2.69-5.15C3.06-5.15 3.37-4.94 3.56-4.59C3.78-4.17 3.92-3.62 3.92-3.16Z'/&gt;
&lt;path id='eq19-g8-108' d='M3.06 0V-.18C2.3-.23 2.17-.35 2.17-1V-8.11L2.12-8.13C1.5-7.93 1.05-7.81 .23-7.61V-7.42H.3C.43-7.43 .57-7.44 .67-7.44C1.05-7.44 1.17-7.28 1.17-6.72V-1.04C1.17-.39 1-.24 .25-.18V0H3.06Z'/&gt;
&lt;path id='eq19-g8-111' d='M5.6-2.79C5.6-4.35 4.5-5.48 2.98-5.48C1.43-5.48 .35-4.34 .35-2.69C.35-1.08 1.45 .12 2.95 .12S5.6-1.14 5.6-2.79ZM4.53-2.37C4.53-1.02 3.99-.21 3.1-.21C2.63-.21 2.19-.5 1.94-.98C1.61-1.6 1.42-2.43 1.42-3.28C1.42-4.41 1.98-5.15 2.82-5.15C3.82-5.15 4.53-4 4.53-2.37Z'/&gt;
&lt;path id='eq19-g1-79' d='M8.29-5.48C8.29-7.02 7.34-8.52 5.67-8.52C4.94-8.52 4.2-8.33 3.66-7.84C3.94-7.9 4.23-7.91 4.53-7.91C5.94-7.91 7.12-7.13 7.12-5.61C7.12-3.78 5.42-.2 3.29-.2C2.02-.2 1.39-1.37 1.39-2.5C1.39-4.54 3.1-7.06 5.22-7.35C4.9-7.5 4.59-7.53 4.23-7.53C2.18-7.53 .08-4.6 .08-2.69C.08-.73 1.41 .3 3.29 .3C6.21 .3 8.29-2.78 8.29-5.48Z'/&gt;
&lt;path id='eq19-g1-185' d='M3.51-8.54C1.79-7.42 .57-5.49 .57-3.06C.57-.85 1.83 1.39 3.48 2.41L3.62 2.22C2.05 .98 1.6-.46 1.6-3.1C1.6-5.74 2.08-7.11 3.62-8.35L3.51-8.54Z'/&gt;
&lt;path id='eq19-g1-186' d='M.45-8.54L.35-8.35C1.88-7.11 2.37-5.74 2.37-3.1C2.37-.46 1.92 .98 .35 2.22L.49 2.41C2.13 1.39 3.39-.85 3.39-3.06C3.39-5.49 2.18-7.42 .45-8.54Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq19-page1'&gt;
&lt;use x='.3' y='0' xlink:href='#eq19-g1-79'/&gt;
&lt;use x='9.91' y='0' xlink:href='#eq19-g1-185'/&gt;
&lt;use x='14.37' y='0' xlink:href='#eq19-g8-108'/&gt;
&lt;use x='17.69' y='0' xlink:href='#eq19-g8-111'/&gt;
&lt;use x='23.67' y='0' xlink:href='#eq19-g8-103'/&gt;
&lt;use x='31.7' y='0' xlink:href='#eq19-g4-61'/&gt;
&lt;use x='37.83' y='0' xlink:href='#eq19-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;. It is no longer necessary
to traverse all nodes in order from the lowest value to the given index. The node&amp;rsquo;s size allows the algorithm
to skip most nodes along that path.&lt;/p&gt;
&lt;p&gt;So, again assuming a &lt;span class="math"&gt;&lt;svg style="width: 2.516em; height: 0.827em; vertical-align: -0.014em; " viewBox=".49 -8.13 25.16 8.27"&gt;
&lt;title&gt;
\(k \times k\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq20-g1-2' d='M6.91-.42L4.25-3.06L6.91-5.72L6.43-6.19L3.79-3.54L1.14-6.19L.67-5.72L3.31-3.06L.67-.42L1.14 .06L3.79-2.6L6.43 .06L6.91-.42Z'/&gt;
&lt;path id='eq20-g4-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq20-page1'&gt;
&lt;use x='.49' y='0' xlink:href='#eq20-g4-58'/&gt;
&lt;use x='9.41' y='0' xlink:href='#eq20-g1-2'/&gt;
&lt;use x='20.16' y='0' xlink:href='#eq20-g4-58'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; square kernel, we have &lt;span class="math"&gt;&lt;svg style="width: 3.076em; height: 1.038em; vertical-align: -0.014em; " viewBox=".06 -10.24 30.76 10.38"&gt;
&lt;title&gt;
\(n=k^2\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq21-g1-61' d='M6.57-3.93V-4.6H.48V-3.93H6.57ZM6.57-1.51V-2.18H.48V-1.51H6.57Z'/&gt;
&lt;path id='eq21-g8-50' d='M4.15-1.2L4.04-1.24C3.71-.74 3.6-.66 3.21-.66H1.12L2.59-2.2C3.36-3.01 3.7-3.68 3.7-4.36C3.7-5.23 3-5.9 2.09-5.9C1.61-5.9 1.15-5.71 .83-5.36C.55-5.07 .42-4.79 .27-4.17L.45-4.12C.8-4.98 1.12-5.26 1.72-5.26C2.45-5.26 2.95-4.76 2.95-4.03C2.95-3.35 2.55-2.53 1.82-1.76L.26-.1V0H3.67L4.15-1.2Z'/&gt;
&lt;path id='eq21-g4-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;path id='eq21-g4-61' d='M5.65-1.24L5.48-1.39C5.4-1.29 5.31-1.18 5.23-1.08C4.88-.64 4.67-.45 4.5-.45C4.39-.45 4.31-.54 4.31-.64C4.31-.74 4.36-.96 4.48-1.39L5.16-3.85C5.23-4.1 5.26-4.36 5.26-4.53C5.26-4.95 4.94-5.25 4.48-5.25C3.72-5.25 2.97-4.53 1.74-2.63L2.54-5.23L2.5-5.25L.57-4.88V-4.69C1.18-4.68 1.33-4.61 1.33-4.37C1.33-4.3 1.32-4.23 1.31-4.17L.17 0H1.06C1.62-1.88 1.73-2.14 2.25-2.95C2.97-4.06 3.57-4.65 4.01-4.65C4.19-4.65 4.3-4.51 4.3-4.3C4.3-4.16 4.23-3.76 4.13-3.39L3.61-1.43C3.45-.81 3.42-.66 3.42-.54C3.42-.08 3.59 .11 3.98 .11C4.51 .11 4.81-.13 5.65-1.24Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq21-page1'&gt;
&lt;use x='.06' y='0' xlink:href='#eq21-g4-61'/&gt;
&lt;use x='9.51' y='0' xlink:href='#eq21-g1-61'/&gt;
&lt;use x='20.4' y='0' xlink:href='#eq21-g4-58'/&gt;
&lt;use x='26.67' y='-4.34' xlink:href='#eq21-g8-50'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; values in the tree, and for each pixel we need
to remove &lt;span class="math"&gt;&lt;svg style="width: 0.549em; height: 0.827em; vertical-align: -0.014em; " viewBox=".49 -8.13 5.49 8.27"&gt;
&lt;title&gt;
\(k\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq22-g1-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq22-page1'&gt;
&lt;use x='.49' y='0' xlink:href='#eq22-g1-58'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; values, insert &lt;span class="math"&gt;&lt;svg style="width: 0.549em; height: 0.827em; vertical-align: -0.014em; " viewBox=".49 -8.13 5.49 8.27"&gt;
&lt;title&gt;
\(k\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq23-g1-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq23-page1'&gt;
&lt;use x='.49' y='0' xlink:href='#eq23-g1-58'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; values, then find the median. This has a complexity of &lt;span class="math"&gt;&lt;svg style="width: 5.024em; height: 1.114em; vertical-align: -0.260em; " viewBox=".3 -8.54 50.24 11.14"&gt;
&lt;title&gt;
\(\mathcal{O}(k \log k)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq24-g8-103' d='M5.6-4.62V-5.09H4.68C4.44-5.09 4.26-5.12 4.03-5.2L3.76-5.3C3.44-5.42 3.12-5.48 2.81-5.48C1.7-5.48 .82-4.62 .82-3.54C.82-2.79 1.14-2.33 1.93-1.94L1.42-1.46C1.02-1.12 .87-.88 .87-.64C.87-.39 1.01-.25 1.5-.01C.66 .61 .33 1 .33 1.44C.33 2.07 1.26 2.6 2.39 2.6C3.29 2.6 4.22 2.29 4.84 1.79C5.29 1.42 5.49 1.04 5.49 .58C5.49-.15 4.93-.66 4.05-.69L2.51-.76C1.88-.79 1.58-.89 1.58-1.08C1.58-1.32 1.98-1.74 2.3-1.83C2.41-1.82 2.49-1.81 2.53-1.81C2.75-1.79 2.91-1.77 2.98-1.77C3.42-1.77 3.89-1.95 4.26-2.27C4.66-2.61 4.84-3.03 4.84-3.62C4.84-3.97 4.78-4.24 4.61-4.62H5.6ZM5.16 .76C5.16 1.45 4.25 1.92 2.91 1.92C1.86 1.92 1.17 1.57 1.17 1.05C1.17 .77 1.25 .62 1.75 .02C2.14 .11 3.1 .18 3.68 .18C4.76 .18 5.16 .33 5.16 .76ZM3.92-3.16C3.92-2.49 3.57-2.07 3.03-2.07C2.31-2.07 1.81-2.85 1.81-3.99V-4.03C1.81-4.73 2.14-5.15 2.69-5.15C3.06-5.15 3.37-4.94 3.56-4.59C3.78-4.17 3.92-3.62 3.92-3.16Z'/&gt;
&lt;path id='eq24-g8-108' d='M3.06 0V-.18C2.3-.23 2.17-.35 2.17-1V-8.11L2.12-8.13C1.5-7.93 1.05-7.81 .23-7.61V-7.42H.3C.43-7.43 .57-7.44 .67-7.44C1.05-7.44 1.17-7.28 1.17-6.72V-1.04C1.17-.39 1-.24 .25-.18V0H3.06Z'/&gt;
&lt;path id='eq24-g8-111' d='M5.6-2.79C5.6-4.35 4.5-5.48 2.98-5.48C1.43-5.48 .35-4.34 .35-2.69C.35-1.08 1.45 .12 2.95 .12S5.6-1.14 5.6-2.79ZM4.53-2.37C4.53-1.02 3.99-.21 3.1-.21C2.63-.21 2.19-.5 1.94-.98C1.61-1.6 1.42-2.43 1.42-3.28C1.42-4.41 1.98-5.15 2.82-5.15C3.82-5.15 4.53-4 4.53-2.37Z'/&gt;
&lt;path id='eq24-g4-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;path id='eq24-g1-79' d='M8.29-5.48C8.29-7.02 7.34-8.52 5.67-8.52C4.94-8.52 4.2-8.33 3.66-7.84C3.94-7.9 4.23-7.91 4.53-7.91C5.94-7.91 7.12-7.13 7.12-5.61C7.12-3.78 5.42-.2 3.29-.2C2.02-.2 1.39-1.37 1.39-2.5C1.39-4.54 3.1-7.06 5.22-7.35C4.9-7.5 4.59-7.53 4.23-7.53C2.18-7.53 .08-4.6 .08-2.69C.08-.73 1.41 .3 3.29 .3C6.21 .3 8.29-2.78 8.29-5.48Z'/&gt;
&lt;path id='eq24-g1-185' d='M3.51-8.54C1.79-7.42 .57-5.49 .57-3.06C.57-.85 1.83 1.39 3.48 2.41L3.62 2.22C2.05 .98 1.6-.46 1.6-3.1C1.6-5.74 2.08-7.11 3.62-8.35L3.51-8.54Z'/&gt;
&lt;path id='eq24-g1-186' d='M.45-8.54L.35-8.35C1.88-7.11 2.37-5.74 2.37-3.1C2.37-.46 1.92 .98 .35 2.22L.49 2.41C2.13 1.39 3.39-.85 3.39-3.06C3.39-5.49 2.18-7.42 .45-8.54Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq24-page1'&gt;
&lt;use x='.3' y='0' xlink:href='#eq24-g1-79'/&gt;
&lt;use x='9.91' y='0' xlink:href='#eq24-g1-185'/&gt;
&lt;use x='14.86' y='0' xlink:href='#eq24-g4-58'/&gt;
&lt;use x='23.12' y='0' xlink:href='#eq24-g8-108'/&gt;
&lt;use x='26.44' y='0' xlink:href='#eq24-g8-111'/&gt;
&lt;use x='32.42' y='0' xlink:href='#eq24-g8-103'/&gt;
&lt;use x='40.88' y='0' xlink:href='#eq24-g4-58'/&gt;
&lt;use x='47.14' y='0' xlink:href='#eq24-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The complexity analysis here assumes that the binary tree is balanced. Values in an image are locally correlated,
meaning that the tree is likely to become unbalanced quickly unless we specifically rebalance the tree when needed.
Binary trees typically store some additional value to enable this balancing act (e.g. red-black trees).
One form of self-balancing tree is the &lt;a href="https://en.wikipedia.org/wiki/Weight-balanced_tree"&gt;weight-balanced tree&lt;/a&gt;,
which keeps the weight of the two subtrees within some factor of each other. Each node stores the weight of the
subtree rooted at that node. The weight is defined as one plus the size of the subtree, and we&amp;rsquo;re already storing
the size to compute the order statistic. This means that we can keep the order statistic tree balanced without
any additional information or bookkeeping. I followed Hirai and Yamamoto&amp;nbsp;&lt;a href="#ref-hirai"&gt;[2]&lt;/a&gt; to implement the
balancing scheme, using their proposed (3,2) factors: the weight of the two subtrees are kept within a factor of 3
from each other, and the 2 is used when the balance is broken, to decide on whether to do a single or a double
rotation at the node to restore balance.&lt;/p&gt;
&lt;p&gt;Because pixel values are locally correlated, it is likely that there are repeated values in a neighborhood. A simple
modification to the order statistic tree gives it a speed boost when this happens: repeated values are stored in the
same node, which now additionally stores this count. This change means there are fewer nodes to traverse when
inserting or removing values, or when indexing, and so the computational cost is reduced.&lt;/p&gt;
&lt;p&gt;This implementation is in DIPlib, &lt;a href="https://github.com/DIPlib/diplib/blob/master/src/nonlinear/percentile.cpp"&gt;here&lt;/a&gt;.
It will be available in the next release (whichever comes after the current 3.3.0 release), or you can build it yourself.&lt;/p&gt;
&lt;p&gt;The binary search tree is a bit more expensive than the naive algorithm for small kernels.
I ran a small experiment to find out how to decide which of the two algorithms to use for any particular call
to the &lt;code&gt;dip::MedianFilter()&lt;/code&gt; function. Using a rectangular kernel, I varied the width and height independently,
from 3 to 39, and determined the running time on some sufficiently large image. This is the result (blue is the
naive algorithm, yellow the new order statistic tree algorithm):&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="Timing the naive vs order statistic tree implementation of the median filter" src="/images/median_filter_naive_vs_tree.png"&gt;&lt;/p&gt;
&lt;p&gt;It can clearly be seen that the naive implementation has a running time that is linear in both the width (&lt;span class="math"&gt;&lt;svg style="width: 0.838em; height: 0.536em; vertical-align: -0.011em; " viewBox=".08 -5.25 8.38 5.36"&gt;
&lt;title&gt;
\(m\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq25-g1-60' d='M8.38-1.25L8.21-1.39L7.96-1.08C7.61-.64 7.38-.45 7.18-.45C7.09-.45 7.04-.51 7.04-.64C7.04-.69 7.09-.91 7.16-1.18L7.97-4.17C7.98-4.23 8-4.42 8-4.5C8-4.93 7.71-5.25 7.31-5.25C7.07-5.25 6.86-5.18 6.57-5C5.99-4.63 5.49-4.05 4.66-2.73C4.95-3.64 5.13-4.31 5.13-4.54C5.13-4.97 4.86-5.25 4.45-5.25C3.73-5.25 2.89-4.47 1.8-2.74L2.49-5.23L2.45-5.25L.55-4.88V-4.69H.85C1.17-4.69 1.31-4.61 1.31-4.43C1.31-4.29 1.04-3.2 .61-1.7L.14 0H1.04C1.61-1.88 1.79-2.33 2.19-2.95C2.86-3.97 3.57-4.63 3.98-4.63C4.13-4.63 4.22-4.53 4.22-4.34C4.22-4.18 3.89-2.92 3.38-1.1L3.08 0H3.98C4.44-1.83 4.68-2.43 5.26-3.25C5.86-4.09 6.47-4.63 6.82-4.63C6.96-4.63 7.06-4.53 7.06-4.38C7.06-4.32 7.04-4.2 6.98-3.97L6.29-1.23C6.17-.76 6.13-.56 6.13-.44C6.13-.08 6.3 .11 6.61 .11C7.17 .11 7.69-.29 8.33-1.17L8.38-1.25Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq25-page1'&gt;
&lt;use x='.08' y='0' xlink:href='#eq25-g1-60'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;)
and the height (&lt;span class="math"&gt;&lt;svg style="width: 0.549em; height: 0.827em; vertical-align: -0.014em; " viewBox=".49 -8.13 5.49 8.27"&gt;
&lt;title&gt;
\(k\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq26-g1-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq26-page1'&gt;
&lt;use x='.49' y='0' xlink:href='#eq26-g1-58'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;) of the kernel (&lt;span class="math"&gt;&lt;svg style="width: 3.344em; height: 1.095em; vertical-align: -0.241em; " viewBox=".3 -8.54 33.44 10.95"&gt;
&lt;title&gt;
\(\mathcal{O}(mk)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq27-g4-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;path id='eq27-g4-60' d='M8.38-1.25L8.21-1.39L7.96-1.08C7.61-.64 7.38-.45 7.18-.45C7.09-.45 7.04-.51 7.04-.64C7.04-.69 7.09-.91 7.16-1.18L7.97-4.17C7.98-4.23 8-4.42 8-4.5C8-4.93 7.71-5.25 7.31-5.25C7.07-5.25 6.86-5.18 6.57-5C5.99-4.63 5.49-4.05 4.66-2.73C4.95-3.64 5.13-4.31 5.13-4.54C5.13-4.97 4.86-5.25 4.45-5.25C3.73-5.25 2.89-4.47 1.8-2.74L2.49-5.23L2.45-5.25L.55-4.88V-4.69H.85C1.17-4.69 1.31-4.61 1.31-4.43C1.31-4.29 1.04-3.2 .61-1.7L.14 0H1.04C1.61-1.88 1.79-2.33 2.19-2.95C2.86-3.97 3.57-4.63 3.98-4.63C4.13-4.63 4.22-4.53 4.22-4.34C4.22-4.18 3.89-2.92 3.38-1.1L3.08 0H3.98C4.44-1.83 4.68-2.43 5.26-3.25C5.86-4.09 6.47-4.63 6.82-4.63C6.96-4.63 7.06-4.53 7.06-4.38C7.06-4.32 7.04-4.2 6.98-3.97L6.29-1.23C6.17-.76 6.13-.56 6.13-.44C6.13-.08 6.3 .11 6.61 .11C7.17 .11 7.69-.29 8.33-1.17L8.38-1.25Z'/&gt;
&lt;path id='eq27-g1-79' d='M8.29-5.48C8.29-7.02 7.34-8.52 5.67-8.52C4.94-8.52 4.2-8.33 3.66-7.84C3.94-7.9 4.23-7.91 4.53-7.91C5.94-7.91 7.12-7.13 7.12-5.61C7.12-3.78 5.42-.2 3.29-.2C2.02-.2 1.39-1.37 1.39-2.5C1.39-4.54 3.1-7.06 5.22-7.35C4.9-7.5 4.59-7.53 4.23-7.53C2.18-7.53 .08-4.6 .08-2.69C.08-.73 1.41 .3 3.29 .3C6.21 .3 8.29-2.78 8.29-5.48Z'/&gt;
&lt;path id='eq27-g1-185' d='M3.51-8.54C1.79-7.42 .57-5.49 .57-3.06C.57-.85 1.83 1.39 3.48 2.41L3.62 2.22C2.05 .98 1.6-.46 1.6-3.1C1.6-5.74 2.08-7.11 3.62-8.35L3.51-8.54Z'/&gt;
&lt;path id='eq27-g1-186' d='M.45-8.54L.35-8.35C1.88-7.11 2.37-5.74 2.37-3.1C2.37-.46 1.92 .98 .35 2.22L.49 2.41C2.13 1.39 3.39-.85 3.39-3.06C3.39-5.49 2.18-7.42 .45-8.54Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq27-page1'&gt;
&lt;use x='.3' y='0' xlink:href='#eq27-g1-79'/&gt;
&lt;use x='9.91' y='0' xlink:href='#eq27-g1-185'/&gt;
&lt;use x='14.45' y='0' xlink:href='#eq27-g4-60'/&gt;
&lt;use x='24.08' y='0' xlink:href='#eq27-g4-58'/&gt;
&lt;use x='30.34' y='0' xlink:href='#eq27-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;). In contrast, the binary tree implementation has a running
time that is independent of the width, and linear in the height (&lt;span class="math"&gt;&lt;svg style="width: 2.422em; height: 1.095em; vertical-align: -0.241em; " viewBox=".3 -8.54 24.22 10.95"&gt;
&lt;title&gt;
\(\mathcal{O}(k)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq28-g4-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;path id='eq28-g1-79' d='M8.29-5.48C8.29-7.02 7.34-8.52 5.67-8.52C4.94-8.52 4.2-8.33 3.66-7.84C3.94-7.9 4.23-7.91 4.53-7.91C5.94-7.91 7.12-7.13 7.12-5.61C7.12-3.78 5.42-.2 3.29-.2C2.02-.2 1.39-1.37 1.39-2.5C1.39-4.54 3.1-7.06 5.22-7.35C4.9-7.5 4.59-7.53 4.23-7.53C2.18-7.53 .08-4.6 .08-2.69C.08-.73 1.41 .3 3.29 .3C6.21 .3 8.29-2.78 8.29-5.48Z'/&gt;
&lt;path id='eq28-g1-185' d='M3.51-8.54C1.79-7.42 .57-5.49 .57-3.06C.57-.85 1.83 1.39 3.48 2.41L3.62 2.22C2.05 .98 1.6-.46 1.6-3.1C1.6-5.74 2.08-7.11 3.62-8.35L3.51-8.54Z'/&gt;
&lt;path id='eq28-g1-186' d='M.45-8.54L.35-8.35C1.88-7.11 2.37-5.74 2.37-3.1C2.37-.46 1.92 .98 .35 2.22L.49 2.41C2.13 1.39 3.39-.85 3.39-3.06C3.39-5.49 2.18-7.42 .45-8.54Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq28-page1'&gt;
&lt;use x='.3' y='0' xlink:href='#eq28-g1-79'/&gt;
&lt;use x='9.91' y='0' xlink:href='#eq28-g1-185'/&gt;
&lt;use x='14.86' y='0' xlink:href='#eq28-g4-58'/&gt;
&lt;use x='21.12' y='0' xlink:href='#eq28-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;). It is actually
&lt;span class="math"&gt;&lt;svg style="width: 5.024em; height: 1.114em; vertical-align: -0.260em; " viewBox=".3 -8.54 50.24 11.14"&gt;
&lt;title&gt;
\(\mathcal{O}(k \log k)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq29-g8-103' d='M5.6-4.62V-5.09H4.68C4.44-5.09 4.26-5.12 4.03-5.2L3.76-5.3C3.44-5.42 3.12-5.48 2.81-5.48C1.7-5.48 .82-4.62 .82-3.54C.82-2.79 1.14-2.33 1.93-1.94L1.42-1.46C1.02-1.12 .87-.88 .87-.64C.87-.39 1.01-.25 1.5-.01C.66 .61 .33 1 .33 1.44C.33 2.07 1.26 2.6 2.39 2.6C3.29 2.6 4.22 2.29 4.84 1.79C5.29 1.42 5.49 1.04 5.49 .58C5.49-.15 4.93-.66 4.05-.69L2.51-.76C1.88-.79 1.58-.89 1.58-1.08C1.58-1.32 1.98-1.74 2.3-1.83C2.41-1.82 2.49-1.81 2.53-1.81C2.75-1.79 2.91-1.77 2.98-1.77C3.42-1.77 3.89-1.95 4.26-2.27C4.66-2.61 4.84-3.03 4.84-3.62C4.84-3.97 4.78-4.24 4.61-4.62H5.6ZM5.16 .76C5.16 1.45 4.25 1.92 2.91 1.92C1.86 1.92 1.17 1.57 1.17 1.05C1.17 .77 1.25 .62 1.75 .02C2.14 .11 3.1 .18 3.68 .18C4.76 .18 5.16 .33 5.16 .76ZM3.92-3.16C3.92-2.49 3.57-2.07 3.03-2.07C2.31-2.07 1.81-2.85 1.81-3.99V-4.03C1.81-4.73 2.14-5.15 2.69-5.15C3.06-5.15 3.37-4.94 3.56-4.59C3.78-4.17 3.92-3.62 3.92-3.16Z'/&gt;
&lt;path id='eq29-g8-108' d='M3.06 0V-.18C2.3-.23 2.17-.35 2.17-1V-8.11L2.12-8.13C1.5-7.93 1.05-7.81 .23-7.61V-7.42H.3C.43-7.43 .57-7.44 .67-7.44C1.05-7.44 1.17-7.28 1.17-6.72V-1.04C1.17-.39 1-.24 .25-.18V0H3.06Z'/&gt;
&lt;path id='eq29-g8-111' d='M5.6-2.79C5.6-4.35 4.5-5.48 2.98-5.48C1.43-5.48 .35-4.34 .35-2.69C.35-1.08 1.45 .12 2.95 .12S5.6-1.14 5.6-2.79ZM4.53-2.37C4.53-1.02 3.99-.21 3.1-.21C2.63-.21 2.19-.5 1.94-.98C1.61-1.6 1.42-2.43 1.42-3.28C1.42-4.41 1.98-5.15 2.82-5.15C3.82-5.15 4.53-4 4.53-2.37Z'/&gt;
&lt;path id='eq29-g4-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;path id='eq29-g1-79' d='M8.29-5.48C8.29-7.02 7.34-8.52 5.67-8.52C4.94-8.52 4.2-8.33 3.66-7.84C3.94-7.9 4.23-7.91 4.53-7.91C5.94-7.91 7.12-7.13 7.12-5.61C7.12-3.78 5.42-.2 3.29-.2C2.02-.2 1.39-1.37 1.39-2.5C1.39-4.54 3.1-7.06 5.22-7.35C4.9-7.5 4.59-7.53 4.23-7.53C2.18-7.53 .08-4.6 .08-2.69C.08-.73 1.41 .3 3.29 .3C6.21 .3 8.29-2.78 8.29-5.48Z'/&gt;
&lt;path id='eq29-g1-185' d='M3.51-8.54C1.79-7.42 .57-5.49 .57-3.06C.57-.85 1.83 1.39 3.48 2.41L3.62 2.22C2.05 .98 1.6-.46 1.6-3.1C1.6-5.74 2.08-7.11 3.62-8.35L3.51-8.54Z'/&gt;
&lt;path id='eq29-g1-186' d='M.45-8.54L.35-8.35C1.88-7.11 2.37-5.74 2.37-3.1C2.37-.46 1.92 .98 .35 2.22L.49 2.41C2.13 1.39 3.39-.85 3.39-3.06C3.39-5.49 2.18-7.42 .45-8.54Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq29-page1'&gt;
&lt;use x='.3' y='0' xlink:href='#eq29-g1-79'/&gt;
&lt;use x='9.91' y='0' xlink:href='#eq29-g1-185'/&gt;
&lt;use x='14.86' y='0' xlink:href='#eq29-g4-58'/&gt;
&lt;use x='23.12' y='0' xlink:href='#eq29-g8-108'/&gt;
&lt;use x='26.44' y='0' xlink:href='#eq29-g8-111'/&gt;
&lt;use x='32.42' y='0' xlink:href='#eq29-g8-103'/&gt;
&lt;use x='40.88' y='0' xlink:href='#eq29-g4-58'/&gt;
&lt;use x='47.14' y='0' xlink:href='#eq29-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;, but the range is too small to discern the difference.&lt;/p&gt;
&lt;p&gt;It can also clearly be seen that the two implementations take the same time for &lt;span class="math"&gt;&lt;svg style="width: 0.549em; height: 0.827em; vertical-align: -0.014em; " viewBox=".49 -8.13 5.49 8.27"&gt;
&lt;title&gt;
\(k\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq30-g1-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq30-page1'&gt;
&lt;use x='.49' y='0' xlink:href='#eq30-g1-58'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; equal to 11. This threshold
is likely different on different computers, but I hard-coded this threshold into the &lt;code&gt;dip::MedianFilter()&lt;/code&gt; function
anyway as it should give some meaningful algorithm selection on any machine.&lt;/p&gt;
&lt;h2&gt;O(1) algorithm&lt;/h2&gt;
&lt;p&gt;Perreault and Hérbert&amp;nbsp;&lt;a href="#ref-perreault"&gt;[3]&lt;/a&gt; figured out how to compute a median filter in constant time. This
algorithm is based on Huang&amp;rsquo;s histogram algorithm, but they store a histogram for each image column, updating these
when moving the kernel by one pixel only requires removing one value and adding one value to one of these histograms,
then subtracting one histogram and adding another histogram to the main histogram that covers all values under the
kernel. OpenCV implements this algorithm, which only works for 8-bit images and square kernels.&lt;/p&gt;
&lt;p&gt;Perreault and Hérbert&amp;rsquo;s paper discusses extension to octagonal kernels (as an approximation to round kernels),
which require more intermediate memory (more histograms stored). They claim that for these kernels the algorithm
is still &lt;span class="math"&gt;&lt;svg style="width: 2.344em; height: 1.095em; vertical-align: -0.241em; " viewBox=".3 -8.54 23.44 10.95"&gt;
&lt;title&gt;
\(\mathcal{O}(1)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq31-g5-49' d='M4.69 0V-.18C3.75-.19 3.56-.31 3.56-.88V-8.03L3.47-8.05L1.32-6.97V-6.8C1.46-6.86 1.6-6.91 1.64-6.93C1.86-7.02 2.06-7.06 2.18-7.06C2.43-7.06 2.54-6.88 2.54-6.5V-1.11C2.54-.71 2.44-.44 2.25-.33C2.07-.23 1.91-.19 1.41-.18V0H4.69Z'/&gt;
&lt;path id='eq31-g1-79' d='M8.29-5.48C8.29-7.02 7.34-8.52 5.67-8.52C4.94-8.52 4.2-8.33 3.66-7.84C3.94-7.9 4.23-7.91 4.53-7.91C5.94-7.91 7.12-7.13 7.12-5.61C7.12-3.78 5.42-.2 3.29-.2C2.02-.2 1.39-1.37 1.39-2.5C1.39-4.54 3.1-7.06 5.22-7.35C4.9-7.5 4.59-7.53 4.23-7.53C2.18-7.53 .08-4.6 .08-2.69C.08-.73 1.41 .3 3.29 .3C6.21 .3 8.29-2.78 8.29-5.48Z'/&gt;
&lt;path id='eq31-g1-185' d='M3.51-8.54C1.79-7.42 .57-5.49 .57-3.06C.57-.85 1.83 1.39 3.48 2.41L3.62 2.22C2.05 .98 1.6-.46 1.6-3.1C1.6-5.74 2.08-7.11 3.62-8.35L3.51-8.54Z'/&gt;
&lt;path id='eq31-g1-186' d='M.45-8.54L.35-8.35C1.88-7.11 2.37-5.74 2.37-3.1C2.37-.46 1.92 .98 .35 2.22L.49 2.41C2.13 1.39 3.39-.85 3.39-3.06C3.39-5.49 2.18-7.42 .45-8.54Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq31-page1'&gt;
&lt;use x='.3' y='0' xlink:href='#eq31-g1-79'/&gt;
&lt;use x='9.91' y='0' xlink:href='#eq31-g1-185'/&gt;
&lt;use x='14.37' y='0' xlink:href='#eq31-g5-49'/&gt;
&lt;use x='20.35' y='0' xlink:href='#eq31-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;, but the increased cost would make this algorithm impractical for all but the largest of
kernels.&lt;/p&gt;
&lt;p&gt;This algorithm is incredibly fast for any kernel size, as long as the kernel is square and the image is 8-bit. This
lack of generality makes it uninteresting to include in DIPlib, which quickly switches to single-precision floating
point pixels to avoid rounding errors, and which uses isotropic (round) kernels by default everywhere.&lt;/p&gt;
&lt;h2&gt;Timing comparisons&lt;/h2&gt;
&lt;p&gt;The graph below shows the computational time for a square median filter.
The new DIPlib implementation is in blue, the constant-time OpenCV implementation is in red, and scikit-image&amp;rsquo;s
implementation (which actually calls SciPy ndimage&amp;rsquo;s implementation) in green.&lt;/p&gt;
&lt;p&gt;For each implementation, I used a single-precision floating-point image (continuous line), an 8-bit unsigned integer
version of the same image (dashed line), and that same image divided by 25, meaning that it only has integer values in
the range 0-10 (dotted line). This latter case should have many more repeated values within the kernel than the other
cases.&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="Timing the median filter in various Python libraries" src="/images/median_filter_diplib_vs_opencv_vs_skimage.png"&gt;&lt;/p&gt;
&lt;p&gt;One can see the kink in the blue lines for DIPlib between 11 and 13 pixels, where a different algorithm is chosen.
To the left, the curve is quadratic (&lt;span class="math"&gt;&lt;svg style="width: 2.910em; height: 1.265em; vertical-align: -0.241em; " viewBox=".3 -10.24 29.1 12.65"&gt;
&lt;title&gt;
\(\mathcal{O}(k^2)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq32-g8-50' d='M4.15-1.2L4.04-1.24C3.71-.74 3.6-.66 3.21-.66H1.12L2.59-2.2C3.36-3.01 3.7-3.68 3.7-4.36C3.7-5.23 3-5.9 2.09-5.9C1.61-5.9 1.15-5.71 .83-5.36C.55-5.07 .42-4.79 .27-4.17L.45-4.12C.8-4.98 1.12-5.26 1.72-5.26C2.45-5.26 2.95-4.76 2.95-4.03C2.95-3.35 2.55-2.53 1.82-1.76L.26-.1V0H3.67L4.15-1.2Z'/&gt;
&lt;path id='eq32-g4-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;path id='eq32-g1-79' d='M8.29-5.48C8.29-7.02 7.34-8.52 5.67-8.52C4.94-8.52 4.2-8.33 3.66-7.84C3.94-7.9 4.23-7.91 4.53-7.91C5.94-7.91 7.12-7.13 7.12-5.61C7.12-3.78 5.42-.2 3.29-.2C2.02-.2 1.39-1.37 1.39-2.5C1.39-4.54 3.1-7.06 5.22-7.35C4.9-7.5 4.59-7.53 4.23-7.53C2.18-7.53 .08-4.6 .08-2.69C.08-.73 1.41 .3 3.29 .3C6.21 .3 8.29-2.78 8.29-5.48Z'/&gt;
&lt;path id='eq32-g1-185' d='M3.51-8.54C1.79-7.42 .57-5.49 .57-3.06C.57-.85 1.83 1.39 3.48 2.41L3.62 2.22C2.05 .98 1.6-.46 1.6-3.1C1.6-5.74 2.08-7.11 3.62-8.35L3.51-8.54Z'/&gt;
&lt;path id='eq32-g1-186' d='M.45-8.54L.35-8.35C1.88-7.11 2.37-5.74 2.37-3.1C2.37-.46 1.92 .98 .35 2.22L.49 2.41C2.13 1.39 3.39-.85 3.39-3.06C3.39-5.49 2.18-7.42 .45-8.54Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq32-page1'&gt;
&lt;use x='.3' y='0' xlink:href='#eq32-g1-79'/&gt;
&lt;use x='9.91' y='0' xlink:href='#eq32-g1-185'/&gt;
&lt;use x='14.86' y='0' xlink:href='#eq32-g4-58'/&gt;
&lt;use x='21.12' y='-4.34' xlink:href='#eq32-g8-50'/&gt;
&lt;use x='26.01' y='0' xlink:href='#eq32-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;), to the right it is &lt;span class="math"&gt;&lt;svg style="width: 5.024em; height: 1.114em; vertical-align: -0.260em; " viewBox=".3 -8.54 50.24 11.14"&gt;
&lt;title&gt;
\(\mathcal{O}(k \log k)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq33-g8-103' d='M5.6-4.62V-5.09H4.68C4.44-5.09 4.26-5.12 4.03-5.2L3.76-5.3C3.44-5.42 3.12-5.48 2.81-5.48C1.7-5.48 .82-4.62 .82-3.54C.82-2.79 1.14-2.33 1.93-1.94L1.42-1.46C1.02-1.12 .87-.88 .87-.64C.87-.39 1.01-.25 1.5-.01C.66 .61 .33 1 .33 1.44C.33 2.07 1.26 2.6 2.39 2.6C3.29 2.6 4.22 2.29 4.84 1.79C5.29 1.42 5.49 1.04 5.49 .58C5.49-.15 4.93-.66 4.05-.69L2.51-.76C1.88-.79 1.58-.89 1.58-1.08C1.58-1.32 1.98-1.74 2.3-1.83C2.41-1.82 2.49-1.81 2.53-1.81C2.75-1.79 2.91-1.77 2.98-1.77C3.42-1.77 3.89-1.95 4.26-2.27C4.66-2.61 4.84-3.03 4.84-3.62C4.84-3.97 4.78-4.24 4.61-4.62H5.6ZM5.16 .76C5.16 1.45 4.25 1.92 2.91 1.92C1.86 1.92 1.17 1.57 1.17 1.05C1.17 .77 1.25 .62 1.75 .02C2.14 .11 3.1 .18 3.68 .18C4.76 .18 5.16 .33 5.16 .76ZM3.92-3.16C3.92-2.49 3.57-2.07 3.03-2.07C2.31-2.07 1.81-2.85 1.81-3.99V-4.03C1.81-4.73 2.14-5.15 2.69-5.15C3.06-5.15 3.37-4.94 3.56-4.59C3.78-4.17 3.92-3.62 3.92-3.16Z'/&gt;
&lt;path id='eq33-g8-108' d='M3.06 0V-.18C2.3-.23 2.17-.35 2.17-1V-8.11L2.12-8.13C1.5-7.93 1.05-7.81 .23-7.61V-7.42H.3C.43-7.43 .57-7.44 .67-7.44C1.05-7.44 1.17-7.28 1.17-6.72V-1.04C1.17-.39 1-.24 .25-.18V0H3.06Z'/&gt;
&lt;path id='eq33-g8-111' d='M5.6-2.79C5.6-4.35 4.5-5.48 2.98-5.48C1.43-5.48 .35-4.34 .35-2.69C.35-1.08 1.45 .12 2.95 .12S5.6-1.14 5.6-2.79ZM4.53-2.37C4.53-1.02 3.99-.21 3.1-.21C2.63-.21 2.19-.5 1.94-.98C1.61-1.6 1.42-2.43 1.42-3.28C1.42-4.41 1.98-5.15 2.82-5.15C3.82-5.15 4.53-4 4.53-2.37Z'/&gt;
&lt;path id='eq33-g4-58' d='M5.11-1.17L4.93-1.3C4.87-1.19 4.81-1.08 4.75-.99C4.54-.62 4.39-.49 4.22-.49C3.97-.49 3.73-.87 3.25-2.02L2.81-3.07C4.56-4.63 4.93-4.87 5.49-4.91V-5.1H3.31V-4.91H3.5C3.8-4.91 3.97-4.82 3.97-4.69C3.97-4.45 3.38-3.88 2.26-3.03L1.75-2.63L3.18-8.08L3.12-8.13C2.42-7.98 1.97-7.9 1.26-7.81V-7.62H1.46C1.87-7.62 2.05-7.54 2.07-7.32C2.05-7.12 1.98-6.81 1.81-6.25L1.7-5.86L1.68-5.75L.17 0H1.06L1.63-2.14L2.13-2.53C2.32-2 2.63-1.26 2.85-.85C3.23-.1 3.44 .13 3.79 .13C4.26 .13 4.56-.17 5.11-1.17Z'/&gt;
&lt;path id='eq33-g1-79' d='M8.29-5.48C8.29-7.02 7.34-8.52 5.67-8.52C4.94-8.52 4.2-8.33 3.66-7.84C3.94-7.9 4.23-7.91 4.53-7.91C5.94-7.91 7.12-7.13 7.12-5.61C7.12-3.78 5.42-.2 3.29-.2C2.02-.2 1.39-1.37 1.39-2.5C1.39-4.54 3.1-7.06 5.22-7.35C4.9-7.5 4.59-7.53 4.23-7.53C2.18-7.53 .08-4.6 .08-2.69C.08-.73 1.41 .3 3.29 .3C6.21 .3 8.29-2.78 8.29-5.48Z'/&gt;
&lt;path id='eq33-g1-185' d='M3.51-8.54C1.79-7.42 .57-5.49 .57-3.06C.57-.85 1.83 1.39 3.48 2.41L3.62 2.22C2.05 .98 1.6-.46 1.6-3.1C1.6-5.74 2.08-7.11 3.62-8.35L3.51-8.54Z'/&gt;
&lt;path id='eq33-g1-186' d='M.45-8.54L.35-8.35C1.88-7.11 2.37-5.74 2.37-3.1C2.37-.46 1.92 .98 .35 2.22L.49 2.41C2.13 1.39 3.39-.85 3.39-3.06C3.39-5.49 2.18-7.42 .45-8.54Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq33-page1'&gt;
&lt;use x='.3' y='0' xlink:href='#eq33-g1-79'/&gt;
&lt;use x='9.91' y='0' xlink:href='#eq33-g1-185'/&gt;
&lt;use x='14.86' y='0' xlink:href='#eq33-g4-58'/&gt;
&lt;use x='23.12' y='0' xlink:href='#eq33-g8-108'/&gt;
&lt;use x='26.44' y='0' xlink:href='#eq33-g8-111'/&gt;
&lt;use x='32.42' y='0' xlink:href='#eq33-g8-103'/&gt;
&lt;use x='40.88' y='0' xlink:href='#eq33-g4-58'/&gt;
&lt;use x='47.14' y='0' xlink:href='#eq33-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;. Scikit-image&amp;rsquo;s
lines are quadratic, the difference with DIPlib&amp;rsquo;s line is partially due to quality of implementation, but mostly
because DIPlib uses all cores whereas scikit-image is single-threaded. For both implementations, reducing the number
of values in the input image strongly reduces the computational cost (dotted lines). Quickselect is faster when many
values are identical, and our binary tree implementation has strongly reduced number of nodes when each node represents
many pixels with the same value.&lt;/p&gt;
&lt;p&gt;The constant-time implementation in OpenCV is not actually constant time. It seems that a different algorithm
is used for kernels up to 15 pixels, the constant-time algorithm kicks in at 19 pixels.
It also looks like the selection of which algorithm to use was tuned on a machine with different characteristics
to mine, meaning that a different algorithm was chosen for some sizes where the constant-time implementation would
be more efficient. Because of this, the filter is significantly slower for a kernel of size 15 than for a kernel
of size 19.&lt;/p&gt;
&lt;p&gt;We can see the same issue in the DIPlib implementation for the dotted line, where the binary tree implementation is
faster than what I measured earlier because of the reduced number of different values in the image.
Picking the most efficient algorithm under all circumstances is an unattainable dream, I am willing to live with
small imperfections like this.&lt;/p&gt;
&lt;div class="admonition ref"&gt;
&lt;p class="admonition-title"&gt;Literature&lt;/p&gt;
&lt;ol&gt;
&lt;li class="link_target" id="ref-huang"&gt;T.S. Huang, G.J. Yang, G.Y. Tang, &amp;ldquo;A fast two-dimensional median filtering algorithm&amp;rdquo;,
    IEEE Transactions on Acoustics, Speech, and Signal Processing 27(1):13&amp;ndash;18, 1979.&lt;/li&gt;
&lt;li class="link_target" id="ref-hirai"&gt;Y. Hirai, K. Yamamoto, &amp;ldquo;Balancing weight-balanced trees&amp;rdquo;,
    Journal of Functional Programming 21(3):287&amp;ndash;307, 2011.&lt;/li&gt;
&lt;li class="link_target" id="ref-perreault"&gt;S. Perreault and P. Hérbert, &amp;ldquo;Median Filtering in Constant Time&amp;rdquo;,
    IEEE Transactions on Image Processing 16(9):2389&amp;ndash;2394, 2007.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="algorithms"></category><category term="median filter"></category><category term="percentile filter"></category><category term="rank filter"></category><category term="filtering"></category><category term="binary tree"></category></entry><entry><title>Morphological reconstruction, or don't trust old timings</title><link href="https://www.crisluengo.net/archives/1135" rel="alternate"></link><published>2021-09-27T00:00:00-06:00</published><updated>2021-09-27T00:00:00-06:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2021-09-27:/archives/1135</id><summary type="html">&lt;p&gt;DIPlib 3.1.0 was released last week (see the &lt;a href="https://diplib.org/changelogs/diplib_3.1.0.html"&gt;change log&lt;/a&gt;).
One of the changes in this release is a very significant speed improvement in the morphological
reconstruction (&lt;a href="https://diplib.org/diplib-docs/morphology.html#dip-MorphologicalReconstruction-dip-Image-CL-dip-Image-CL-dip-Image-L-dip-uint--dip-String-CL"&gt;&lt;code&gt;dip::MorphologicalReconstruction&lt;/code&gt;&lt;/a&gt;).
This is quite an interesting story, so I decided to write it down.&lt;/p&gt;
&lt;p&gt;The morphological reconstruction algorithm can be …&lt;/p&gt;</summary><content type="html">&lt;p&gt;DIPlib 3.1.0 was released last week (see the &lt;a href="https://diplib.org/changelogs/diplib_3.1.0.html"&gt;change log&lt;/a&gt;).
One of the changes in this release is a very significant speed improvement in the morphological
reconstruction (&lt;a href="https://diplib.org/diplib-docs/morphology.html#dip-MorphologicalReconstruction-dip-Image-CL-dip-Image-CL-dip-Image-L-dip-uint--dip-String-CL"&gt;&lt;code&gt;dip::MorphologicalReconstruction&lt;/code&gt;&lt;/a&gt;).
This is quite an interesting story, so I decided to write it down.&lt;/p&gt;
&lt;p&gt;The morphological reconstruction algorithm can be described as an iterative dilation or erosion of
an image &lt;em&gt;f&lt;/em&gt; conditioned by an image &lt;em&gt;g&lt;/em&gt;. That is, we iterate the following statement until the image
&lt;em&gt;f&lt;/em&gt; doesn&amp;rsquo;t change any more:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dilation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Of course this can be implemented much more efficiently. In 1993, Luc Vincent (on a roll publishing
efficient algorithms for quite a few morphological operators, including the watershed) published
a paper describing four possible implementations of the reconstruction operator&amp;nbsp;&lt;a href="#ref-vincent"&gt;[1]&lt;/a&gt;.
The fourth method, the most efficient one, is a combination of two other methods: first it does a
raster and an anti-raster scan (see box below for an explanation), then it uses a FIFO queue to
complete the propagation.
In 2004, Robinson and Whelan published a paper describing a much more efficient algorithm,
based on a priority queue&amp;nbsp;&lt;a href="#ref-robinson"&gt;[2]&lt;/a&gt;. This table from the paper shows the results of experiments on 2D images
(there&amp;rsquo;s a similar one for 3D):&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="Table from Robinson and Whelan (2004)" src="/images/Robinson2004_table.png"&gt;&lt;/p&gt;
&lt;p&gt;Here, A, B, C and D are the algorithms described by Vincent, with D the most efficient one. E is the
priority-queue algorithm by Robinson and Whelan. These experiments clearly show that the priority-queue
algorithm is significantly more efficient. The reasoning also makes sense: the priority queue ensures
that each pixel gets updated only once, whereas the FIFO queue potentially updates some pixels many
times. And so this is the algorithm that I implemented in DIPlib many years ago.&lt;/p&gt;
&lt;div class="admonition blue"&gt;
&lt;p class="admonition-title"&gt;Raster scan&lt;/p&gt;
&lt;p&gt;A raster scan is a scan of the image in the order in which it is stored in memory. Typically, this
is row by row. The scan starts at the top-left pixel, visits each pixel on that top row in order,
then continues with the next row, etc. This is the scanning order for most acquisition devices,
and therefore also the order in which images are typically stored in memory and on disk.&lt;/p&gt;
&lt;p&gt;During a raster scan, one propagates values to the right and down. Information from the top-left
of the image can thus make its way all the way to the right and bottom of the image. An anti-raster
scan is a scan in the opposite order, starting at the bottom-right pixel, scanning left along
the row, and then up to process each row. Here, information is propagated to the left and up.&lt;/p&gt;
&lt;p&gt;The combination of a raster scan and an anti-raster scan is sufficient for some algorithms to
propagate all the information in the image to where it needs to go. For example, one can compute
a distance transform in 2D using just one raster scan and one anti-raster scan.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;About two months ago, I was replacing an old, no longer supported mathematical morphology library
(used in some code I maintain at work) with DIPlib. To do so, I compared each function in the old library
with the equivalent DIPlib function to make sure the results were identical. At the same time I compared
running times. Some functions were a bit faster in one library, some a bit faster in the other, and some,
like basic dilations with larger structuring elements, were significantly faster in DIPlib.
Of course, I took the opportunity to improve the implementation of some of these functions that were
a little slower in DIPlib, where obvious gains could be made. But the morphological reconstruction
function really surprised me. The old library&amp;rsquo;s implementation was ten times faster than DIPlib&amp;rsquo;s!&lt;/p&gt;
&lt;p&gt;Examining the DIPlib implementation, I noticed that I had been lazy when making the original implementation.
Marking border pixels in a flag image allows the algorithm to skip testing coordinates before accessing
neighbors for most pixels in the image, speeding up processing by about 20%. Ensuring consistent strides
so that the same offsets can be used in the &lt;em&gt;f&lt;/em&gt;, &lt;em&gt;g&lt;/em&gt; and intermediate flag images sped up processing
by another 20%. And a few small things here and there rounded out improvements to a total of about 50%.
But this meant that the implementation was still five times slower than that in the old library.&lt;/p&gt;
&lt;p&gt;So I took a look at the code in the old library, and found that it implemented the algorithm proposed by
Vincent (method D in the table above), which was supposed to be anywhere from two times to 14 times
slower than the priority queue algorithm.&lt;/p&gt;
&lt;p&gt;I was doing my timing comparison on quite a large image. Robinson and Whelan did their experiments on quite
small images, a much larger fraction of which would have fit in their cache. And a priority-queue algorithm
processes pixels in the image in a rather disorganized way. Putting these points together, it becomes obvious
that I was seeing a cache miss for just about every pixel processed, whereas Robinson and Whelan were seeing
a lot fewer cache misses, and my cache misses were likely more expensive too.
I believe that this is why I was seeing such slow execution in the DIPlib implementation.
In contrast, Vincent&amp;rsquo;s algorithm uses a raster scan and an anti-raster scan to do a large chunk of the propagation.
These scans are very cache-friendly.&lt;/p&gt;
&lt;p&gt;So I rewrote the DIPlib implementation to be a combination of the two algorithms: it first applies a
raster scan and an anti-raster scan to do a large chunk of the work. The second scan, in anti-raster order,
also initializes a priority queue, enqueueing all pixels from which it is possible to propagate right and down
(since this scan is propagating left and up). Next, the algorithm processes the pixels in the priority queue,
enqueuing neighbors from which further propagation is possible, until the queue is empty. Each pixel is
thus updated no more than three times. This implementation was, in my tests, a bit faster than Vincent&amp;rsquo;s,
which uses a FIFO queue and therefore each pixel can potentially be updated many times.&lt;/p&gt;
&lt;p&gt;So, lessons learned:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;One cannot make assumptions about which algorithm or implementation is faster without
    actually measuring times.&lt;/li&gt;
&lt;li&gt;Timings from 15 years ago are likely no longer relevant.&lt;/li&gt;
&lt;li&gt;Sometimes it&amp;rsquo;s good to take another look at older, superseded algorithms.&lt;/li&gt;
&lt;li&gt;What is true for small data is not necessarily true for large data.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="admonition ref"&gt;
&lt;p class="admonition-title"&gt;Literature&lt;/p&gt;
&lt;ol&gt;
&lt;li class="link_target" id="ref-vincent"&gt;L. Vincent, &amp;ldquo;Morphological grayscale reconstruction in image analysis: applications and efficient algorithms&amp;rdquo;,
    IEEE Transactions on Image Processing 2(2):176&amp;ndash;201, 1993&lt;/li&gt;
&lt;li class="link_target" id="ref-robinson"&gt;K. Robinson and P.F. Whelan, &amp;ldquo;Efficient morphological reconstruction: a downhill filter&amp;rdquo;,
    Pattern Recognition Letters 25:1759&amp;ndash;1767, 2004&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="algorithms"></category><category term="mathematical morphology"></category><category term="reconstruction"></category><category term="DIPlib"></category><category term="timing"></category><category term="performance"></category></entry><entry><title>Filling an image with grid points</title><link href="https://www.crisluengo.net/archives/1082" rel="alternate"></link><published>2019-01-22T23:09:00-07:00</published><updated>2019-01-22T23:09:00-07:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2019-01-22:/archives/1082</id><summary type="html">&lt;p&gt;Several algorithms require a set of uniformly distributed points across the image. For example, superpixel algorithms
typically start with a regular grid. A rectangular grid of points is trivial to draw into an image. One simply generates
the set of points of a grid that covers the image, rounds those …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Several algorithms require a set of uniformly distributed points across the image. For example, superpixel algorithms
typically start with a regular grid. A rectangular grid of points is trivial to draw into an image. One simply generates
the set of points of a grid that covers the image, rounds those points to integer coordinates, and sets those pixels. A
rotated grid is a bit more challenging&amp;ndash;one needs to compute bounds of the rotated grid such that the full image is
covered. With other than rectangular grids (for example a hexagonal grid) the math gets a bit more complicated, but this
can still be computed. But how to generalize such an algorithm to three dimensions? And to an arbitrary number of
dimensions?&lt;/p&gt;
&lt;p&gt;I played around with this task for a bit and came up with a very simple, general solution.&lt;/p&gt;
&lt;p&gt;First, let&amp;rsquo;s review how points on a grid can be generated in general. A grid is defined by a set of basis vectors. These
vectors do not need to be orthogonal. For example, for an hexagonal grid, the basis vectors are &lt;code&gt;v1 = [1,0]&lt;/code&gt;
and &lt;code&gt;v2 = [cos(60°),sin(60°)]&lt;/code&gt; (using degrees, not radian, for simplicity of notation). Given these two
vectors, &lt;code&gt;a*v1 + b*v2&lt;/code&gt;, with &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; integers, yields the grid points.&lt;/p&gt;
&lt;p&gt;This notion generalizes trivially to any number of dimensions. For example, in 3D the FCC (face-centered cubic) grid is
given by vectors &lt;code&gt;v1 = [1,1,0]&lt;/code&gt;, &lt;code&gt;v2 = [1,0,1]&lt;/code&gt; and &lt;code&gt;v3 = [0,1,1]&lt;/code&gt;. There are always as many vectors as number of
dimensions. These vectors form the columns of a matrix &lt;code&gt;M&lt;/code&gt;, such that &lt;code&gt;M*s&lt;/code&gt; is a grid point, with &lt;code&gt;s&lt;/code&gt; a column vector of
integer values. This &lt;code&gt;s&lt;/code&gt; can be thought of as the grid index.&lt;/p&gt;
&lt;p&gt;So the difficulty is to determine which integer-valued vectors &lt;code&gt;p&lt;/code&gt; lead to grid points that intersect the image. For a
rectangular grid that is aligned with the axes, it is also a rectangular domain of &lt;code&gt;s&lt;/code&gt; that needs to be considered. When
rotating a 2D grid, it is still quite simple to determine the larger rectangular domain that completely covers the
image, then discard the points that do not intersect the image. The same can be said for a non-rectangular grid in 2D,
it is always possible to find the rectangular domain of &lt;code&gt;s&lt;/code&gt; that covers the whole image. But these computations are more
complex in the 3D case, and quite difficult to generalize to an arbitrary number of dimensions. For higher-dimensional
spaces, these domains of &lt;code&gt;s&lt;/code&gt; become increasingly large, making the point discarding solution increasingly inefficient.&lt;/p&gt;
&lt;p&gt;Instead, we can start with the image domain itself, and for each pixel find out if it is the pixel nearest a grid point.
For this, we first need to find the inverse of the matrix &lt;code&gt;M&lt;/code&gt;, let&amp;rsquo;s call it &lt;code&gt;inv_M&lt;/code&gt;. For a given
pixel &lt;code&gt;p = [x,y,z,...]&lt;/code&gt; the expression &lt;code&gt;s = round(inv_M*p)&lt;/code&gt; is an integer grid index. As we saw before, &lt;code&gt;q = M*s&lt;/code&gt; is the
coordinates of the grid point for grid index &lt;code&gt;s&lt;/code&gt;. &lt;code&gt;round(q)&lt;/code&gt; is the nearest pixel. If &lt;code&gt;round(q)==p&lt;/code&gt;, then the pixel
at &lt;code&gt;p&lt;/code&gt; should be marked as a grid point.&lt;/p&gt;
&lt;p&gt;Using DIPimage 3, we can write as follows (this code will not work in DIPimage 2.9 or earlier without some changes):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;% Define image domain&lt;/span&gt;
&lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;140&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c"&gt;% Define grid&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],[&lt;/span&gt;&lt;span class="nb"&gt;cosd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="nb"&gt;sind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c"&gt;% Let&amp;#39;s rotate the grid too&lt;/span&gt;
&lt;span class="n"&gt;phi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;pi&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phi&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="nb"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phi&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phi&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="nb"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phi&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c"&gt;% The inverse of the grid matrix&lt;/span&gt;
&lt;span class="n"&gt;inv_M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c"&gt;% Convert M and inv_M to dip_image objects so we can do the multiplication&lt;/span&gt;
&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dip_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;(:),[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="c"&gt;% Create single-pixel tensor image&lt;/span&gt;
&lt;span class="n"&gt;inv_M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dip_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inv_M&lt;/span&gt;&lt;span class="p"&gt;(:),[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;% ...yes, this syntax is a bit awkward still, hopefully this will be easier soon&lt;/span&gt;
&lt;span class="c"&gt;% Do the computation&lt;/span&gt;
&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="c"&gt;% An image where each pixel&amp;#39;s value is it&amp;#39;s own coordinate vector&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inv_M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;grid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;tensor&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p class="centering"&gt;&lt;img alt="The hexagonal grid generated with the code above" src="/images/grid_hexagonal.png"&gt;&lt;/p&gt;
&lt;p&gt;Note that in the code above, we determined the image domain &lt;code&gt;sz&lt;/code&gt; as 2D, and defined the grid matrix &lt;code&gt;M&lt;/code&gt; as 2D. Other
than that, there is no mention of the dimensionality. Changing these two matrices we can instead generate a grid in a 3D
image, or a 5D image.&lt;/p&gt;
&lt;p&gt;In case you don&amp;rsquo;t want to bother installing DIPimage, below is a version of the code that uses only standard MATLAB
functions. This version, however, is explicitly for 2D.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;% Define image domain&lt;/span&gt;
&lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;140&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c"&gt;% Define grid&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],[&lt;/span&gt;&lt;span class="nb"&gt;cosd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="nb"&gt;sind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c"&gt;% Let&amp;#39;s rotate the grid too&lt;/span&gt;
&lt;span class="n"&gt;phi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;pi&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phi&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="nb"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phi&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phi&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="nb"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phi&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c"&gt;% The inverse of the grid matrix&lt;/span&gt;
&lt;span class="n"&gt;inv_M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c"&gt;% Do the computation&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;meshgrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;(:).&lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;(:).&lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inv_M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;grid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;grid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;reshape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="algorithms"></category><category term="grid"></category></entry><entry><title>Color quantization, minimizing variance, and k-d trees</title><link href="https://www.crisluengo.net/archives/932" rel="alternate"></link><published>2018-05-26T01:22:00-06:00</published><updated>2018-05-26T01:22:00-06:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2018-05-26:/archives/932</id><summary type="html">&lt;p&gt;A &lt;a href="https://stackoverflow.com/a/49715101/7328782"&gt;recent question on Stack Overflow&lt;/a&gt; really stroke my curiosity. The
question was about why &lt;a href="https://www.mathworks.com/help/matlab/ref/rgb2ind.html"&gt;MATLAB&amp;rsquo;s &lt;code&gt;rgb2ind&lt;/code&gt;&lt;/a&gt; function is so much
faster at finding a color table than k-means clustering. It turns out that the algorithm in &lt;code&gt;rgb2ind&lt;/code&gt;, which they call
&amp;ldquo;Minimum Variance Quantization&amp;rdquo;, is not documented anywhere.
A …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A &lt;a href="https://stackoverflow.com/a/49715101/7328782"&gt;recent question on Stack Overflow&lt;/a&gt; really stroke my curiosity. The
question was about why &lt;a href="https://www.mathworks.com/help/matlab/ref/rgb2ind.html"&gt;MATLAB&amp;rsquo;s &lt;code&gt;rgb2ind&lt;/code&gt;&lt;/a&gt; function is so much
faster at finding a color table than k-means clustering. It turns out that the algorithm in &lt;code&gt;rgb2ind&lt;/code&gt;, which they call
&amp;ldquo;Minimum Variance Quantization&amp;rdquo;, is not documented anywhere.
A &lt;a href="https://www.mathworks.com/help/images/reduce-the-number-of-colors-in-an-image.html#f8-13153"&gt;different page in the MATLAB documentation&lt;/a&gt;
does give away a little bit about this algorithm: it shows a partitioning of the RGB cube with what looks
like &lt;a href="https://en.wikipedia.org/wiki/K-d_tree"&gt;a k-d tree&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So I ended up thinking quite a bit about how a k-d tree, minimizing variance, and color quantization could intersect.
I ended up devising an algorithm that works quite well. It is implemented in DIPlib 3.0
as &lt;code&gt;dip::Minimum​Variance​Partitioning&lt;/code&gt;
(&lt;a href="https://github.com/DIPlib/diplib/blob/master/src/segmentation/minimum_variance_partitioning.cpp"&gt;source code&lt;/a&gt;).
I&amp;rsquo;d like to describe the algorithm here in a bit of detail.&lt;/p&gt;
&lt;h2&gt;Partitioning a color histogram&lt;/h2&gt;
&lt;p&gt;The RGB cube is a 3D space where each pixel can be represented as a point. Put all pixels in an image in there, and you
get a point cloud. Clustering of these points leads to a partitioning of the RGB cube into cells (or regions, clusters,
tiles, whatever you want to call them) that each represent a group of similar colors present in the image. For each cell
one can decide on a single color (e.g. the mean) that will represent all colors within that cell. This leads to a short
list of colors, a color palette. Next, for each pixel in the image, one finds the cell it falls into, and replaces it
with the color chosen for that cell. This transforms the image such that only the colors in the color palette are used.
The colors are now quantized, as there are many fewer colors than in the original image. The more cells the RGB cube is
partitioned in, the more the transformed image will look like the original image.&lt;/p&gt;
&lt;p&gt;This color quantization was a very important technique in the days of 256-color displays, and GIF images, as the quality
of the image displayed depended very much on how well the set of 256 colors was chosen.&lt;/p&gt;
&lt;p&gt;But note that it is not necessary to go through a clustering process using all pixels in the image. It is much more
efficient (typically) to use a color histogram instead. The color histogram is simply a discretization of the RGB cube,
where each element is a count of the number of pixels with that color. That is, a color histogram is a 3D array of, say,
32x32x32 or 64x64x64 values, containing pixel counts corresponding to each combination of RGB values. For a normal
photograph (today often around 10 or 15 million pixels) this represents a reduction of the number of data points of 40x
to 400x.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/K-means_clustering"&gt;K-means clustering&lt;/a&gt; is the obvious choice for clustering, except that
it is quite expensive. Current common methods are the &lt;a href="https://en.wikipedia.org/wiki/Median_cut"&gt;median cut algorithm&lt;/a&gt;
and a &lt;a href="https://en.wikipedia.org/wiki/Octree"&gt;clustering using octrees&lt;/a&gt;. The median cut recursively splits the RGB cube
along its longest axis, such that each half represents about half the pixels. This leads to a partitioning where each
cell represents the same number of pixels. A k-d tree is the data structure used for this algorithm (see below for
details). The other popular method builds an octree over the histogram, and merges groups of 8 cells recursively until
the right number of cells remain. The least occupied cells are the first to be merged.&lt;/p&gt;
&lt;h2&gt;K-d trees&lt;/h2&gt;
&lt;p&gt;K-d trees are a spatial indexing data structure. It is a binary tree (each node has either zero or two children), where
the leaf nodes (the ones with zero children) represent the cells of the partition. Each branch node has a threshold
value, which operates along one axis. Thus, each node splits the space in two, and all cells are rectangular (or boxes
in 3D). Finding the cell that a point in space belongs to is O(log &lt;em&gt;n&lt;/em&gt;), for &lt;em&gt;n&lt;/em&gt; cells: starting at the root node,
follow the left or right child depending on the threshold value stored in the node, until you reach a leaf node.&lt;/p&gt;
&lt;p&gt;The k-d tree thus is very flexible in how the space is partitioned, but it cannot generate splits at arbitrary angles,
like you can get with k-means clustering. K-means clustering produces
a &lt;a href="https://en.wikipedia.org/wiki/Voronoi_diagram"&gt;Voronoi tessellation&lt;/a&gt; of the space, induced by the &lt;em&gt;k&lt;/em&gt; centroids of
the cells. Each cell is composed of the points that are closer to the cell&amp;rsquo;s centroid than to any other of the
centroids.&lt;/p&gt;
&lt;p&gt;In contrast, in the octree data structure, each node has either zero or eight children, where the children are all the
same size and shape. That is, each cell is divided into eighths by splitting it in half along each dimension. This means
that the nodes do not need to store a threshold value and direction, but it also means that meaningful clusters will be
split, and thus the octree typically requires more cells than an equivalent k-d tree.&lt;/p&gt;
&lt;p&gt;Here are example partitionings of a 2D space into 25 cells using a k-d tree (left), a Voronoi graph (middle), and an
octree (actually a quadtree, the 2D equivalent, right):&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="k-d tree partitioning" src="/images/test_clustering_kdtree.png" width="32%"&gt; &lt;img alt="Voronoi partitioning" src="/images/test_clustering_kmeans.png" width="32%"&gt; &lt;img alt="octree partitioning" src="/images/test_clustering_octree.png" width="32%"&gt;&lt;/p&gt;
&lt;h2&gt;Minimizing variance&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Otsu%27s_method"&gt;Otsu&amp;rsquo;s algorithm for finding a global threshold&lt;/a&gt; finds a threshold that
minimizes the (weighted) sum of the variances of the two classes. This sum is always smaller than the variance of all
the data combined. Imagine data with a bimodal distribution, half the data points have a Gaussian distribution with
mean &lt;span class="math"&gt;&lt;svg style="width: 1.255em; height: 0.705em; vertical-align: -0.180em; " viewBox=".08 -5.25 12.55 7.05"&gt;
&lt;title&gt;
\(m_1\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq1-g5-49' d='M3.44 0V-.13C2.75-.14 2.61-.23 2.61-.65V-5.89L2.54-5.9L.97-5.11V-4.99C1.07-5.03 1.17-5.07 1.21-5.08C1.36-5.14 1.51-5.18 1.6-5.18C1.78-5.18 1.86-5.05 1.86-4.77V-.81C1.86-.52 1.79-.32 1.65-.24C1.52-.17 1.4-.14 1.03-.13V0H3.44Z'/&gt;
&lt;path id='eq1-g1-60' d='M8.38-1.25L8.21-1.39L7.96-1.08C7.61-.64 7.38-.45 7.18-.45C7.09-.45 7.04-.51 7.04-.64C7.04-.69 7.09-.91 7.16-1.18L7.97-4.17C7.98-4.23 8-4.42 8-4.5C8-4.93 7.71-5.25 7.31-5.25C7.07-5.25 6.86-5.18 6.57-5C5.99-4.63 5.49-4.05 4.66-2.73C4.95-3.64 5.13-4.31 5.13-4.54C5.13-4.97 4.86-5.25 4.45-5.25C3.73-5.25 2.89-4.47 1.8-2.74L2.49-5.23L2.45-5.25L.55-4.88V-4.69H.85C1.17-4.69 1.31-4.61 1.31-4.43C1.31-4.29 1.04-3.2 .61-1.7L.14 0H1.04C1.61-1.88 1.79-2.33 2.19-2.95C2.86-3.97 3.57-4.63 3.98-4.63C4.13-4.63 4.22-4.53 4.22-4.34C4.22-4.18 3.89-2.92 3.38-1.1L3.08 0H3.98C4.44-1.83 4.68-2.43 5.26-3.25C5.86-4.09 6.47-4.63 6.82-4.63C6.96-4.63 7.06-4.53 7.06-4.38C7.06-4.32 7.04-4.2 6.98-3.97L6.29-1.23C6.17-.76 6.13-.56 6.13-.44C6.13-.08 6.3 .11 6.61 .11C7.17 .11 7.69-.29 8.33-1.17L8.38-1.25Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq1-page1'&gt;
&lt;use x='.08' y='0' xlink:href='#eq1-g1-60'/&gt;
&lt;use x='9.19' y='1.79' xlink:href='#eq1-g5-49'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; and variance &lt;span class="math"&gt;&lt;svg style="width: 1.271em; height: 1.450em; vertical-align: -0.426em; " viewBox=".16 -10.24 12.71 14.5"&gt;
&lt;title&gt;
\(\sigma_1^2\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq2-g5-49' d='M3.44 0V-.13C2.75-.14 2.61-.23 2.61-.65V-5.89L2.54-5.9L.97-5.11V-4.99C1.07-5.03 1.17-5.07 1.21-5.08C1.36-5.14 1.51-5.18 1.6-5.18C1.78-5.18 1.86-5.05 1.86-4.77V-.81C1.86-.52 1.79-.32 1.65-.24C1.52-.17 1.4-.14 1.03-.13V0H3.44Z'/&gt;
&lt;path id='eq2-g5-50' d='M4.15-1.2L4.04-1.24C3.71-.74 3.6-.66 3.21-.66H1.12L2.59-2.2C3.36-3.01 3.7-3.68 3.7-4.36C3.7-5.23 3-5.9 2.09-5.9C1.61-5.9 1.15-5.71 .83-5.36C.55-5.07 .42-4.79 .27-4.17L.45-4.12C.8-4.98 1.12-5.26 1.72-5.26C2.45-5.26 2.95-4.76 2.95-4.03C2.95-3.35 2.55-2.53 1.82-1.76L.26-.1V0H3.67L4.15-1.2Z'/&gt;
&lt;path id='eq2-g1-102' d='M8.05-5.25H3.7C1.91-5.25 .3-3.75 .3-1.75C.3-.55 .94 .13 2.16 .13C3.62 .13 5.44-1.29 5.44-3.41C5.44-4.09 5.22-4.35 5.22-4.36H7.81L8.05-5.25ZM4.54-3.39C4.54-1.62 3.55-.17 2.32-.17C1.41-.17 1.29-1.36 1.29-1.72C1.29-4.01 2.51-4.93 3.39-4.93C3.84-4.93 4.54-4.81 4.54-3.39Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq2-page1'&gt;
&lt;use x='.16' y='0' xlink:href='#eq2-g1-102'/&gt;
&lt;use x='8.72' y='-4.34' xlink:href='#eq2-g5-50'/&gt;
&lt;use x='7.56' y='4.26' xlink:href='#eq2-g5-49'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;, the other half a similar distribution with mean &lt;span class="math"&gt;&lt;svg style="width: 1.326em; height: 0.705em; vertical-align: -0.180em; " viewBox=".08 -5.25 13.26 7.05"&gt;
&lt;title&gt;
\(m_2\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq3-g5-50' d='M4.15-1.2L4.04-1.24C3.71-.74 3.6-.66 3.21-.66H1.12L2.59-2.2C3.36-3.01 3.7-3.68 3.7-4.36C3.7-5.23 3-5.9 2.09-5.9C1.61-5.9 1.15-5.71 .83-5.36C.55-5.07 .42-4.79 .27-4.17L.45-4.12C.8-4.98 1.12-5.26 1.72-5.26C2.45-5.26 2.95-4.76 2.95-4.03C2.95-3.35 2.55-2.53 1.82-1.76L.26-.1V0H3.67L4.15-1.2Z'/&gt;
&lt;path id='eq3-g1-60' d='M8.38-1.25L8.21-1.39L7.96-1.08C7.61-.64 7.38-.45 7.18-.45C7.09-.45 7.04-.51 7.04-.64C7.04-.69 7.09-.91 7.16-1.18L7.97-4.17C7.98-4.23 8-4.42 8-4.5C8-4.93 7.71-5.25 7.31-5.25C7.07-5.25 6.86-5.18 6.57-5C5.99-4.63 5.49-4.05 4.66-2.73C4.95-3.64 5.13-4.31 5.13-4.54C5.13-4.97 4.86-5.25 4.45-5.25C3.73-5.25 2.89-4.47 1.8-2.74L2.49-5.23L2.45-5.25L.55-4.88V-4.69H.85C1.17-4.69 1.31-4.61 1.31-4.43C1.31-4.29 1.04-3.2 .61-1.7L.14 0H1.04C1.61-1.88 1.79-2.33 2.19-2.95C2.86-3.97 3.57-4.63 3.98-4.63C4.13-4.63 4.22-4.53 4.22-4.34C4.22-4.18 3.89-2.92 3.38-1.1L3.08 0H3.98C4.44-1.83 4.68-2.43 5.26-3.25C5.86-4.09 6.47-4.63 6.82-4.63C6.96-4.63 7.06-4.53 7.06-4.38C7.06-4.32 7.04-4.2 6.98-3.97L6.29-1.23C6.17-.76 6.13-.56 6.13-.44C6.13-.08 6.3 .11 6.61 .11C7.17 .11 7.69-.29 8.33-1.17L8.38-1.25Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq3-page1'&gt;
&lt;use x='.08' y='0' xlink:href='#eq3-g1-60'/&gt;
&lt;use x='9.19' y='1.79' xlink:href='#eq3-g5-50'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; and variance &lt;span class="math"&gt;&lt;svg style="width: 1.271em; height: 1.450em; vertical-align: -0.426em; " viewBox=".16 -10.24 12.71 14.5"&gt;
&lt;title&gt;
\(\sigma_2^2\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq4-g5-50' d='M4.15-1.2L4.04-1.24C3.71-.74 3.6-.66 3.21-.66H1.12L2.59-2.2C3.36-3.01 3.7-3.68 3.7-4.36C3.7-5.23 3-5.9 2.09-5.9C1.61-5.9 1.15-5.71 .83-5.36C.55-5.07 .42-4.79 .27-4.17L.45-4.12C.8-4.98 1.12-5.26 1.72-5.26C2.45-5.26 2.95-4.76 2.95-4.03C2.95-3.35 2.55-2.53 1.82-1.76L.26-.1V0H3.67L4.15-1.2Z'/&gt;
&lt;path id='eq4-g1-102' d='M8.05-5.25H3.7C1.91-5.25 .3-3.75 .3-1.75C.3-.55 .94 .13 2.16 .13C3.62 .13 5.44-1.29 5.44-3.41C5.44-4.09 5.22-4.35 5.22-4.36H7.81L8.05-5.25ZM4.54-3.39C4.54-1.62 3.55-.17 2.32-.17C1.41-.17 1.29-1.36 1.29-1.72C1.29-4.01 2.51-4.93 3.39-4.93C3.84-4.93 4.54-4.81 4.54-3.39Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq4-page1'&gt;
&lt;use x='.16' y='0' xlink:href='#eq4-g1-102'/&gt;
&lt;use x='8.72' y='-4.34' xlink:href='#eq4-g5-50'/&gt;
&lt;use x='7.56' y='4.26' xlink:href='#eq4-g5-50'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;.
As long as &lt;span class="math"&gt;&lt;svg style="width: 4.158em; height: 0.853em; vertical-align: -0.179em; " viewBox=".08 -6.74 41.58 8.53"&gt;
&lt;title&gt;
\(m_1 \neq m_2\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq5-g8-49' d='M3.44 0V-.13C2.75-.14 2.61-.23 2.61-.65V-5.89L2.54-5.9L.97-5.11V-4.99C1.07-5.03 1.17-5.07 1.21-5.08C1.36-5.14 1.51-5.18 1.6-5.18C1.78-5.18 1.86-5.05 1.86-4.77V-.81C1.86-.52 1.79-.32 1.65-.24C1.52-.17 1.4-.14 1.03-.13V0H3.44Z'/&gt;
&lt;path id='eq5-g8-50' d='M4.15-1.2L4.04-1.24C3.71-.74 3.6-.66 3.21-.66H1.12L2.59-2.2C3.36-3.01 3.7-3.68 3.7-4.36C3.7-5.23 3-5.9 2.09-5.9C1.61-5.9 1.15-5.71 .83-5.36C.55-5.07 .42-4.79 .27-4.17L.45-4.12C.8-4.98 1.12-5.26 1.72-5.26C2.45-5.26 2.95-4.76 2.95-4.03C2.95-3.35 2.55-2.53 1.82-1.76L.26-.1V0H3.67L4.15-1.2Z'/&gt;
&lt;path id='eq5-g1-60' d='M6.84-1.52V-2.19H3.79L4.48-3.94H6.84V-4.61H4.75L5.6-6.74H4.9L4.05-4.6H.74V-3.93H3.78L3.08-2.18H.74V-1.51H2.81L1.97 .62H2.67L3.51-1.52H6.84Z'/&gt;
&lt;path id='eq5-g4-60' d='M8.38-1.25L8.21-1.39L7.96-1.08C7.61-.64 7.38-.45 7.18-.45C7.09-.45 7.04-.51 7.04-.64C7.04-.69 7.09-.91 7.16-1.18L7.97-4.17C7.98-4.23 8-4.42 8-4.5C8-4.93 7.71-5.25 7.31-5.25C7.07-5.25 6.86-5.18 6.57-5C5.99-4.63 5.49-4.05 4.66-2.73C4.95-3.64 5.13-4.31 5.13-4.54C5.13-4.97 4.86-5.25 4.45-5.25C3.73-5.25 2.89-4.47 1.8-2.74L2.49-5.23L2.45-5.25L.55-4.88V-4.69H.85C1.17-4.69 1.31-4.61 1.31-4.43C1.31-4.29 1.04-3.2 .61-1.7L.14 0H1.04C1.61-1.88 1.79-2.33 2.19-2.95C2.86-3.97 3.57-4.63 3.98-4.63C4.13-4.63 4.22-4.53 4.22-4.34C4.22-4.18 3.89-2.92 3.38-1.1L3.08 0H3.98C4.44-1.83 4.68-2.43 5.26-3.25C5.86-4.09 6.47-4.63 6.82-4.63C6.96-4.63 7.06-4.53 7.06-4.38C7.06-4.32 7.04-4.2 6.98-3.97L6.29-1.23C6.17-.76 6.13-.56 6.13-.44C6.13-.08 6.3 .11 6.61 .11C7.17 .11 7.69-.29 8.33-1.17L8.38-1.25Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq5-page1'&gt;
&lt;use x='.08' y='0' xlink:href='#eq5-g4-60'/&gt;
&lt;use x='9.19' y='1.79' xlink:href='#eq5-g8-49'/&gt;
&lt;use x='17.4' y='0' xlink:href='#eq5-g1-60'/&gt;
&lt;use x='28.4' y='0' xlink:href='#eq5-g4-60'/&gt;
&lt;use x='37.51' y='1.79' xlink:href='#eq5-g8-50'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;, then the variance of this data set &lt;span class="math"&gt;&lt;svg style="width: 7.489em; height: 1.450em; vertical-align: -0.426em; " viewBox=".16 -10.24 74.89 14.5"&gt;
&lt;title&gt;
\(\sigma_{1+2}^2 &amp;gt; \sigma_1^2 + \sigma_2^2\)
&lt;/title&gt;
&lt;defs&gt;
&lt;use id='eq6-g4-184' xlink:href='#eq6-g1-184' transform='scale(1.36)'/&gt;
&lt;path id='eq6-g1-184' d='M2.17-2.5H.26V-1.92H2.17V0H2.75V-1.92H4.66V-2.5H2.75V-4.42H2.17V-2.5Z'/&gt;
&lt;path id='eq6-g11-49' d='M3.44 0V-.13C2.75-.14 2.61-.23 2.61-.65V-5.89L2.54-5.9L.97-5.11V-4.99C1.07-5.03 1.17-5.07 1.21-5.08C1.36-5.14 1.51-5.18 1.6-5.18C1.78-5.18 1.86-5.05 1.86-4.77V-.81C1.86-.52 1.79-.32 1.65-.24C1.52-.17 1.4-.14 1.03-.13V0H3.44Z'/&gt;
&lt;path id='eq6-g11-50' d='M4.15-1.2L4.04-1.24C3.71-.74 3.6-.66 3.21-.66H1.12L2.59-2.2C3.36-3.01 3.7-3.68 3.7-4.36C3.7-5.23 3-5.9 2.09-5.9C1.61-5.9 1.15-5.71 .83-5.36C.55-5.07 .42-4.79 .27-4.17L.45-4.12C.8-4.98 1.12-5.26 1.72-5.26C2.45-5.26 2.95-4.76 2.95-4.03C2.95-3.35 2.55-2.53 1.82-1.76L.26-.1V0H3.67L4.15-1.2Z'/&gt;
&lt;path id='eq6-g7-102' d='M8.05-5.25H3.7C1.91-5.25 .3-3.75 .3-1.75C.3-.55 .94 .13 2.16 .13C3.62 .13 5.44-1.29 5.44-3.41C5.44-4.09 5.22-4.35 5.22-4.36H7.81L8.05-5.25ZM4.54-3.39C4.54-1.62 3.55-.17 2.32-.17C1.41-.17 1.29-1.36 1.29-1.72C1.29-4.01 2.51-4.93 3.39-4.93C3.84-4.93 4.54-4.81 4.54-3.39Z'/&gt;
&lt;path id='eq6-g7-161' d='M6.84-2.94V-3.18L.74-6.05V-5.38L5.69-3.06L.74-.74V-.07L6.84-2.94Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq6-page1'&gt;
&lt;use x='.16' y='0' xlink:href='#eq6-g7-102'/&gt;
&lt;use x='8.72' y='-4.34' xlink:href='#eq6-g11-50'/&gt;
&lt;use x='7.56' y='4.26' xlink:href='#eq6-g11-49'/&gt;
&lt;use x='11.94' y='4.26' xlink:href='#eq6-g1-184'/&gt;
&lt;use x='16.88' y='4.26' xlink:href='#eq6-g11-50'/&gt;
&lt;use x='25.34' y='0' xlink:href='#eq6-g7-161'/&gt;
&lt;use x='36.68' y='0' xlink:href='#eq6-g7-102'/&gt;
&lt;use x='45.24' y='-4.34' xlink:href='#eq6-g11-50'/&gt;
&lt;use x='44.08' y='4.26' xlink:href='#eq6-g11-49'/&gt;
&lt;use x='52.78' y='0' xlink:href='#eq6-g4-184'/&gt;
&lt;use x='62.33' y='0' xlink:href='#eq6-g7-102'/&gt;
&lt;use x='70.89' y='-4.34' xlink:href='#eq6-g11-50'/&gt;
&lt;use x='69.73' y='4.26' xlink:href='#eq6-g11-50'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;.
Thus, splitting our RGB cube in a meaningful way will reduce the sum of variances along all three axes.&lt;/p&gt;
&lt;p&gt;Since the k-d tree always splits along one axis, I figured it makes sense to compute the marginal distributions for a
cell (i.e. the projections of the histogram onto each axis), determine the optimal Otsu threshold for each of those
projections, and determine which of these three cuts reduces the variance the most. The figure below gives a graphical
representation of these marginal distributions and Otsu thresholds in 2D (for the RGB cube you&amp;rsquo;d add a 3&lt;sup&gt;rd&lt;/sup&gt; dimension
of course). The horizontal cut would reduce the total variance more than the vertical cut, so the horizontal cut is
chosen.&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="Otsu thresholds along each dimension of a 2D space" src="/images/minimum_variance_clustering.png"&gt;&lt;/p&gt;
&lt;p&gt;This process will allows us to divide the space into two cells. Next, we can repeat this process for each of the
resulting two cells, to find the next best cut. But now we also need to choose which of the cells to cut. The more cuts
we make, the more cells we will have to choose from. To make this problem tractable, a priority queue is the best
approach. Each of the cells is put into the priority queue, with a priority given by how much the sum of variances is
reduced by splitting that cell. Now the cell at the top of the queue is always the best one to cut. After cutting it,
the two resulting cells are put back into the queue.&lt;/p&gt;
&lt;p&gt;Here is an example partitioning of a 2D space with 5 Gaussian blobs:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="minimum variance partitioning in 2D" src="/images/test_clustering_b.png" width="32%"&gt;&lt;/p&gt;
&lt;p&gt;As shown earlier, the first cut is a horizontal cut that separates the one blob that is a little bit more isolated than
the others. The next cut is a vertical cut of the top cell, again separating one blob from the other three. Next we get
another horizontal cut, and finally a vertical cut. Note that these cuts do not need to separate one blob from the
group, it is a coincidence that it happened that way in this example.&lt;/p&gt;
&lt;p&gt;With k-means clustering, using random initialization, you get a different result every time. Sometimes the result is
really good, but with data like this you have to get lucky. Many initializations yield poor results, with two or three
blobs in one partition, and one blob split into two:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="k-means partitioning in 2D" src="/images/test_clustering_e.png" width="32%"&gt; &lt;img alt="k-means partitioning in 2D" src="/images/test_clustering_d.png" width="32%"&gt; &lt;img alt="k-means partitioning in 2D" src="/images/test_clustering_c.png" width="32%"&gt;&lt;/p&gt;
&lt;h2&gt;Putting it all together&lt;/h2&gt;
&lt;p&gt;As an example, I&amp;rsquo;m using the well-known &amp;ldquo;mandril&amp;rdquo; image:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="mandril image" src="/images/quantize_input.png" width="48%"&gt;&lt;/p&gt;
&lt;p&gt;Here are the results of using this minimum variance partitioning algorithm on the color histogram with 100&lt;sup&gt;3&lt;/sup&gt; bins, for
5 colors (left) and 25 colors (right):&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="5 colors chosen with minimum variance partitioning" src="/images/quantize_minvar_5.png" width="48%"&gt; &lt;img alt="25 colors chosen with minimum variance partitioning" src="/images/quantize_minvar_25.png" width="48%"&gt;&lt;/p&gt;
&lt;p&gt;The computation of the 25-color quantized image took 0.05 s on my computer. I find it striking that the image looks so
good with only 25 unique colors. For fewer colors, &lt;a href="/archives/355"&gt;dithering&lt;/a&gt; could have improved the results.&lt;/p&gt;
&lt;p&gt;For comparison, this is what the results are with k-means clustering. Running the algorithm multiple times gives
slightly different results. The computation of the 25-color quantized image took nearly 5 s, 100 times longer than the
minimum variance algorithm.&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="5 colors chosen with k-means clustering" src="/images/quantize_kmeans_5.png" width="48%"&gt; &lt;img alt="25 colors chosen with k-means clustering" src="/images/quantize_kmeans_25.png" width="48%"&gt;&lt;/p&gt;
&lt;p&gt;Which results do you prefer? Do you think that MATLAB&amp;rsquo;s &lt;code&gt;rgb2ind&lt;/code&gt; implements a similar algorithm to this?&lt;/p&gt;</content><category term="algorithms"></category><category term="clustering"></category><category term="color"></category><category term="DIPlib"></category><category term="k-d tree"></category><category term="k-means"></category><category term="minimum variance"></category><category term="color quantization"></category><category term="threshold"></category></entry><entry><title>Union-Find</title><link href="https://www.crisluengo.net/archives/913" rel="alternate"></link><published>2018-03-21T01:02:00-06:00</published><updated>2018-03-21T01:02:00-06:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2018-03-21:/archives/913</id><summary type="html">&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/Disjoint-set_data_structure"&gt;Union-Find data structure&lt;/a&gt; is well known in the image
processing community because of its use in efficient
&lt;a href="https://en.wikipedia.org/wiki/Connected-component_labeling"&gt;connected component labeling&lt;/a&gt; algorithms.
It is also an important part of
&lt;a href="https://en.wikipedia.org/wiki/Kruskal%27s_algorithm"&gt;Kruskal&amp;rsquo;s algorithm for the minimum spanning tree&lt;/a&gt;. It is used
to keep track of equivalences: are these two objects equivalent …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/Disjoint-set_data_structure"&gt;Union-Find data structure&lt;/a&gt; is well known in the image
processing community because of its use in efficient
&lt;a href="https://en.wikipedia.org/wiki/Connected-component_labeling"&gt;connected component labeling&lt;/a&gt; algorithms.
It is also an important part of
&lt;a href="https://en.wikipedia.org/wiki/Kruskal%27s_algorithm"&gt;Kruskal&amp;rsquo;s algorithm for the minimum spanning tree&lt;/a&gt;. It is used
to keep track of equivalences: are these two objects equivalent/connected/joined? You can think of it as a forest of
trees. The nodes in the trees are the objects. If two nodes are in the same tree, they are equivalent. It is called
Union-Find because it is optimized for those two operations, Union (joining two trees) and Find (determining if two
objects are in the same tree). Both operations are performed in (essentially) constant time (actually it is O(α(n)),
where α is the &lt;a href="https://en.wikipedia.org/wiki/Ackermann_function#Inverse"&gt;inverse Ackermann function&lt;/a&gt;, which grows
extremely slowly and is always less than 5 for any number you can write down).&lt;/p&gt;
&lt;p&gt;Here I&amp;rsquo;ll describe the data structure and show how its use can significantly speed up certain types of operations.&lt;/p&gt;
&lt;h2&gt;Implementation&lt;/h2&gt;
&lt;p&gt;The Union-Find data structure is basically an array &lt;em&gt;A&lt;/em&gt;, where an object with ID (label, index) &lt;em&gt;i&lt;/em&gt; is located at
&lt;em&gt;A&lt;/em&gt;[&lt;em&gt;i&lt;/em&gt;]. The value at that location is the ID of the parent node. Thus, the tree structure only contains pointers
from the children to the parent. A node that points at itself (&lt;em&gt;A&lt;/em&gt;[&lt;em&gt;i&lt;/em&gt;]==&lt;em&gt;i&lt;/em&gt;) is the root of a tree. In C++:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;UnionFind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Three operations are required on this tree:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create: add a new node.&lt;/li&gt;
&lt;li&gt;Find: find the root of a tree.&lt;/li&gt;
&lt;li&gt;Union: join two trees.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Create operation simply extends the array &lt;em&gt;A&lt;/em&gt; by one element and sets its value to its index. This creates a new
tree, the new node is its own parent:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;UnionFind::Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push_back&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The Find operation needs to follow parent links until it reaches a node that points to itself. This is the root. The
trick that makes this operation constant time is that, every time we look for a root, we set all nodes along the path to
point directly to the root. This makes each tree completely flat. The next time the same node is queried, its result is
obtained directly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, the Union operation simply makes one root point to the other. It uses the Find operation on its two operands,
so that any two nodes can be used to merge their trees:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;i1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;i2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;leaf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;leaf&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here, I picked the smaller of the indices to be the root. I have seen implementations that pick one node at random, but
this is computationally more expensive. Other implementations keep track of a &amp;ldquo;rank&amp;rdquo; for each tree, and merge the tree
with the lower rank into the one with the higher rank. This keeps the trees flatter. However, because of the flattening
in the Find operations, trees are pretty flat anyway. The most efficient approach would be to simply take &lt;code&gt;i1&lt;/code&gt; as root.&lt;/p&gt;
&lt;p&gt;Note that it is possible to extend the above and add some additional information to each root, such as the size of the
connected component for connected component labeling. The Union then must add the sizes for the two roots, and assign
that to the new root.&lt;/p&gt;
&lt;h2&gt;Watershed region merging&lt;/h2&gt;
&lt;p&gt;The first time I implemented a Union-Find&amp;ndash;based algorithm, I did not know what Union-Find was (I re-invented the wheel)
. And consequently I missed the important detail of flattening the trees (my wheel was square). This was the &amp;ldquo;fast&amp;rdquo;
watershed algorithm in DIPlib and DIPimage. It is an algorithm that takes some shortcuts to speed up the watershed
computation. It sorts all pixels, then processes them in this sorted order. Plateaus (regions of constant value in the
image) are not handled correctly by this algorithm, but a built-in merging procedure solves that issue. Basically, when
two different regions first meet, a decision is made to keep them separate and draw a watershed line, or to merge them.
The Union-Find structure is used here to keep track of these merged regions.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s time this watershed in DIPimage 2:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;repmat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;readim&lt;/span&gt;&lt;span class="p"&gt;,[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(@()&lt;/span&gt;&lt;span class="n"&gt;watershed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;% merge only within plateaus&lt;/span&gt;
&lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(@()&lt;/span&gt;&lt;span class="n"&gt;watershed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;% merge more aggressively&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This leads to &lt;code&gt;t1 = 0.4034&lt;/code&gt; and &lt;code&gt;t2 = 1.5193&lt;/code&gt;. As you can see, more merging means deeper trees, which means it takes
more time to find the root. In DIPlib 3 I implemented a correct Union-Find data structure, and plugged it into the same
watershed algorithm. The same test now gives &lt;code&gt;t1 = 0.0819&lt;/code&gt; and &lt;code&gt;t2 = 0.0821&lt;/code&gt;. That is, with little merging the algorithm
is 5x faster, and no matter how much we merge, it takes the same amount of time. (Do note that some of that 5x speedup
is caused by the way that DIPimage 2 handled input argument parsing, I did not eliminate that from these tests.)&lt;/p&gt;
&lt;h2&gt;Connected component labeling&lt;/h2&gt;
&lt;p&gt;DIPlib 2 used a fairly efficient connected component labeling algorithm that works for images of any dimensionality, and
can be adjusted to use any connectivity. It walks over the image until it finds a foreground pixel that hasn&amp;rsquo;t been
processed yet, assigns a new label to it, then applies a flood filling algorithm to visit all pixels in the connected
component and assign the same label to them. It then continues where it left off to search for the next unprocessed
pixel. In DIPlib 3 I copied the fastest algorithm from &lt;a href="https://github.com/prittt/YACCLAB"&gt;YACCLAB&lt;/a&gt;, which is specific
to 2D images and 8-connected neighborhood, and wrote a similar algorithm that works for images of any dimensionality and
for any connectivity. This algorithm makes two passes over the image. First it labels each pixel it finds, and uses the
Union-Find data structure to keep track of which labels are connected (and thus equivalent). In a second pass it paints
each output pixel with the index of the corresponding root node. This second step also allows the output labels to be
consecutive.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s time these two different labeling algorithms:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;repmat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;readim&lt;/span&gt;&lt;span class="p"&gt;,[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;% a large 2D binary image&lt;/span&gt;
&lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(@()&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;% 8-connected, DIPlib 3 uses algorithm from YACCLAB&lt;/span&gt;
&lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(@()&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;% 4-connected, DIPlib 3 uses my own algorithm&lt;/span&gt;
&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;repmat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;readim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;chromo3d&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;% a large 3D binary image&lt;/span&gt;
&lt;span class="n"&gt;t3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(@()&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;% DIPlib 3 uses my own algorithm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Under DIPimage 2, this code leads to &lt;code&gt;t1 = 0.0197&lt;/code&gt;, &lt;code&gt;t2 = 0.0187&lt;/code&gt; and &lt;code&gt;t3 = 1.1520&lt;/code&gt;. Under DIPimage 3 I
see &lt;code&gt;t1 = 0.0045&lt;/code&gt;, &lt;code&gt;t2 = 0.0051&lt;/code&gt; and &lt;code&gt;t3 = 0.5352&lt;/code&gt;. So the difference between the fastest implementation in YACCLAB and
my own, generic algorithm is fairly small, but there is a difference of a factor of about 2x with the non&amp;ndash;Union-Find
algorithm (plus again some overhead for parameter parsing in the DIPimage 2 toolbox). Do note that the old algorithm&amp;rsquo;s
running time depends on the number and size of objects, whereas the Union-Find algorithm is fairly consistent in timing.&lt;/p&gt;
&lt;h2&gt;Detecting local maxima&lt;/h2&gt;
&lt;p&gt;In DIPlib 2 I had implemented a local maxima algorithm that used a similar flood-fill method to handle plateaus that are
local maxima. I recently implemented a new algorithm that uses Union-Find for these. The new algorithm again walks over
the image once, assigns a label to pixels that seem like they are local maxima or part of a local maximum plateau. Other
pixels belonging to such a plateau are assigned the same label. At times a pixel is processed that &amp;ldquo;ties&amp;rdquo; two
differently labeled regions together, these two regions are merged. When a pixel on such a plateau makes it clear that
the plateau is not a local maximum (it has a higher-valued neighbor), the region is merged with the &amp;ldquo;background&amp;rdquo; (zero)
label, identifying it as not a local maximum. Like in the connected component labeling algorithm, a second pass paints
all pixels with a consistent label, and resets all pixels that are not local maxima. This algorithm is significantly
more efficient for images with many plateaus. For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;repmat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;readim&lt;/span&gt;&lt;span class="p"&gt;,[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(@()&lt;/span&gt;&lt;span class="n"&gt;maxima&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gaussf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;% image with many plateaus&lt;/span&gt;
&lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(@()&lt;/span&gt;&lt;span class="n"&gt;maxima&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With DIPimage 2 I get &lt;code&gt;t1 = 0.0397&lt;/code&gt; and &lt;code&gt;t2 = 0.0657&lt;/code&gt;. The second image needs nearly twice as much time because of the
many plateaus. With DIPimage 3 this is &lt;code&gt;t1 = 0.0185&lt;/code&gt; and &lt;code&gt;t2 = 0.0198&lt;/code&gt;. That is, the two images run in nearly the same
amount of time. The two-fold difference for the first image is in part due to more efficient pixel access. While
porting (and rewriting!) algorithms for DIPlib 3 I paid a lot more attention to
&lt;a href="/archives/885"&gt;walking over the image in memory storage order, and avoiding conditional statements within tight loops&lt;/a&gt;.&lt;/p&gt;</content><category term="algorithms"></category><category term="DIPimage"></category><category term="DIPlib"></category><category term="labeling"></category><category term="mathematical morphology"></category><category term="timing"></category><category term="Union-Find"></category><category term="watershed"></category></entry><entry><title>Computing Feret diameters from the convex hull</title><link href="https://www.crisluengo.net/archives/408" rel="alternate"></link><published>2012-02-13T17:45:00-07:00</published><updated>2012-02-13T17:45:00-07:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2012-02-13:/archives/408</id><summary type="html">&lt;p&gt;Some time ago I wrote about &lt;a href="/archives/339"&gt;how to compute the Feret diameters&lt;/a&gt; of a 2D object based on the chain code
of its boundary. The diameters we computed were the longest and shortest projections of the object. The shortest
projection, or smallest Feret diameter, is equivalent to the size measured …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Some time ago I wrote about &lt;a href="/archives/339"&gt;how to compute the Feret diameters&lt;/a&gt; of a 2D object based on the chain code
of its boundary. The diameters we computed were the longest and shortest projections of the object. The shortest
projection, or smallest Feret diameter, is equivalent to the size measured when physically passing objects through
sieves (i.e. &lt;a href="http://en.wikipedia.org/wiki/Sieve_analysis"&gt;sieve analysis&lt;/a&gt;, as is often done, e.g., with rocks). The
longest projection, or largest Feret diameter, is useful as an estimate of the length of elongated objects.&lt;/p&gt;
&lt;p&gt;The algorithm I described then simply rotated the object in two-degree intervals, and computed the projection length at
each orientation. The problem with this algorithm is that the width estimated for very elongated objects is not very
accurate: the orientation that produces the shortest projection could be up to 1 degree away from the optimal
orientation, meaning that the estimated width is &lt;em&gt;length&lt;/em&gt;⋅sin(&lt;em&gt;π&lt;/em&gt;/180) too large. This doesn&amp;rsquo;t sound like much, but if
the aspect ratio is 100, meaning the length is 100 times the width, we can overestimate the width by up to 175%!&lt;/p&gt;
&lt;p&gt;A more accurate algorithm needs to check every possible angle. This sounds infeasible, but can actually be done if one
considers the object under examination as a polygon with a finite set of vertices. The smallest diameter is the
narrowest calliper measure possible for the object. Imagine the calliper arms tightening around the object from either
side. At least one calliper arm will touch the object at two vertices. If it doesn&amp;rsquo;t, it is possible to rotate the
object to reduce the distance between the calliper arms. Knowing this, we can also realize that any of the object&amp;rsquo;s
diameters is equivalent to the corresponding diameter of the object&amp;rsquo;s convex hull. Any of the vertices of the object
that are not part of the convex hull will never be touched by the calliper arms, and are therefore irrelevant for the
computation of the Feret diameters. Thus, we can simplify our polygon by computing its convex hull, as we did
in &lt;a href="/archives/405"&gt;this earlier post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once we have converted our object boundary into a convex polygon, we can use the
so-called &lt;a href="http://cgm.cs.mcgill.ca/~orm/rotcal.html"&gt;rotating callipers&lt;/a&gt; algorithm to compute the Feret diameters. Such
an algorithm enumerates the antipodal pairs of the convex polygon; that is, it enumerates pairs of points that can be
touched at the same time by the two calliper arms. The minimum diameter will always be at an angle such that one of the
calliper arms touches a side of the polygon, the other arm might touch a point or a side.&lt;/p&gt;
&lt;p&gt;Here I&amp;rsquo;ll assume we just ran all the code in &lt;a href="/archives/405"&gt;the post on computing the convex hull&lt;/a&gt;, and thus we have a
variable &lt;code&gt;D&lt;/code&gt; containing the vertices of the convex hull, derived from the polygon &lt;code&gt;coords&lt;/code&gt;. Let&amp;rsquo;s start with defining
three simple helper functions we&amp;rsquo;ll be using:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;% Signed area of triangle&lt;/span&gt;
&lt;span class="nb"&gt;area&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pt2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pt3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c"&gt;% Height of triangle, pt3 is the top vertex&lt;/span&gt;
&lt;span class="nb"&gt;height&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pt2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pt3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;                          &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;                                               &lt;/span&gt;&lt;span class="nb"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c"&gt;% Next point along the polygon&lt;/span&gt;
&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next comes the rotating calliper algorithm itself. We initialise &lt;code&gt;minD&lt;/code&gt;, the minimum diameter, to infinity, and &lt;code&gt;maxD&lt;/code&gt;,
the maximum diameter, to zero. &lt;code&gt;minV&lt;/code&gt; and &lt;code&gt;minV&lt;/code&gt; are the vectors that represent the corresponding directions. &lt;code&gt;p&lt;/code&gt;
and &lt;code&gt;q&lt;/code&gt; are a pair of points on opposite sides of the polygon, and we move both around the shape such that they are
always antipodal. For a given &lt;code&gt;p&lt;/code&gt;, we find all antipodal points, and store them in the list &lt;code&gt;listq&lt;/code&gt;. We then check to
see if the distance between &lt;code&gt;p&lt;/code&gt; and any of its antipodal points is smaller than the current &lt;code&gt;minD&lt;/code&gt; or larger than the
current &lt;code&gt;maxD&lt;/code&gt;. Note that, to check for smallest distance, we cannot use the direct distance between the two points, but
need to look at the height of the triangle formed by &lt;code&gt;p&lt;/code&gt;, &lt;code&gt;p+1&lt;/code&gt; and &lt;code&gt;q&lt;/code&gt;. &lt;code&gt;p+1&lt;/code&gt; is the point next to p, and the line
joining the two is the one that the calliper arm is touching. The code below is not very transparent, it is a
translation and adaptation of the code on &lt;a href="http://cgm.cs.mcgill.ca/~orm/diam.html"&gt;this page&lt;/a&gt; (which has several errors).
I&amp;rsquo;ve also added a graphical representation of the calliper:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;position&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,[&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;450&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;450&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nb"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;(:,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)],[&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;(:,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;b-&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;hold&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;axis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;axis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;off&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;axis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;manual&lt;/span&gt;
&lt;span class="nb"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(:,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)],[&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(:,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;r-o&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;wl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;k-*&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;markersize&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;minD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Inf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;minV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="n"&gt;maxD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;maxV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;p0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;area&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,:),&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;),:),&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;),:))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nb"&gt;area&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,:),&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;),:),&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,:))&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;q0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;~=&lt;/span&gt;&lt;span class="n"&gt;p0&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;listq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="c"&gt;% p,q is an antipodal pair&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;area&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,:),&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;),:),&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;),:))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nb"&gt;area&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,:),&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;),:),&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,:))&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;listq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;listq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c"&gt;% p,q is an antipodal pair&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;area&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,:),&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;),:),&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;),:))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nb"&gt;area&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,:),&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;),:),&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,:))&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;listq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;listq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;% p,q+1 is an antipodal pair&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,:)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,:);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;maxD&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;maxD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;maxV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;pt3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,:);&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="p"&gt;),:);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;pt2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),:);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pt2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pt3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;minD&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;minD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;minV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pt2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;pt4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pt3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;minV&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;pt5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pt3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;minV&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;xdata&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;pt2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="nb"&gt;NaN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pt3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="nb"&gt;NaN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pt4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;pt5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;ydata&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;pt2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="nb"&gt;NaN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pt3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="nb"&gt;NaN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pt4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;pt5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)]);&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nb"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p class="centering"&gt;&lt;img alt="output produced by code on this page" src="/images/convhullferet.png"&gt;&lt;/p&gt;
&lt;p&gt;Now &lt;code&gt;minD&lt;/code&gt; and &lt;code&gt;maxD&lt;/code&gt; contain the minimum and maximum diameters found, and &lt;code&gt;minV&lt;/code&gt; and &lt;code&gt;minV&lt;/code&gt; the corresponding vectors.
It is simple to derive the orientation of the two diameters. Computing the diameter perpendicular to the minimum
diameter involves projecting the coordinates of the vertices onto a normalized &lt;code&gt;minV&lt;/code&gt;, then looking for the maximum
distance:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;maxA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;atan2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;maxV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;minA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;atan2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;minV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nb"&gt;pi&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;minV&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;minV&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nb"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minV&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;minV&lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;minP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Object length: %.2f, at %.2f degreesn&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;maxD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;maxA&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nb"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Minimum bounding box: %.2f by %.2f, at %.2f degreesn&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;minD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;minP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;minA&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nb"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="output highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="go"&gt;Object length: 37.20, at 53.75 degrees&lt;/span&gt;
&lt;span class="go"&gt;Minimum bounding box: 21.02 by 36.67, at -26.57 degrees&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that these distances are between the centres of boundary pixels, and the object itself is typically one pixel
wider/longer. The same was true when &lt;a href="/archives/339"&gt;we computed the Feret diameters from the chain code directly&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The new &lt;code&gt;'Feret'&lt;/code&gt; measurement in DIPimage works essentially this way, as described in
the &lt;a href="https://diplib.org/news/2012/01/10/Version-2.4.html"&gt;changelist&lt;/a&gt;.&lt;/p&gt;</content><category term="algorithms"></category><category term="area"></category><category term="boundary"></category><category term="chain code"></category><category term="convex hull"></category><category term="Feret"></category><category term="length"></category><category term="measure"></category><category term="object"></category><category term="polygon"></category><category term="width"></category></entry><entry><title>The convex hull of a 2D object</title><link href="https://www.crisluengo.net/archives/405" rel="alternate"></link><published>2011-09-18T16:32:00-06:00</published><updated>2011-09-18T16:32:00-06:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2011-09-18:/archives/405</id><summary type="html">&lt;p&gt;Last year I wrote about computing the &lt;a href="/archives/310"&gt;the boundary length&lt;/a&gt; and &lt;a href="/archives/339"&gt;various other measures&lt;/a&gt;,
given an object&amp;rsquo;s &lt;a href="/archives/324"&gt;chain code&lt;/a&gt;. The chain code is a simple way of encoding the polygon that represents a
2D object. It is very simple to compute the object&amp;rsquo;s convex hull given this polygon …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Last year I wrote about computing the &lt;a href="/archives/310"&gt;the boundary length&lt;/a&gt; and &lt;a href="/archives/339"&gt;various other measures&lt;/a&gt;,
given an object&amp;rsquo;s &lt;a href="/archives/324"&gt;chain code&lt;/a&gt;. The chain code is a simple way of encoding the polygon that represents a
2D object. It is very simple to compute the object&amp;rsquo;s convex hull given this polygon. Why would I want to do that? Well,
the convex hull gives several interesting object properties, such as the convexity (object area divided by convex hull
area). Certain other properties, such as the Feret diameters, are identical for an object and its convex hull, and the
convex hull thus gives an efficient algorithm to compute these properties.&lt;/p&gt;
&lt;p&gt;Avraham Melkman (1987) has
published &lt;a href="http://dx.doi.org/10.1016/0020-0190(87)90086-X"&gt;a very efficient algorithm to compute the convex hull&lt;/a&gt; given
a polygon (find the PDF through &lt;a href="http://scholar.google.com/scholar?hl=en&amp;amp;q=melkman+1987"&gt;Google Scholar&lt;/a&gt;).
&lt;a href="http://dx.doi.org/10.1016/0031-3203(91)90087-L"&gt;It has been said&lt;/a&gt; that this might very well be the most efficient
algorithm possible. It visits each polygon vertex only once, and keeps only those vertices that form the convex hull. To
accomplish this, it uses a deque (also known as &lt;a href="http://en.wikipedia.org/wiki/Double-ended_queue"&gt;double-ended queue&lt;/a&gt;)
to represent the convex hull. The deque has 4 basic operations: &lt;em&gt;push&lt;/em&gt; and &lt;em&gt;pop&lt;/em&gt; add and remove elements from the end of
the queue, and &lt;em&gt;insert&lt;/em&gt; and &lt;em&gt;remove&lt;/em&gt; add and remove elements from the front of the queue. By using such a data
structure, Melkman was able to add and remove vertices from his convex polygon at either side. The points in the deque
always constitute a convex polygon, and is the convex hull of the input points seen up to that point.&lt;/p&gt;
&lt;p&gt;The Melkman algorithm also depends on a function, which I&amp;rsquo;ve called &lt;em&gt;position&lt;/em&gt; here, which returns 1 if &lt;code&gt;pt3&lt;/code&gt; is to the
right of the directed line formed by &lt;code&gt;pt1&lt;/code&gt; and &lt;code&gt;pt2&lt;/code&gt;, -1 if it is to the left, or 0 if all three points are collinear
(form a straight line). This function simply returns the sign of the signed area of the parallelogram given by the
vectors &lt;code&gt;pt2-pt1&lt;/code&gt; and &lt;code&gt;pt3-pt1&lt;/code&gt;. It we embed these vectors in 3D (adding the value 0 as the z component), and compute
the cross product, the norm of this cross product gives the area of the parallelogram, whereas its direction is given by
the ordering of the three points on the plane (as per the right-hand rule). The cross product will, however, always have
x and y components equal to 0, so we only need to compute the z component. Let&amp;rsquo;s define an anonymous
function &lt;code&gt;position(pt1,pt2,pt3)&lt;/code&gt; for this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pt2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pt3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nb"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Before we can apply the algorithm, we&amp;rsquo;ll need some data. I like the image &amp;ldquo;cermet&amp;rdquo;. Let&amp;rsquo;s pick object #31, and get its
chain code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;readim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;cermet&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dip_imagechaincode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;% for DIPimage 2&lt;/span&gt;
&lt;span class="c"&gt;% cc = traceobjects(img,31,2,&amp;#39;chain code&amp;#39;)  % for DIPimage 3, use cc{1} instead of cc.chain below&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The code below, however, takes a polygon represented by a set of points, not a chain code. We can easily convert the
chain code &lt;code&gt;cc&lt;/code&gt; into a list of coordinates &lt;code&gt;coords&lt;/code&gt; (this is the same code as in the &lt;a href="/archives/324"&gt;post on chain codes&lt;/a&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;directions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cumsum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;directions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,:));&lt;/span&gt;
&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c"&gt;% Draw input polygon:&lt;/span&gt;
&lt;span class="nb"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;position&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,[&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;450&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;450&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nb"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;(:,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)],[&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;(:,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;b-&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;hold&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;axis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;axis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;off&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Melkman&amp;rsquo;s algorithm starts by taking the first three points in the list and forming them into a start of the convex
hull. However, the algorithm assumes these points are not collinear. So let&amp;rsquo;s examine the first 3 points in the set, and
delete the second one if all 3 are collinear:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,:),&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,:),&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,:))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,:)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At this point, we&amp;rsquo;d of course check to make sure that we have more than 3 points left. If not, we&amp;rsquo;re done! There is also
a special case that could happen if our object is a single line. In this case, all points in the object are collinear,
and the code above leaves only 2 points. We&amp;rsquo;d need to detect this case, and make sure that we keep the two extreme
points, rather than the first and last points in the list. Otherwise, length measurements on our convex hull will be
wrong.&lt;/p&gt;
&lt;p&gt;We start by creating a deque data structure &lt;code&gt;D&lt;/code&gt; (in MATLAB we can just use a simple array), and put the first three
points in it. We first check the order of these points, and put them in the deque so that the polygon is right-turning.
Furthermore, we always want the first and last point in our deque to be the same point:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,:),&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,:),&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,:))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],:);&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],:);&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="c"&gt;% Add convex hull to figure:&lt;/span&gt;
&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(:,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(:,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;ro-&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, the algorithm takes one new point &lt;code&gt;v&lt;/code&gt; at the time, and checks where it lies with respect to the first two points
and the last two points in the deque. This tells us if the point is already inside the convex hull or not. If it is
inside, we ignore the point and take the next one. If it is not inside, we&amp;rsquo;ll be wanting to add this point at the
beginning and at the end of the deque (push and insert). But before we add this new point, we need to remove (pop and
remove) previous points that will potentially end up inside the convex hull. We start with the end of the deque, and see
if the last point is inside the line formed by the second to last point and the new one. If so, we pop that point. This
is repeated until we cannot pop the last point any more, and then push the new point. We repeat the same procedure with
the front of the deque, removing points and then inserting the new one. Now the algorithm loops back to process the next
point. This is the code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="p"&gt;,:);&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,:),&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,:))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,:),&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,:),&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="p"&gt;,:);&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,:),&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,:),&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,:)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;% pop&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;% push v&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,:),&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,:))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,:)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;% remove&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;% insert v&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;% Update convex hull in figure:&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;xdata&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(:,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;ydata&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(:,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nb"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,:)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;% start and end points are the same, remove one.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When running the code, you will see the convex hull being updated as new points are examined by the algorithm (note that
the slowness comes from the &lt;code&gt;pause&lt;/code&gt; statement in the code!). At the end of the algorithm you&amp;rsquo;ll see this on your screen:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="output produced by code on this page" src="/images/convexhull.png"&gt;&lt;/p&gt;
&lt;p&gt;In &lt;a href="/archives/408"&gt;a future post&lt;/a&gt; we&amp;rsquo;ll use this convex hull to compute Feret diameters.&lt;/p&gt;</content><category term="algorithms"></category><category term="area"></category><category term="boundary"></category><category term="chain code"></category><category term="convex hull"></category><category term="convexity"></category><category term="measure"></category><category term="object"></category><category term="polygon"></category></entry><entry><title>Bresenham's line drawing algorithm in any number of dimensions</title><link href="https://www.crisluengo.net/archives/400" rel="alternate"></link><published>2011-07-22T10:17:00-06:00</published><updated>2011-07-22T10:17:00-06:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2011-07-22:/archives/400</id><summary type="html">&lt;p&gt;&lt;a href="http://dx.doi.org/10.1147/sj.41.0025"&gt;J.E. Bresenham published an algorithm in 1965&lt;/a&gt;, which is used to draw 2D digital
lines. Many people have extended the algorithm to work in 3D, and I&amp;rsquo;ve even found
a &lt;a href="https://sites.google.com/site/proyectosroboticos/bresenham-4d"&gt;website with code for 4D, 5D and 6D versions of the algorithm.&lt;/a&gt;
However, all the code I come …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://dx.doi.org/10.1147/sj.41.0025"&gt;J.E. Bresenham published an algorithm in 1965&lt;/a&gt;, which is used to draw 2D digital
lines. Many people have extended the algorithm to work in 3D, and I&amp;rsquo;ve even found
a &lt;a href="https://sites.google.com/site/proyectosroboticos/bresenham-4d"&gt;website with code for 4D, 5D and 6D versions of the algorithm.&lt;/a&gt;
However, all the code I come across is way more complex that it needs to be, giving the impression that this is a
complicated algorithm. Nothing is further from the truth! Drawing digital lines is the most simple and straight-forward
task. I&amp;rsquo;m going to deviate from the classical thinking that an efficient algorithm should use only integer values. This
will make our task trivial.&lt;/p&gt;
&lt;p&gt;The first thing we need to realize is that, when walking from the start pixel to the end pixel, there is always one
dimension along which we take integer steps. Along the other dimensions sometimes we&amp;rsquo;ll have to step, ans sometimes we
won&amp;rsquo;t. This is, to me, the key finding in the Bresenham algorithm. This tells us how many pixels will compose the line.
If we use more pixels, the line will be thickened at some points, or we&amp;rsquo;ll use some pixels more than once. If we use
fewer pixels, the line will not appear continuous. Based on this 1-pixel step size in one dimension, we can easily
calculate the sub-pixel step sizes we have to take in the other dimensions. We will then take these steps, computing
floating-point coordinates, until we reach the end point. Each of the points we compute we&amp;rsquo;ll round to integer pixel
coordinates for output. In the original algorithm, instead of computing a floating-point coordinate, an integer
coordinate is computed, and an additional integer value accumulates the fractional error. These two approaches are
equivalent, except that, when using floating-point values, everything becomes easier to understand.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s write some MATLAB code. We&amp;rsquo;ll start with two random points to draw a line between (I&amp;rsquo;m using 3D points here, but
you can use any number of dimensions):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, we take a floating-point vector variable, &lt;code&gt;p&lt;/code&gt;, which will contain the sub-pixel coordinates of the line. We also
compute the step size, &lt;code&gt;s&lt;/code&gt;, which is the total distance travelled in each dimension divided by the longest distance.
Thus, the vector &lt;code&gt;s&lt;/code&gt; contains one element with value 1 (or -1), and other values that are smaller.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We need to take &lt;code&gt;N&lt;/code&gt; steps of size &lt;code&gt;s&lt;/code&gt;, then round each of the floating-point positions found:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;disp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nb"&gt;disp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="output highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="go"&gt;    14     6    19&lt;/span&gt;
&lt;span class="go"&gt;    13     6    18&lt;/span&gt;
&lt;span class="go"&gt;    12     6    17&lt;/span&gt;
&lt;span class="go"&gt;    11     7    16&lt;/span&gt;
&lt;span class="go"&gt;    10     7    16&lt;/span&gt;
&lt;span class="go"&gt;     9     7    15&lt;/span&gt;
&lt;span class="go"&gt;     8     7    14&lt;/span&gt;
&lt;span class="go"&gt;     7     8    13&lt;/span&gt;
&lt;span class="go"&gt;     6     8    12&lt;/span&gt;
&lt;span class="go"&gt;     5     8    11&lt;/span&gt;
&lt;span class="go"&gt;     4     8    11&lt;/span&gt;
&lt;span class="go"&gt;     3     9    10&lt;/span&gt;
&lt;span class="go"&gt;     2     9     9&lt;/span&gt;
&lt;span class="go"&gt;     1     9     8&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There it is! Bresenham&amp;rsquo;s algorithm, for any number of dimensions, in 9 lines of code.&lt;/p&gt;</content><category term="algorithms"></category><category term="Bresenham"></category><category term="line"></category><category term="MATLAB"></category></entry><entry><title>Panoramic photograph stitching — again</title><link href="https://www.crisluengo.net/archives/393" rel="alternate"></link><published>2011-04-03T12:02:00-06:00</published><updated>2011-04-03T12:02:00-06:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2011-04-03:/archives/393</id><summary type="html">&lt;p&gt;In an &lt;a href="/archives/76"&gt;earlier post&lt;/a&gt;, I described and implemented a method, that was published recently, to stitch
together photographs from a panoramic set. In a comment this morning, Panda
asked about the parameters that direct the region merging in the watershed that I used. This set me to think about how …&lt;/p&gt;</summary><content type="html">&lt;p&gt;In an &lt;a href="/archives/76"&gt;earlier post&lt;/a&gt;, I described and implemented a method, that was published recently, to stitch
together photographs from a panoramic set. In a comment this morning, Panda
asked about the parameters that direct the region merging in the watershed that I used. This set me to think about how
much region merging the watershed should do. The only limitation that I can think of, is that we need two regions: one
touching the left image and one toughing the right. We can easily do this with a seeded watershed: we create two seeds,
one at each end of the region where the stitch should be, and run a seeded watershed. This watershed will not create new
regions. You should see it as a region growing algorithm, more than a watershed. However, the regions are grown
according to the watershed algorithm: low grey values first. That insures that, when the two regions meet, it happens at
a line with high grey values (a &amp;ldquo;ridge&amp;rdquo; in the grey-value landscape). The graph cut algorithm can now be left out: the
region growing algorithm does everything.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start with a quick recap. We have two images in variables &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="left image" src="/images/bigsur1.jpg"&gt;&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="right image" src="/images/bigsur2.jpg"&gt;&lt;/p&gt;
&lt;p&gt;I found the coordinates of a common point in the two images, &lt;code&gt;ca&lt;/code&gt; and &lt;code&gt;cb&lt;/code&gt;. Then we extend (and crop) the two images so
that they have a common coordinate system:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="left image, padded" src="/images/bigsur1x.jpg"&gt;&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="right image, padded" src="/images/bigsur2x.jpg"&gt;&lt;/p&gt;
&lt;p&gt;This is where the two methods start to differ. We again look at the difference between the two images in the stitching
region, taking the maximum of the differences in the three colour channels:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),:)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="n"&gt;imsize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,:)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The only difference with the &lt;code&gt;d&lt;/code&gt; from the earlier blog post is that we do not look at the inverse of the difference,
because the region growing algorithm in DIPimage that I will use can grow into high grey values first (i.e. the opposite
of what the watershed would do). The next task is to create a seed image. We set the pixels unique to the first image (
image &lt;code&gt;a&lt;/code&gt;) to 1, and the pixels unique to the second image to 2. The common region, where the stitch will be, we leave
0, so that the two seeds can grow into this area:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;newim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;sint8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),:)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;imsize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,:)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we just run the region growing algorithm:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dip_growregions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,[],&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;high_first&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;% For DIPimage 2&lt;/span&gt;
&lt;span class="c"&gt;% c = waterseed(c,d,1,-1,0,{&amp;#39;high_first&amp;#39;,&amp;#39;labels&amp;#39;,&amp;#39;no gaps&amp;#39;});  % For DIPimage 3&lt;/span&gt;
&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p class="centering"&gt;&lt;img alt="mask for stitching the two padded images" src="/images/bigsur_mask_new.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Note that we used the &lt;code&gt;'high_first'&lt;/code&gt; option. Using &lt;code&gt;'low_first'&lt;/code&gt; would yield the more traditional seeded watershed. As
before, the final composition is trivial using this mask:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p class="centering"&gt;&lt;img alt="stitched image result" src="/images/bigsur_new.jpg"&gt;&lt;/p&gt;
&lt;p&gt;As can be seen below, the two methods choose a different stitching, but both are quite successful. In this example, the
region growing method seems better than the graph cut method, but we&amp;rsquo;d need to do some more extensive testing to know
whether this is always the case or not. These two figures show where the images were stitched with each of the two
methods, the graph cut method first, the region growing method second:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="stitched image from previous post, with stitch line overlaid" src="/images/bigsur_overlay.jpg"&gt;&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="stitched image from this post, with stitch line overlaid" src="/images/bigsur_new_overlay.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Finally, a comparison like in the earlier blog post, comparing the blurring, the graph cut, and the region growing
methods:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="comparison of stitching methods" src="/images/bigsur_3stitches_new.jpg"&gt;&lt;/p&gt;
&lt;p&gt;The new method does not duplicate that one rock in the middle of the zoomed area. This might be coincidence, of course.
Don&amp;rsquo;t assume this method is better just because of this one example!&lt;/p&gt;
&lt;p&gt;Feel free to download the &lt;a href="/code/bigsur_new.m"&gt;script&lt;/a&gt; I used to generate the images on this page. [&lt;em&gt;Note: This
script was written for DIPimage 2, and will not work unmodified with DIPimage 3. See the comments in the code
in this post and the previous one for how to change the script.&lt;/em&gt;]&lt;/p&gt;</content><category term="algorithms"></category><category term="panorama"></category><category term="photography"></category><category term="region growing"></category><category term="stitching"></category><category term="watershed"></category></entry></feed>