<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Cris’ Image Analysis Blog</title><link href="https://www.crisluengo.net/" rel="alternate"></link><link href="https://www.crisluengo.net/feeds/all.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>DIPlib 3.5.2 released</title><link href="https://www.crisluengo.net/archives/1147" rel="alternate"></link><published>2024-12-27T00:00:00-07:00</published><updated>2024-12-27T00:00:00-07:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2024-12-27:/archives/1147</id><summary type="html">&lt;p&gt;Today we released DIPlib version 3.5.2. This release has quite a lot of changes, see
&lt;a href="https://diplib.org/changelogs/diplib_3.5.2.html"&gt;the change log&lt;/a&gt;.
In &lt;a href="/archives/1146" title="Graph cut segmentation"&gt;my last blog post&lt;/a&gt; I discussed the graph cut algorithm.
This is the largest addition (effort wise) in this release, together with the changes to the
&lt;a href="https://diplib.org/diplib-docs/dip-Graph.html"&gt;&lt;code&gt;dip::Graph&lt;/code&gt;&lt;/a&gt; class …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Today we released DIPlib version 3.5.2. This release has quite a lot of changes, see
&lt;a href="https://diplib.org/changelogs/diplib_3.5.2.html"&gt;the change log&lt;/a&gt;.
In &lt;a href="/archives/1146" title="Graph cut segmentation"&gt;my last blog post&lt;/a&gt; I discussed the graph cut algorithm.
This is the largest addition (effort wise) in this release, together with the changes to the
&lt;a href="https://diplib.org/diplib-docs/dip-Graph.html"&gt;&lt;code&gt;dip::Graph&lt;/code&gt;&lt;/a&gt; class and the new (but similar)
&lt;a href="https://diplib.org/diplib-docs/dip-DirectedGraph.html"&gt;&lt;code&gt;dip::DirectedGraph&lt;/code&gt;&lt;/a&gt; class.&lt;/p&gt;
&lt;p&gt;The other major change is in documentation, both for DIPimage (the MATLAB toolbox) and for PyDIP
(the Python bindings).&lt;/p&gt;
&lt;p&gt;In DIPimage, all the references to DIPlib functions are now links to the on-line documentation:&lt;/p&gt;
&lt;div class="output highlight"&gt;&lt;pre&gt;
&gt;&gt; help gaussf
 gaussf   Gaussian filter

  SYNOPSIS:
   image_out = gaussf(image_in,sigma,method,boundary_condition,truncation)

  PARAMETERS:
   sigma: Gaussian parameter for each dimension
   method: Method used to compute the Gaussian. One of:
     - 'fir':    Finite Impulse Resonse filter (convolution with a kernel).
     - 'iir':    Infinte Impulse Response filter (recursive filtering).
     - 'ft':     Convolution via a multiplication in the Fourier Domain.
     - 'best':   Chooses the best option above for your kernel.
     - 'kernel': The convolution kernel is returned, rather than the result
                 of the convolution.
   boundary_condition: Defines how the boundary of the image is handled.
                       See HELP BOUNDARY_CONDITION
   truncation: Determines the size of the Gaussian filters.

  DEFAULTS:
   sigma = 1
   metod = 'best'
   bounary_condition = 'mirror'
   truncation = 3

  NOTES:
   See DERIVATIVE for an explanation of the option 'best'.

   If SIGMA==0 for a particular dimension, that dimension is not processed.

  SEE ALSO:
   &lt;a href="" onclick="return false;"&gt;derivative&lt;/a&gt;

  DIPlib:
   This function calls the DIPlib function &lt;a href="https://diplib.org/diplib-docs/linear.html#dip-Derivative-Image-CL-Image-L-UnsignedArray--FloatArray--String-CL-StringArray-CL-dfloat-"&gt;dip::Derivative&lt;/a&gt; (which calls &lt;a href="https://diplib.org/diplib-docs/linear.html#dip-GaussFIR-Image-CL-Image-L-FloatArray--UnsignedArray--StringArray-CL-dfloat-"&gt;dip::GaussFIR&lt;/a&gt;,
   &lt;a href="https://diplib.org/diplib-docs/linear.html#dip-GaussIIR-Image-CL-Image-L-FloatArray--UnsignedArray--StringArray-CL-UnsignedArray--String-CL-dfloat-"&gt;dip::GaussIIR&lt;/a&gt; and &lt;a href="https://diplib.org/diplib-docs/linear.html#dip-GaussFT-Image-CL-Image-L-FloatArray--UnsignedArray--dfloat--String-CL-String-CL-StringArray-CL"&gt;dip::GaussFT&lt;/a&gt;) and &lt;a href="https://diplib.org/diplib-docs/generation_test.html#dip-CreateGauss-Image-L-FloatArray-CL-UnsignedArray--dfloat--UnsignedArray-"&gt;dip::CreateGauss&lt;/a&gt;.
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The text &amp;ldquo;derivative&amp;rdquo; is automatically turned into a link by MATLAB, as it&amp;rsquo;s the name
of a function. The last section previoulsy just named &amp;ldquo;dip::Derivative&amp;rdquo;, now it&amp;rsquo;s a link
that the user can click on to go to the documentation.
(A shout-out &lt;a href="https://github.com/DIPlib/diplib/issues/183"&gt;to Andre Zeug for this idea&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;In PyDIP, we previously only had the help text automatically generated by Pybind11, the
C++ library that we use to create the Python bindings. We have now added the first paragraph
from the on-line help to this:&lt;/p&gt;
&lt;div class="output highlight"&gt;&lt;pre&gt;
&gt;&gt;&gt; help(dip.Gauss)
Help on built-in function Gauss in module diplib.PyDIP_bin:

Gauss(...) method of builtins.PyCapsule instance
Gauss(*args, **kwargs)
Overloaded function.

    1. Gauss(in: diplib.PyDIP_bin.Image, sigmas: list[float] = [1.0], derivativeOrder: list[int] = [0], method: str = 'best', boundaryCondition: list[str] = [], truncation: float = 3.0) -&gt; diplib.PyDIP_bin.Image

    Convolution with a Gaussian kernel and its derivatives

    2. Gauss(in: diplib.PyDIP_bin.Image, *, out: diplib.PyDIP_bin.Image, sigmas: list[float] = [1.0], derivativeOrder: list[int] = [0], method: str = 'best', boundaryCondition: list[str] = [], truncation: float = 3.0) -&gt; None

    Convolution with a Gaussian kernel and its derivatives
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This help shows the input and output arguments to the function, and if there is more than one
legal signature (as is the case for most functions in DIPlib) shows each of them. The text
&amp;ldquo;Convolution with a Gaussian kernel and its derivatives&amp;rdquo; above is new, the rest is the
same as it was before.&lt;/p&gt;
&lt;p&gt;This automatically generated help text has some issues, it references the module &lt;code&gt;diplib.PyDIP_bin&lt;/code&gt;,
which is an internal name, rather than &lt;code&gt;diplib&lt;/code&gt;, which is the external name of the module, or
&lt;code&gt;dip&lt;/code&gt;, which is the name under which the user imported it. It also references the &amp;ldquo;&lt;code&gt;builtins.PyCapsule&lt;/code&gt; instance&amp;rdquo;,
which is an internal Pybind11 construct.&lt;/p&gt;
&lt;p&gt;This first paragraph of the help (the short description) is sometimes enough to know what a function
does, but if you want to know more about the meaning of the input arguments, how to use the function,
or exactly what algorithm(s) it implements, then you need to see the full documentation. Putting the
full documentation into the Python help string is not doable. The source for the documentation is
Markdown, we could parse that and render it as pure text into the Python help string, but for some
functions we make heavy use of equations, which we cannot render in pure text.&lt;/p&gt;
&lt;p&gt;Instead, we can help the user find the on-line documentation, like we did in DIPimage. We created
a new function, &lt;code&gt;dip.Doc&lt;/code&gt;, which will open your default web browser on the documentation for any
specific function. 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;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Doc&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;Gauss&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;will open &lt;a href="https://diplib.org/diplib-docs/linear.html#dip-Gauss-Image-CL-Image-L-FloatArray--UnsignedArray--String-CL-StringArray-CL-dfloat-"&gt;this link&lt;/a&gt;.
If a function has multiple overloads with separate documentation, then it opens the first one it finds,
and presents the list of all the matches:&lt;/p&gt;
&lt;div class="output highlight"&gt;&lt;pre&gt;
&gt;&gt;&gt; dip.Doc(dip.OtsuThreshold)
Found multiple matches for OtsuThreshold (opening the first one):
  - https://diplib.org/diplib-docs/histograms.html#dip-OtsuThreshold-Histogram-CL
  - https://diplib.org/diplib-docs/segmentation.html#dip-OtsuThreshold-Image-CL-Image-CL-Image-L
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;dip.Doc("watershed")&lt;/code&gt; will open the documentation for &lt;code&gt;dip::Watershed&lt;/code&gt;, because the string exactly
matches a function name (without regard to case). If the string doesn&amp;rsquo;t match a function exactly,
it will a Google search in the DIPlib documentation. For example, &lt;code&gt;dip.Doc("diffusion")&lt;/code&gt; opens
&lt;a href="https://www.google.com/search?q=site:diplib.org+diffusion"&gt;this Google search&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d love to hear your opinion about these solutions to the lack of documentation in Python,
and any ideas you might have to improve it further!&lt;/p&gt;</content><category term="announcements"></category><category term="DIPlib"></category><category term="PyDIP"></category><category term="DIPimage"></category></entry><entry><title>Graph cut segmentation</title><link href="https://www.crisluengo.net/archives/1146" rel="alternate"></link><published>2024-12-01T00:00:00-07:00</published><updated>2024-12-01T00:00:00-07:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2024-12-01:/archives/1146</id><summary type="html">&lt;p&gt;For a while I&amp;rsquo;ve been interested in adding the popular graph cut segmentation algorithm to DIPlib.
But I was not able to find an implementation I could adopt (the most commonly used implementation
has an incompatible open-source license).
So I finally sat down and re-implemented the algorithm myself from …&lt;/p&gt;</summary><content type="html">&lt;p&gt;For a while I&amp;rsquo;ve been interested in adding the popular graph cut segmentation algorithm to DIPlib.
But I was not able to find an implementation I could adopt (the most commonly used implementation
has an incompatible open-source license).
So I finally sat down and re-implemented the algorithm myself from scratch.
I learned a lot about the algorithm, hopefully with this blog post you&amp;rsquo;ll learn something too.&lt;/p&gt;
&lt;p&gt;The graph cut segmentation algorithm sees the image as a weighted graph, where each pixel is a node
(or vertex), and each pixel is connected to its neighbors with an edge. The weight of the edge is
given by the difference in intensity of the two pixels it connects. If we now find a way to
split the graph into two, by cutting edges with a high weight (i.e. connecting pixels with a
large difference in intensity), then we will have identified a region in the image with a large
contrast to its surrounding.&lt;/p&gt;
&lt;p&gt;This algorithm requires two markers: one for the background and one for the foreground. The algorithm
will find a cut of the graph that separates these two markers: each marker will be in a different
region. These markers therefore direct the segmentation. Typically, this is used in a situation where
the user can draw a set of markers on the image to give a rough indication of what the object is and what
the background is.&lt;/p&gt;
&lt;p&gt;How is this optimal cut found? And what really is optimized? First we need to learn about the max-flow
problem, the min-cut problem, and their relation.&lt;/p&gt;
&lt;h2&gt;The min-cut problem&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s forget about images for a moment, and think of a network of tubes. Each tube has a specific cross-section,
which will determine how much water can flow through it (its &lt;em&gt;capacity&lt;/em&gt;).
The network has a source, where water flows into the network, and a sink, where water flows out.
We can represent this network as a graph: each tube is an edge in the graph, its weight is the tube cross-section.
Where the tubes are joined together we have a node in the graph. The source and sink are two nodes like any other.
One thing to note: the flow of water going into a node must be equal to the flow going out, except for the
source and sink nodes. The flow originating in the source must be equal to the flow exiting at the sink.&lt;/p&gt;
&lt;p&gt;There is a maximum amount of water that can flow through this network from source to sink. Finding out
how much this is, is the &lt;em&gt;max-flow problem&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;When this amount of water is flowing, there must be a set of tubes flowing at maximum capacity (they&amp;rsquo;re &lt;em&gt;saturated&lt;/em&gt;).
Any path that you can find from source to sink must go through one of these tubes flowing at maximum capacity,
otherwise we&amp;rsquo;d be able to increase the amount of water flowing (it can flow along the non-saturated path we just found).
If we were to cut the set of saturated tubes, we would separate the source from the sink: the graph would
be cut into two parts, one containing the source and one containing the sink.&lt;/p&gt;
&lt;p&gt;The set of edges (tubes) we cut constitute the minimal cut. That is, the sum of their weights (or the
cross-sections of the tubes) is smaller than for any other cut. Any other cut will necessarily cut an edge
with available capacity, and therefore yield a larger sum. Finding this cut is the &lt;em&gt;min-cut&lt;/em&gt; problem.
It is clear, I think, from the explanation above that the min-cut problem is equivalent to the max-flow problem:
the maximum amount of water that can flow from source to sink is equal to the minimal possible sum of capacities
of tubes we need to cut to separate the source from the sink. &lt;/p&gt;
&lt;h2&gt;Finding a solution to the min-cut problem&lt;/h2&gt;
&lt;p&gt;To find the maximum amount of water that can flow through our network of tubes, and which tubes will be saturated,
we need to find all possible paths through the network. For each path in turn, we find the minimum capacity,
and add that amount of flow to each tube along the path. That is, we find a path and have as much water as possible
flow along that path. Do that for all possible paths are we&amp;rsquo;re done.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a simple example graph:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="An example weighted graph with one source and one sink nodes, and several paths in between. Each edge has a weight" src="/images/graph-cut_0.svg"&gt;&lt;/p&gt;
&lt;p&gt;One path from source (S) to sink (T) is S&amp;ndash;A&amp;ndash;B&amp;ndash;T. The minimal capacity along this path is 0.1, so we subtract
that amount from each of the edge weights along that path:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="The same graph, with updated edge weights." src="/images/graph-cut_1.svg"&gt;&lt;/p&gt;
&lt;p&gt;The numbers attached to each edge now is not the total capacity of the edge, but the &lt;em&gt;residual&lt;/em&gt; capacity, how
much more water can flow through it.&lt;/p&gt;
&lt;p&gt;Another path is S&amp;ndash;C&amp;ndash;B&amp;ndash;T. The residual along this path is 0.6, so we again subtract that
amount from each of the edge weights along that path:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="The same graph again, with edge weights updated a second time" src="/images/graph-cut_2.svg"&gt;&lt;/p&gt;
&lt;p&gt;Another path is S&amp;ndash;C&amp;ndash;D&amp;ndash;T. Residual to subtract is 0.5:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="The same graph again, with edge weights updated a third time" src="/images/graph-cut_3.svg"&gt;&lt;/p&gt;
&lt;p&gt;Now we have a set of edges that are saturated (their residual is 0): A&amp;ndash;B, C&amp;ndash;B, C&amp;ndash;D. This is our
minimal cut (1.2 was our total flow). By removing these edges, nodes A and C belong to S, and B and D to T.&lt;/p&gt;
&lt;p&gt;But there is another path we haven&amp;rsquo;t yet explored: S&amp;ndash;A&amp;ndash;B&amp;ndash;C&amp;ndash;D&amp;ndash;T. We didn&amp;rsquo;t need to consider this path because
at least one of its edges is already saturated. But I needed to point this out because:&lt;/p&gt;
&lt;p&gt;First, the B&amp;ndash;C edge is being used in reverse in this path, compared to the earlier S&amp;ndash;C&amp;ndash;B&amp;ndash;T path. If we were
to add water flow along this path, the flow from C to B would be reduced, or even reversed. Thus, when
computing flow, we must also consider direction of flow. We either need to remember in which direction the
water flows, or (as is commonly done) we must consider a separate residual in each direction. When one residual
is increased, the other is decreased.&lt;/p&gt;
&lt;p&gt;Second, the number of paths through a graph is deceptively large. For any practical problem, the number is
so large that the trivial algorithm described above is impossible to compute. We need to use a more efficient
algorithm. Many such algorithms have been described in the literature. The algorithm commonly used in image
processing is due to Boykov and Kolmogorov&amp;nbsp;&lt;a href="#ref-boykov-2004"&gt;[1]&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;An efficient algorithm&lt;/h2&gt;
&lt;p&gt;Boykov and Kolmogorov&amp;nbsp;&lt;a href="#ref-boykov-2004"&gt;[1]&lt;/a&gt; described an algorithm with a higher time complexity
than that of earlier algorithms. But they show that, in practice, it is faster for the case of graphs derived
from images (where each node has few edges).&lt;/p&gt;
&lt;p&gt;The algorithm is based on building two trees, one rooted in the source and one in the sink. Each node in the tree
points to its parent (so that one can navigate from any node in the tree to the root, but not the other way around).
As these trees grow, we&amp;rsquo;ll eventually find one edge that joins the two trees. From this edge, we can travel backwards
to the source and forward to the sink, identifying a complete path from source to sink. Crucially, the trees can
later continue growing to identify all the other paths.&lt;/p&gt;
&lt;p&gt;When a path is identified, we update residuals along it. One edge will become saturated, and this is not necessarily
the edge that joins the two trees. In fact, it is possible for multiple edges to become saturated.
The branch that is rooted in this saturated edge is cut off from its tree (we&amp;rsquo;ll mark its root node as an orphan).
Next, we attempt to re-attach the orphan nodes by looking at their neighbors. Any non-saturated edge that connects
an orphan node back to the same tree it originally belonged to can be used. If no such edge exists,
the node becomes a free node (one that the trees can eventually grow into), and its children are marked orphans.
When there are no more orphans, the tree growing phase continues.&lt;/p&gt;
&lt;p&gt;The algorithm finishes when all the free nodes have become part of one of the two trees, and no more paths
can be found. Note that it is possible for there to be more saturated edges than strictly required to cut the
graph in two. And the set of saturated nodes can even yield different graph cuts! We cannot simply look at all
the saturated nodes to find our solution. We need to traverse the graph from the source (or from the sink) and
find all nodes that we can reach without using a saturated edge. This set of nodes will be one of the graph
components, all the other nodes will form the other graph component. The graph cut is the set of edges that
join a node in each component.&lt;/p&gt;
&lt;h2&gt;Application to image processing&lt;/h2&gt;
&lt;p&gt;The above can be directly applied to an image, from which we construct a graph by turning each pixel into a node,
and add edges between each pair of neighboring pixels. In a 2D image, using a 4-connected neighborhood, we&amp;rsquo;d have
4 edges connecting each node. Edge weights have to be computed in some way from the difference in pixel intensity
such that for a large intensity difference we obtain a small weight, and for a small intensity difference we obtain
a large weight. The cut will then go between pixels with a larger difference in intensity. We&amp;rsquo;d mark one pixel as
the source and one as the sink, or, in the case of many marker pixels, we&amp;rsquo;d add a new source node connected to all
the source marker pixels with a very high weight, and similarly for the sink.&lt;/p&gt;
&lt;p&gt;But Boykov and Jolly&amp;nbsp;&lt;a href="#ref-boykov-2001"&gt;[1]&lt;/a&gt; suggested a more elaborate scheme, where the source and sink
nodes connect to all pixels in the image. The weight from source and sink nodes to each pixel are given by
the difference between the pixel&amp;rsquo;s intensity and the expected foreground or background intensities. They
actually use the histogram of the pixel values for the foreground and background markers as the expected
intensities. What this accomplishes is striking a balance between just propagating a marker to the nearest
edges and just selecting pixels based on colors (as you&amp;rsquo;d do with a threshold).&lt;/p&gt;
&lt;p&gt;In any case, this segmentation approach provides a global optimum (as opposed to a local one in algorithms
such as level sets) given the constraints (markers) and the cost function (edge weights). This allows for
more easyly tuning markers and edge weights to obtain the desired result, without being dependent on
algorithm initialization.&lt;/p&gt;
&lt;p&gt;The next release of DIPlib will have a graph cut function.&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-boykov-2004"&gt;Y. Boykov and V. Kolmogorov, &amp;ldquo;An Experimental Comparison of Min-Cut/Max-Flow Algorithms for Energy Minimization in Vision&amp;rdquo;,
    IEEE Transactions on Pattern Analysis and Machine Intelligence 26(9):1124&amp;ndash;1137, 2004.&lt;/li&gt;
&lt;li class="link_target" id="ref-boykov-2001"&gt;Y. Boykov M.-P. Jolly, &amp;ldquo;Interactive Graph Cuts for Optimal Boundary &amp;amp; Region Segmentation of Objects in N-D Images&amp;rdquo;,
    Proceedings Eighth IEEE International Conference on Computer Vision (ICCV 2001) 1:105&amp;ndash;112, 2001.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="tutorials"></category><category term="segmentation"></category><category term="graph"></category><category term="graph cuts"></category></entry><entry><title>ITK's architecture</title><link href="https://www.crisluengo.net/archives/1145" rel="alternate"></link><published>2024-07-04T00:00:00-06:00</published><updated>2024-07-04T00:00:00-06:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2024-07-04:/archives/1145</id><summary type="html">&lt;p&gt;This morning I was reading &lt;a href="https://aosabook.org/en/v2/itk.html"&gt;a chapter about the architecture of ITK&lt;/a&gt;
in a book called &lt;a href="https://aosabook.org/en/"&gt;The Architecture of Open Source Applications (Volume 2)&lt;/a&gt;.
ITK is the Insight Toolkit, a large library for image analysis in C++, specifically
aimed at medical applications. Most of its functions work on images of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This morning I was reading &lt;a href="https://aosabook.org/en/v2/itk.html"&gt;a chapter about the architecture of ITK&lt;/a&gt;
in a book called &lt;a href="https://aosabook.org/en/"&gt;The Architecture of Open Source Applications (Volume 2)&lt;/a&gt;.
ITK is the Insight Toolkit, a large library for image analysis in C++, specifically
aimed at medical applications. Most of its functions work on images of any dimensionality,
just like in DIPlib. But just about every other choice made in this library is the polar
opposite of the choices made in DIPlib. And this is why I was curious to learn about
why those choices were made.&lt;/p&gt;
&lt;p&gt;The book chapter was written by Luis Ibáñez and Brad King. Luis is one of the main architects of ITK,
and Brad is one of the original developers. ITK&amp;rsquo;s development started in 1999 with lots of funding
from the US government. This chapter was written maybe 10 or 15 years ago, ITK was already well
established.&lt;/p&gt;
&lt;p&gt;Reading this chapter, I saw some statements that I disagreed with, I guess that is the main
reason I&amp;rsquo;m writing this blog post now. I am going to address these statements, and their
justification for some of the design choices, out of order from how they appear in the chapter,
so that my comments make more sense. Obviously, my comments will make lots of comparisons to DIPlib.&lt;/p&gt;
&lt;p&gt;I once tried using ITK. I spent a week writing a simple function to align two images, then gave up
and implemented a rigid alignment from scratch in a day. That is to say, ITK has a &lt;em&gt;very&lt;/em&gt; steep learning curve.
Keep that in mind when you read my comments below.&lt;/p&gt;
&lt;h2&gt;Object-oriented programming&lt;/h2&gt;
&lt;p&gt;Now, some of my criticism of ITK comes from my dislike of the modern interpretation of object-oriented
programming (OOP): &amp;ldquo;everything is a class&amp;rdquo;. The Java programming language for example is
designed around this mentality. In Java you cannot even write a function outside a class.
ITK&amp;rsquo;s architecture revolves around the &amp;ldquo;everything is a class&amp;rdquo; mentality. This makes my hair stand
on edge when I see ITK code. But I will try to separate my feeling for this programming
paradigm from all the other comments I make in this blog post.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;OOP&lt;/p&gt;
&lt;p&gt;OOP is supposed to simplify the construction of large, complex programs by
dividing it up into smaller, minimally-dependent portions. Each portion is an object, that
communicates with other objects through their shared API. This shared API needs to be minimal,
since it is the only thing that creates dependencies between the components. This division
reduces the complexity of the application: pieces of code can be read and modified independently
of other pieces of code. Nowhere in this definition does it say or imply that the individual components
must be made using classes, that classes must inherit behavior or interfaces from other classes,
or that the OOP paradigm is even suitable for writing a library.&lt;/p&gt;
&lt;/div&gt;
&lt;h2&gt;Functionality&lt;/h2&gt;
&lt;p&gt;The article starts by explaining things about the field of image analysis, and what an image analysis
library needs to deal with.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In this context, a certain level of &amp;ldquo;redundancy&amp;rdquo;—for example, offering three different implementations
of the Gaussian filter—is not seen as a problem but as a valuable feature, because different
implementations can be used interchangeably to satisfy constraints and exploit efficiencies with
respect to image size, number of processors, and Gaussian kernel size that might be specific to
a given imaging application.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is absolutely right. Different algorithms that compute the same thing in different ways are not
necessarily redundant. The example of the Gaussian filter (DIPlib also has 3 different algorithms)
is the most obvious one: The IIR algorithm is most efficient for a very large sigma, but is an approximation.
The FIR algorithm is most efficient for the more common sigma values.
The FT algorithm is the only way you can compute the filter for a sub-pixel sigma with any semblance of precision.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[&amp;hellip;] ITK has a collection of about 700 filters. Given that ITK is implemented in C++, this is a natural
level at which every one of those filters is implemented by a C++ Class following object-oriented design patterns.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&amp;rsquo;ve already said that making each filter a class is not real OOP, but OK, let&amp;rsquo;s set that aside.
I don&amp;rsquo;t think that making each filter a class is a good idea because it makes the user&amp;rsquo;s code so
much more complex. Here&amp;rsquo;s an example program from
&lt;a href="https://examples.itk.org/src/filtering/smoothing/computessmoothingwithgaussiankernel/documentation"&gt;the ITK documentation&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="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;auto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;input&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;itk&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ReadImage&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ImageType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputFileName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FilterType&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;itk&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SmoothingRecursiveGaussianImageFilter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ImageType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ImageType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;auto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;smoothFilter&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;FilterType&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;smoothFilter&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;SetSigma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigmaValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;smoothFilter&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;SetInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;itk&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;WriteImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;smoothFilter&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;GetOutput&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;outputFileName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I&amp;rsquo;ve left out all the include statements, input parsing, exception handling, and the definition
of &lt;code&gt;ImageType&lt;/code&gt; (which we&amp;rsquo;ll talk about later). Also, I&amp;rsquo;m honestly surprised that &lt;code&gt;itk::ReadImage&lt;/code&gt;
and &lt;code&gt;itk::WriteImage&lt;/code&gt; are not classes. Apparently they are convenience functions &lt;a href="https://docs.itk.org/en/latest/releases/5.2.html#c-interface-improvements"&gt;introduced in
release 5.2.0 (May 2021)&lt;/a&gt;.
They just encapsulate the creation and use of the image reading and writing objects.&lt;/p&gt;
&lt;p&gt;Anyway, the above is a very simple program, it reads an image, applies a Gaussian filter, and writes
the result to file. The same program in DIPlib:&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;auto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;input&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;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="n"&gt;inputFileName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;auto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;output&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;dip&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Gauss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&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;sigmaValue&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;ImageWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;outputFileName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As a library user, which would you prefer to write?&lt;/p&gt;
&lt;p&gt;Sure, it is more explicit to do &lt;code&gt;smoothFilter-&amp;gt;SetSigma(sigmaValue)&lt;/code&gt; than having &lt;code&gt;sigmaValue&lt;/code&gt; be the
second input argument to a function. But when being explicit causes you to have to write four lines of
code instead of one&amp;hellip; sigh.
For functions with lots of parameters, the C++ call syntax can be quite obtuse. We&amp;rsquo;re reliant on our IDE
to show the name of the parameters for such function calls. I really like the Python syntax there,
where you can explicitly name the parameters as you fill in their value in the function call.
Will C++ ever adopt that syntax?&lt;/p&gt;
&lt;h2&gt;Pipeline&lt;/h2&gt;
&lt;p&gt;ITK is designed around the so-called &amp;ldquo;Data Pipeline architecture&amp;rdquo;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The staged nature of most image analysis tasks led naturally to the selection of a Data Pipeline architecture
as the backbone infrastructure for data processing. The Data Pipeline enables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Filter Concatenation&lt;/em&gt;: A set of image filters can be concatenated one after another, composing a processing
  chain in which a sequence of operations are applied to the input images.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Parameter Exploration&lt;/em&gt;: Once a processing chain is put together, it is easy to change the parameters of any
  filter in the chain, and to explore the effects that such change will have on the final output image.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Memory Streaming&lt;/em&gt;: Large images can be managed by processing only sub-blocks of the image at a time.
  In this way, it becomes possible to process large images that otherwise would not have fit into main memory.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;This core principle informed every other decision in the library. Regarding the second point, they say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Image filters typically have numeric parameters that are used to regulate the behavior of the filter.
Every time one of the numeric parameters is modified, the data pipeline marks its output as &amp;ldquo;dirty&amp;rdquo; and
knows that this particular filter, and all the downstream ones that use its output, should be executed again.
This feature of the pipeline facilitates the exploration of parameter space while using a minimum amount
of processing power for each instance of an experiment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a neat idea, but to change the parameters, I&amp;rsquo;d have to recompile my program (meaning I don&amp;rsquo;t get
to reuse any of its working data), unless I write code to let the user modify parameters.
If I write code to let the user modify parameters, I can also write code to re-process the relevant steps
of the pipeline. I don&amp;rsquo;t understand why this use case needs to be embedded in the library. But it&amp;rsquo;s cool.&lt;/p&gt;
&lt;p&gt;The main problem with this pipeline idea then is that all intermediate images are stored in memory. If
I have a pipeline with 100 individual processing steps (this is not out of the ordinary at all!), then
I&amp;rsquo;ll have 100 intermediate results being stored. I cannot instruct a step to overwrite its input to save
memory, and I cannot discard intermediate images when I don&amp;rsquo;t need them anymore.&lt;/p&gt;
&lt;p&gt;So this one use case, of allowing a program to do some interactive parameter exploration, makes the (IMO) more
common use case, of a program with fixed parameters needing to be as fast and memory-efficient as possible,
harder.&lt;/p&gt;
&lt;p&gt;The third point, &lt;em&gt;memory streaming&lt;/em&gt;, is also very interesting. Indeed, sometimes images are so large they
don&amp;rsquo;t fit in memory. Back in 1999 when ITK was designed, this might have been more common than it is today,
but it certainly still happens, for example in digital pathology.&lt;/p&gt;
&lt;p&gt;But this point is contradicted by the design decision of keeping all intermediate images in memory. Or maybe
that choice makes it more important to be able to process the image in smaller tiles, since it wastes so
much memory&amp;hellip;&lt;/p&gt;
&lt;p&gt;But it is true, being able to write a program as if you&amp;rsquo;re processing the image as a whole, but have it
automatically split the image up into tiles for you and process those tiles independently (and possibly
in parallel) is great. Each of the steps in the pipeline know how much margin they need (each filter will
read data from outside its output window, so its input needs to be a bit larger), the margin gets
computed automatically. This is one of the more difficult things to get right when doing tile-wise processing.&lt;/p&gt;
&lt;p&gt;This margin, also called &lt;em&gt;overlap&lt;/em&gt;, is processed multiple times, and is necessary for the result on two
neighboring tiles to match up at the edge between them. The smaller the margin, the more efficient the
algorithm is, but if the margin is too small, then the two tiles don&amp;rsquo;t agree at the edge that joins them,
and the final result will show seams. Hence, finding the minimal and sufficient overlap is important.&lt;/p&gt;
&lt;p&gt;However,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Streaming, unfortunately, can not be applied to all types of algorithms. Specific cases that are not
suitable for streaming are: Iterative algorithms. [&amp;hellip;] Algorithms that require the full set of
input pixel values [&amp;hellip;]. Region propagation or front propagation algorithms [&amp;hellip;]. Image registration
algorithms [&amp;hellip;].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a large fraction of the image analysis toolbox. &lt;em&gt;Most&lt;/em&gt; algorithms are not trivial to apply in
tiles. For example, you need to understand the content of the image to decide how large the margin must
be for the Watershed algorithm to produce correct results when applied tile-wise. How far information
gets propagated across the image by such an algorithm depends on the image content. It is only simple
neighborhood algorithms where you can determine the margin needed without understanding the application.&lt;/p&gt;
&lt;p&gt;And then I ask myself: is it really worth it, creating this complex &amp;ldquo;Data Pipeline architecture&amp;rdquo; so that
&lt;em&gt;some&lt;/em&gt; programs can automatically be applied tile-wise to large images?&lt;/p&gt;
&lt;p&gt;Obviously in DIPlib we took the approach that an algorithm works on a complete image in memory. The programmer
can use these functions to process image tiles, by manually determining how large these margins must be,
writing the code to read the input image tile-wise, and writing code to merge the results at the end.
ITK writes the result tile-wise to file, but most image analysis programs do not produce an output image,
they produce measurement results. So combining the results from file-wise processing is application-dependent.
Maybe you need to decide which of the cells that intersect the tile &amp;ldquo;belong&amp;rdquo; to the tile, maybe you need
to merge region outlines produced in tiles, maybe you need to sum the individual tile measurements, &amp;hellip;&lt;/p&gt;
&lt;p&gt;I have often thought about adding a tile-wise processing function to DIPlib. This function would take as input
a user-written function that processes a single tile. But it is the final merging step that I can&amp;rsquo;t seem to
define generically enough.&lt;/p&gt;
&lt;p&gt;And there&amp;rsquo;s another issue with the pipeline approach.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In a typical image analysis problem, a researcher or an engineer will take an input image,
improve some characteristics of the image by, let&amp;rsquo;s say, reducing noise or increasing contrast,
and then proceed to identify some features in the image, such as corners and strong edges
This type of processing is naturally well-suited for a data pipeline architecture, as shown in Figure 9.1.&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="Example image processing pipeline, Fig 9.1" src="/images/itk_ExampleImageProcessingPipeline.png"&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, I think this is rather naive. A typical problem has many more steps, and they&amp;rsquo;re not in a simple
linear fashion like this. This arbitrary graph is a more realistic pipeline:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="More realistic example image processing pipeline" src="/images/itk_realistic_pipeline.svg"&gt;&lt;/p&gt;
&lt;p&gt;In this pipeline, the blocks marked B, C and D read the output of block A, each likely with a different
margin in the tile-wise processing. Does block A process the same tile multiple times? How complex does the
internal logic have to be for A to see that these different output requests are very similar, and have it
process the field of view only once, with the largest of the requested margins? Does ITK implement such logic?&lt;/p&gt;
&lt;h2&gt;Types&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Despite the assistance that the file reader and writer facades provide, it is still up to the application
developer to be aware of the pixel type that the application needs to process. In the context of medical imaging,
it is reasonable to expect that the application developer will know whether the input image will contain a MRI,
a mammogram or a CT scan, and therefore be mindful of selecting the appropriate pixel type and image dimensionality
for each one of these different image modalities. This specificity of image type might not be convenient for
application settings where users wants to read any image type, which are most commonly found in the scenarios
of rapid prototyping and teaching. In the context of deploying a medical image application for production in
a clinical setting, however, it is expected that the pixel type and dimension of the images will be clearly
defined and specified based on the image modality to be processed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sure, this is true. When developing a program for a specific application, you know what file type you&amp;rsquo;ll be loading,
what the pixel type is, and how many dimensions the image has. But when you develop a more generic program,
then allowing for run-time type and dimensionality determination is very useful. &lt;a href="https://simpleitk.org"&gt;SimpleITK&lt;/a&gt;
was developed specifically for this purpose (I actually see it as an admission that ITK is unusable!).
DIPlib and OpenCV both allow for run-time type determination. Why? Because it makes writing code easier.
And because it makes creating bindings for other languages easier.
And because everything can be pre-compiled instead of being offered as templated code in header files that must
be compiled over and over again every time you build your application.
And because it makes generic programs much, much simpler.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ukoethe.github.io/vigra/"&gt;Vigra&lt;/a&gt; is another C++ library that has the pixel type and image dimensionality
as template parameters. Vigra attempts to create something more akin to the Standard Template Library for
image processing. The idea is that algorithms are generic, and the compiler builds an efficient implementation
of the algorithm for your particular use case. Maybe you can apply one of the Vigra image processing
algorithms on a graph data structure. This is a neat idea in principle, but you end up with the same
issues I criticized above.&lt;/p&gt;
&lt;h2&gt;IO Factories&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The Factory pattern in ITK uses class names as keys to a registry of class constructors.
The registration of factories happens at run time, and can be done by simply placing dynamic libraries
in specific directories that ITK applications search at start-up time. This last feature provides a natural
mechanism for implementing a plugin architecture in a clean and transparent way. The outcome is to facilitate
the development of extensible image analysis applications, satisfying the need to provide an ever-growing
set of image analysis capabilities.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, instead of allowing the user of the library to write a new image reading function (or class I guess)
for a new file format, and call that function directly (or register it with the library before using the
library&amp;rsquo;s image reading functionality), they want this new functionality to be compiled into a separate
dynamic library, and installed in a specific directory on the system where the application will run.
The &lt;code&gt;itk::ImageFileReader&lt;/code&gt; class will then find it and be able to read the new file type.&lt;/p&gt;
&lt;p&gt;This seems to be designed specifically so that applications written with ITK can be extended without recompiling
those applications. I think this adds a lot of complexity to the library, and to its use. Still, plugins are
quite neat.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The loadable IO factories has been one of the most successful features in the architectural design of ITK.
It has made it possible to easily manage a challenging situation without placing a burden on the code or
obscuring its implementation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is surprising to me. Plugins are neat, yes, but are they that important? How many people have built
such a plugin? &lt;a href="https://github.com/scifio/scifio-imageio"&gt;Here&lt;/a&gt; is an ITK ImageIO plugin for Bio-Formats.
I haven&amp;rsquo;t found any other plugins so far. Do you know of any others?&lt;/p&gt;
&lt;h2&gt;Some comments not related to ITK&amp;rsquo;s design&lt;/h2&gt;
&lt;p&gt;There were a couple of other comments in this chapter that I&amp;rsquo;d like to highlight and comment on.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;maintainers [&amp;hellip;] account for more than 75% of the cost of software development over the lifetime of a project.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(Later they have a more elaborate description of this finding, with citations.) I did not know this.
An interesting point to keep in mind. For every hour we spend writing an algorithm, we need to spend
3 hours maintaining that code!&lt;/p&gt;
&lt;p&gt;In a last section called &amp;ldquo;Reproducibility&amp;rdquo;, the authors say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;One of the early lessons learned in ITK was that the many papers published in the field were not as easy to
implement as we were led to believe. The computational field tends to over-celebrate algorithms and to dismiss
the practical work of writing software as &amp;ldquo;just an implementation detail&amp;rdquo;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Well, the interesting things to discuss about an algorithm are its properties, not its implementation. Then it says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The outcome is that most published papers are simply not reproducible, and when researchers and students attempt
to use such techniques they end up spending a lot of time in the process and deliver variations of the original work.
It is actually quite difficult, in practice, to verify if an implementation matches what was described in a paper.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is completely true. Unless the authors publish their code, reproducing their work is nearly impossible.
Often paper authors are ashamed about the quality of their code, and do not want to share it without a complete
rewrite. But of course there never is time for that. And so the code will, unfortunately, remain unpublished.&lt;/p&gt;
&lt;p&gt;Next, they claim:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ITK disrupted, for the good, that environment and restored a culture of DIY to a field that had grown
accustomed to theoretical reasoning, and that had learned to dismiss experimental work.
The new culture brought by ITK is a practical and pragmatic one in which the virtues of the software are
judged by its practical results and not by the appearance of complexity that is celebrated in some scientific
publications.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I have not seen any evidence of this. I imagine they would have liked this outcome. I certainly would have welcomed it.
But, 25 years on, papers are, more often than not, still not reproducible.
We still don&amp;rsquo;t get to see the code used to make them.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It turns out that in practice the most effective processing methods are those that would appear
to be too simple to be accepted for a scientific paper.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is also true. I have seen it hundreds of times. It is unfortunate that most journals will only accept
a paper if the method described beats the &amp;ldquo;state of the art&amp;rdquo;. I have written about this before. This leads
to awkward experimental sections with bad comparisons, in an attempt to appear to beat the state of the art.
But journals should be publishing papers about new ways of thinking, new approaches, not tiny incremental,
arbitrary changes that improve results in one particular test from 91.4% to 91.6% (and probably make results
worse in other tests that didn&amp;rsquo;t get included in the paper).&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p&gt;All the quotes in this blog post are from the chapter &lt;em&gt;ITK&lt;/em&gt;, by Luis Ibáñez and Brad King,
9th chapter in the book &lt;em&gt;The Architecture of Open Source Applications (Volume 2)&lt;/em&gt;,
edited by Amy Brown and Greg Wilson, and published under the
&lt;a href="http://creativecommons.org/licenses/by/3.0/legalcode"&gt;Creative Commons Attribution 3.0 Unported&lt;/a&gt;
license.&lt;/p&gt;
&lt;/div&gt;
&lt;h2&gt;In closing&lt;/h2&gt;
&lt;p&gt;And now I&amp;rsquo;d like to hear from you: Have you used ITK? How usable do you find it? Are any of the design
decisions discussed here better than in DIPlib?&lt;/p&gt;</content><category term="analyses"></category><category term="ITK"></category><category term="design"></category><category term="C++"></category><category term="style"></category><category term="syntax"></category></entry><entry><title>DIPlib 3.5.0 released</title><link href="https://www.crisluengo.net/archives/1144" rel="alternate"></link><published>2024-06-19T00:00:00-06:00</published><updated>2024-06-19T00:00:00-06:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2024-06-19:/archives/1144</id><summary type="html">&lt;p&gt;This week we released DIPlib version 3.5.0. In my previous blog post I talked about
&lt;a href="/archives/1143" title="Improving the selection of labeled objects"&gt;new syntax for selecting labeled objects&lt;/a&gt;.
There are quite a few more quality-of-life improvements like that in this release; changes that, rather
than adding algorithms, make existing ones easier to use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;dip::​Measurement …&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;This week we released DIPlib version 3.5.0. In my previous blog post I talked about
&lt;a href="/archives/1143" title="Improving the selection of labeled objects"&gt;new syntax for selecting labeled objects&lt;/a&gt;.
There are quite a few more quality-of-life improvements like that in this release; changes that, rather
than adding algorithms, make existing ones easier to use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;dip::​Measurement::​Iterator​Feature::​AsImage()&lt;/code&gt; converts a measurement column into an
image, making it easy to apply a lot of statistics functions to measurement features. &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There&amp;rsquo;s a new overload of &lt;code&gt;dip::​Gaussian​Mixture​Model()&lt;/code&gt; that takes a 1D histogram as input.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;dip::​GaussFT()&lt;/code&gt; has a new parameter &lt;code&gt;boundaryCondition&lt;/code&gt;, so you don&amp;rsquo;t need to manually pad and crop the
image when doing Gaussian convolution through the FFT.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;dip::Quartiles()&lt;/code&gt; computes the minimum, maximum and three percentiles much more efficiently than
it could be computed when calling &lt;code&gt;dip::Percentile()&lt;/code&gt; three times.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course there are many more changes and bug fixes, see the
&lt;a href="https://diplib.org/changelogs/diplib_3.5.0.html"&gt;change log&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There is only one new algorithm (though it&amp;rsquo;s fairly trivial): &lt;code&gt;dip::​Histogram&lt;/code&gt; now can use the
&lt;a href="https://en.wikipedia.org/wiki/Freedman–Diaconis_rule"&gt;Freedman&amp;ndash;Diaconis rule&lt;/a&gt; to choose the bin size.
Use the new function &lt;code&gt;dip::​Histogram::​Optimal​Configuration()&lt;/code&gt; to create the configuration
object that enables this rule.&lt;/p&gt;
&lt;p&gt;This change has the biggest impact of all the changes in this release, because we&amp;rsquo;re now using it in all
the functions that use a histogram to determine a threshold. In some situations, these algorithms weren&amp;rsquo;t
correct because the histogram that they relied on did not have enough resolution.&lt;/p&gt;
&lt;p&gt;For example, consider the following example image:&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="n"&gt;img&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;Image&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fill&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="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GaussianNoise&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;out&lt;/span&gt;&lt;span class="o"&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;variance&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="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DrawBandlimitedPoint&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="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;400&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigmas&lt;/span&gt;&lt;span class="o"&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;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DrawBandlimitedPoint&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="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigmas&lt;/span&gt;&lt;span class="o"&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;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DrawBandlimitedPoint&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="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigmas&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;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DrawBandlimitedPoint&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="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigmas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&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;DrawBandlimitedPoint&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="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;80000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigmas&lt;/span&gt;&lt;span class="o"&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 class="centering"&gt;&lt;img alt="An example image, it has a blacl background, and a few very bright Gaussian blobs" src="/images/diplib3.5.0_hist_input.png"&gt;&lt;/p&gt;
&lt;p&gt;The background has a value of 20.0 with normally distributed noise with a variance of 16.0 (standard deviation of 4.0).
The foreground is a series of relatively small dots, the brightest one has a peak value of around 4800. This is a
challenging image to generate a histogram for, but it&amp;rsquo;s not out of the ordinary, there are microscope systems
producing 12 or 14-bit images like this.&lt;/p&gt;
&lt;p&gt;The default histogram generated for this image has 256 bins, stretching from the minimum to the maximum value. This
makes the bin size equal to 18.74. All of the background pixels will fall in the same bottom bin:&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;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Histogram&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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Show&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 default histogram" src="/images/diplib3.5.0_hist_old.png"&gt;&lt;/p&gt;
&lt;p&gt;Using such a histogram to estimate the background distribution is simply not possible, the estimate will be wrong
by definition.&lt;/p&gt;
&lt;p&gt;Using the new histogram configuration, the bin size is computed to be 0.1069. The Freedman&amp;ndash;Diaconis rule takes the
interquartile range (IRQ, the distance from the 25th to the 75th percentile), and the number of pixels in the image, &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='eq2-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='eq2-page1'&gt;
&lt;use x='.06' y='0' xlink:href='#eq2-g1-61'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;,
and determines the bin size as&lt;/p&gt;
&lt;div class="math"&gt;&lt;svg style="width: 8.018em; height: 2.802em; vertical-align: 0.004em; " viewBox="153.74 -28.06 80.18 28.02"&gt;
&lt;title&gt;
\[
    w_\text{bin} = \frac{2\ \text{IQR}}{\sqrt[3]{n}} \quad .
\]
&lt;/title&gt;
&lt;defs&gt;
&lt;use id='eq1-g16-73' xlink:href='#eq1-g0-73' transform='scale(1.36)'/&gt;
&lt;use id='eq1-g16-81' xlink:href='#eq1-g0-81' transform='scale(1.36)'/&gt;
&lt;use id='eq1-g16-82' xlink:href='#eq1-g0-82' transform='scale(1.36)'/&gt;
&lt;path id='eq1-g5-112' d='M8.73-.67H8.04L5.34 8.66L3 3.69L.71 4.91L.86 5.16L2.13 4.68L5.26 11.24L8.73-.67Z'/&gt;
&lt;path id='eq1-g2-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='eq1-g0-73' d='M2.76 0V-.17C2.04-.19 1.9-.32 1.9-.96V-4.85C1.9-5.49 2.03-5.6 2.76-5.64V-5.8H.16V-5.64C.9-5.59 1.01-5.5 1.01-4.85V-.96C1.01-.3 .89-.19 .16-.17V0H2.76Z'/&gt;
&lt;path id='eq1-g0-81' d='M6.15 1.55V1.39C5.15 1.39 4.44 .98 3.73 .06C4.33-.05 4.65-.2 5.05-.55C5.7-1.13 6.03-1.93 6.03-2.9C6.03-4.66 4.84-5.93 3.16-5.93S.3-4.66 .3-2.88C.3-2.03 .56-1.31 1.08-.75C1.42-.38 1.7-.2 2.32 .01L2.74 .49C3.33 1.18 4.39 1.56 5.79 1.56C5.92 1.56 6 1.56 6.09 1.55H6.15ZM5.03-2.92C5.03-2.05 4.78-1.16 4.43-.75C4.1-.39 3.65-.19 3.16-.19C2.74-.19 2.36-.34 2.04-.62C1.58-1.03 1.3-1.91 1.3-2.94C1.3-3.78 1.55-4.64 1.91-5.04C2.27-5.43 2.68-5.61 3.19-5.61C3.57-5.61 3.97-5.44 4.28-5.17C4.75-4.75 5.03-3.91 5.03-2.92Z'/&gt;
&lt;path id='eq1-g0-82' d='M5.78 0V-.17C5.44-.19 5.27-.28 5.01-.58L3.21-2.8C3.8-2.91 4.06-3.02 4.35-3.25C4.63-3.48 4.8-3.85 4.8-4.26C4.8-4.64 4.68-4.96 4.45-5.22C4.12-5.58 3.39-5.8 2.57-5.8H.15V-5.64C.81-5.57 .89-5.47 .89-4.85V-1.05C.89-.32 .81-.22 .15-.17V0H2.58V-.17C1.9-.21 1.79-.32 1.79-.96V-2.68L2.28-2.7L4.37 0H5.78ZM3.84-4.28C3.84-3.84 3.66-3.47 3.34-3.31C2.94-3.08 2.63-3.02 1.79-3.01V-5.16C1.79-5.41 1.88-5.48 2.24-5.48C3.33-5.48 3.84-5.1 3.84-4.28Z'/&gt;
&lt;path id='eq1-g0-98' d='M4.1-2.13C4.1-3.21 3.44-4.03 2.56-4.03C2.03-4.03 1.52-3.72 1.34-3.29V-5.97L1.3-5.99C.93-5.86 .69-5.79 .28-5.67L.03-5.6V-5.46C.08-5.47 .11-5.47 .18-5.47C.53-5.47 .6-5.39 .6-5.02V-.47C.6-.2 1.35 .09 2.05 .09C3.21 .09 4.1-.88 4.1-2.13ZM3.33-1.73C3.33-.75 2.91-.19 2.19-.19C1.74-.19 1.34-.39 1.34-.61V-2.82C1.34-3.16 1.75-3.48 2.21-3.48C2.89-3.48 3.33-2.8 3.33-1.73Z'/&gt;
&lt;path id='eq1-g0-105' d='M2.22 0V-.13C1.64-.18 1.57-.26 1.57-.89V-4.01L1.53-4.03L.18-3.55V-3.42L.25-3.43C.35-3.45 .46-3.45 .54-3.45C.75-3.45 .83-3.31 .83-2.93V-.89C.83-.26 .75-.17 .14-.13V0H2.22ZM1.65-5.54C1.65-5.79 1.46-5.99 1.2-5.99C.96-5.99 .75-5.79 .75-5.54C.75-5.29 .96-5.09 1.2-5.09C1.46-5.09 1.65-5.29 1.65-5.54Z'/&gt;
&lt;path id='eq1-g0-110' d='M4.25 0V-.13C3.82-.18 3.72-.28 3.72-.71V-2.72C3.72-3.54 3.33-4.03 2.68-4.03C2.28-4.03 2.01-3.88 1.41-3.32V-4.02L1.35-4.03C.92-3.88 .62-3.78 .14-3.64V-3.49C.19-3.52 .28-3.52 .38-3.52C.62-3.52 .7-3.39 .7-2.96V-.79C.7-.29 .6-.17 .16-.13V0H2.02V-.13C1.57-.17 1.44-.27 1.44-.59V-3.05C1.86-3.45 2.05-3.55 2.34-3.55C2.77-3.55 2.98-3.28 2.98-2.7V-.87C2.98-.32 2.87-.17 2.43-.13V0H4.25Z'/&gt;
&lt;path id='eq1-g12-50' d='M3.11-.9L3.03-.93C2.78-.56 2.7-.5 2.4-.5H.84L1.94-1.65C2.52-2.26 2.78-2.76 2.78-3.27C2.78-3.92 2.25-4.43 1.57-4.43C1.21-4.43 .86-4.28 .62-4.02C.41-3.8 .31-3.59 .2-3.12L.34-3.09C.6-3.73 .84-3.94 1.29-3.94C1.84-3.94 2.21-3.57 2.21-3.02C2.21-2.51 1.91-1.9 1.36-1.32L.2-.08V0H2.75L3.11-.9Z'/&gt;
&lt;path id='eq1-g12-51' d='M2.83-1.43C2.83-1.77 2.73-2.08 2.54-2.28C2.4-2.42 2.28-2.5 1.99-2.63C2.44-2.93 2.61-3.18 2.61-3.53C2.61-4.06 2.19-4.43 1.59-4.43C1.26-4.43 .97-4.32 .73-4.11C.54-3.93 .44-3.76 .29-3.37L.39-3.34C.66-3.82 .96-4.04 1.37-4.04C1.79-4.04 2.09-3.75 2.09-3.33C2.09-3.1 1.99-2.86 1.83-2.7C1.63-2.5 1.45-2.4 1-2.25V-2.16C1.39-2.16 1.54-2.15 1.7-2.09C2.1-1.95 2.36-1.57 2.36-1.12C2.36-.57 1.98-.14 1.5-.14C1.32-.14 1.19-.19 .95-.35C.75-.47 .64-.51 .53-.51C.38-.51 .28-.42 .28-.28C.28-.05 .56 .09 1.02 .09C1.53 .09 2.04-.08 2.35-.35S2.83-1 2.83-1.43Z'/&gt;
&lt;use id='eq1-g15-50' xlink:href='#eq1-g12-50' transform='scale(1.82)'/&gt;
&lt;path id='eq1-g8-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='eq1-g8-70' d='M7.72-4.63C7.72-4.99 7.46-5.25 7.1-5.25C6.81-5.25 6.63-5.1 6.63-4.86C6.63-4.69 6.69-4.6 6.88-4.42S7.15-4.12 7.15-3.98C7.15-3.47 6.74-2.83 5.18-.88L4.8-5.07C4.8-5.2 4.78-5.24 4.7-5.24C4.65-5.24 4.61-5.22 4.55-5.12L2.26-1.5C2.22-2.55 2.05-3.99 1.88-4.85C1.81-5.23 1.79-5.25 1.66-5.25C1.05-5.11 .19-4.97 .19-4.97V-4.81H.54C.88-4.81 .96-4.74 1.06-4.31C1.19-3.74 1.35-2.36 1.39-1.39L1.44-.35C1.46 .12 1.49 .21 1.6 .21C1.73 .21 2.98-2.08 2.98-2.08L4.03-3.76L4.42-.14C4.44 .18 4.47 .21 4.55 .21C4.66 .21 4.78 .1 5.09-.3L5.19-.43C6.85-2.42 7.72-3.87 7.72-4.63Z'/&gt;
&lt;path id='eq1-g8-149' d='M2.16-.51C2.16-.88 1.85-1.19 1.49-1.19S.83-.89 .83-.51C.83-.06 1.24 .13 1.49 .13S2.16-.07 2.16-.51Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq1-page1'&gt;
&lt;use x='153.74' y='-11.88' xlink:href='#eq1-g8-70'/&gt;
&lt;use x='162.25' y='-10.09' xlink:href='#eq1-g0-98'/&gt;
&lt;use x='166.64' y='-10.09' xlink:href='#eq1-g0-105'/&gt;
&lt;use x='169.07' y='-10.09' xlink:href='#eq1-g0-110'/&gt;
&lt;use x='177.28' y='-11.88' xlink:href='#eq1-g2-61'/&gt;
&lt;use x='188.87' y='-19.98' xlink:href='#eq1-g15-50'/&gt;
&lt;use x='197.84' y='-19.98' xlink:href='#eq1-g16-73'/&gt;
&lt;use x='201.82' y='-19.98' xlink:href='#eq1-g16-81'/&gt;
&lt;use x='210.45' y='-19.98' xlink:href='#eq1-g16-82'/&gt;
&lt;rect x='188.87' y='-15.29' height='.67' width='29.55'/&gt;
&lt;use x='199.69' y='-7.03' xlink:href='#eq1-g12-51'/&gt;
&lt;use x='196.33' y='-11.29' xlink:href='#eq1-g5-112'/&gt;
&lt;rect x='204.74' y='-11.95' height='.66' width='6.19'/&gt;
&lt;use x='204.8' y='-2.7' xlink:href='#eq1-g8-61'/&gt;
&lt;use x='231.77' y='-11.88' xlink:href='#eq1-g8-149'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/div&gt;
&lt;p&gt;Given that such a small bin size could potentially lead to a humongous histogram, pixels 50 IQR above the
upper quartile or below the lower quartile are ignored. Thus, in our case, the histogram will be cropped on the top
side, and has 2751 bins:&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;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Histogram&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;configuration&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;Histogram&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OptimalConfiguration&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Show&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="A much better histogram" src="/images/diplib3.5.0_hist_new.png"&gt;&lt;/p&gt;
&lt;p&gt;The pixels that are ignored are not expected to be meaningful, but of course they could be. 
The alternative configuration &lt;code&gt;dip.​Histogram.​Optimal​Configuration​With​Full​Range()&lt;/code&gt;
does not crop the histogram, leading to 146284 bins.&lt;/p&gt;
&lt;p&gt;All the threshold functions that use the histogram to determine a threshold, now use the new &lt;code&gt;Optimal​Configuration()&lt;/code&gt;.
The functions impacted are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://diplib.org/diplib-docs/segmentation.html#dip-IsodataThreshold-Image-CL-Image-CL-Image-L-dip-uint-"&gt;&lt;code&gt;dip::IsodataThreshold()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://diplib.org/diplib-docs/segmentation.html#dip-OtsuThreshold-Image-CL-Image-CL-Image-L"&gt;&lt;code&gt;dip::OtsuThreshold()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://diplib.org/diplib-docs/segmentation.html#dip-MinimumErrorThreshold-Image-CL-Image-CL-Image-L"&gt;&lt;code&gt;dip::MinimumErrorThreshold()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://diplib.org/diplib-docs/segmentation.html#dip-GaussianMixtureModelThreshold-Image-CL-Image-CL-Image-L-dip-uint-"&gt;&lt;code&gt;dip::GaussianMixtureModelThreshold()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://diplib.org/diplib-docs/segmentation.html#dip-TriangleThreshold-Image-CL-Image-CL-Image-L-dfloat-"&gt;&lt;code&gt;dip::TriangleThreshold()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://diplib.org/diplib-docs/segmentation.html#dip-BackgroundThreshold-Image-CL-Image-CL-Image-L-dfloat--dfloat-"&gt;&lt;code&gt;dip::BackgroundThreshold()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These functions will no longer always produce the same output, because the threshold value previously was not always
correct. Of course, it is possible that an image has a very strange histogram and the algorithm still won&amp;rsquo;t compute
the correct threshold, but it is now much more likely for it to be correct.&lt;/p&gt;
&lt;p&gt;For example, applying &lt;code&gt;dip::​Background​Threshold()&lt;/code&gt; to the image above previously yielded a threshold of
58.51, now it yields a threshold of 29.08. We expected a threshold of 29.42, computed as the mean plus two times
the half-width at half maximum (HWHM) of the background Gaussian peak. The HWHM is approximately 1.1775 times the
stdandard deviation for a Gaussian. So we compute 20 + 2 · 1.1775 · 4.0 = 29.42. The difference in this example
is not as extreme as the case that prompted this change, see &lt;a href="https://github.com/DIPlib/diplib/issues/161"&gt;issue #161 on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To reproduce the old behavior, compute a default histogram, then call the equivalent threshold function that takes
a histogram as input. This function will compute the threshold, which can then be applied to the image using a
comparison operator:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Existing C++ code:&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;Image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bin&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;dip&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;OtsuThreshold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&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="c1"&gt;// Code that reproduces the old behavior of the existing code:&lt;/span&gt;
&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;threshold&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;dip&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;OtsuThreshold&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&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Histogram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&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="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bin&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;img&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="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or, in Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Existing Python code:&lt;/span&gt;
&lt;span class="nb"&gt;bin&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;OtsuThreshold&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="c1"&gt;# Code that reproduces the old behavior of the existing code:&lt;/span&gt;
&lt;span class="n"&gt;threshold&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;OtsuThreshold&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;Histogram&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="nb"&gt;bin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let us know what you think of this new functionality!&lt;/p&gt;</content><category term="announcements"></category><category term="DIPlib"></category><category term="PyDIP"></category></entry><entry><title>Improving the selection of labeled objects</title><link href="https://www.crisluengo.net/archives/1143" rel="alternate"></link><published>2024-05-30T00:00:00-06:00</published><updated>2024-05-30T00:00:00-06:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2024-05-30:/archives/1143</id><summary type="html">&lt;p&gt;Say, you have a labeled image, like this:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="A labeled image" src="/images/label_selection_input.png"&gt;&lt;/p&gt;
&lt;p&gt;And say, some of those labeled regions are not objects of interest. You want to erase those labels from
the image. This is a really simple concept, and very generic. It should be easy to do.&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="n"&gt;lab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dip …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Say, you have a labeled image, like this:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="A labeled image" src="/images/label_selection_input.png"&gt;&lt;/p&gt;
&lt;p&gt;And say, some of those labeled regions are not objects of interest. You want to erase those labels from
the image. This is a really simple concept, and very generic. It should be easy to do.&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="n"&gt;lab&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;labels.png&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# the labeled image above&lt;/span&gt;
&lt;span class="n"&gt;lab&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetPixelSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;μm&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;msr&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;MeasurementTool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;features&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;Size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Circularity&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 decided on a threshold of 2.0&amp;nbsp;μm for the size, anything smaller is not of interest. How do we go about
erasing those objects?&lt;/p&gt;
&lt;p&gt;The simple method is to paint each object with the measurement value, then threshold the resulting image and use
that to mask the original label 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;mask&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;ObjectToMeasurement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;
&lt;span class="n"&gt;new_lab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lab&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;new_lab&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is, however, not at all efficient. And doing this for multiple conditions on multiple features it quickly
becomes comical.&lt;/p&gt;
&lt;p&gt;Another way to erase these objects is to iterate over the rows (objects) of the measurement table,
and for each row (object) we make a decision whether to keep the object or not.
To make erasing objects efficient, we create a lookup table that maps all labels to themselves, except the
labels we want to erase, which should map to 0. Finally, we apply the lookup table to the labeled image, and
produce the new image with only the selected objects.&lt;/p&gt;
&lt;p&gt;In Python, we can use NumPy to avoid loops. 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="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;ids&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;asarray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Objects&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="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;asarray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;squeeze&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ids&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;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.0&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;ids&lt;/code&gt; now contains the list of labels we want to keep. We create and apply the lookup table:&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;lut&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;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumberOfObjects&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;dtype&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;uint32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;lut&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;
&lt;span class="n"&gt;new_lab&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;LookupTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lut&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lab&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In C++, the code would be something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;vector&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;diplib.h&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;diplib/lookup_table.h&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;diplib/measurement.h&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;diplib/regions.h&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;diplib/simple_file_io.h&amp;gt;&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&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;dip&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lab&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;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="s"&gt;&amp;quot;labels.png&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// the labeled image above&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;lab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetPixelSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&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;dip&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Units&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Micrometer&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&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MeasurementTool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;measurementTool&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&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Measurement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;msr&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;measurementTool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lab&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="s"&gt;&amp;quot;Size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Circularity&amp;quot;&lt;/span&gt;&lt;span class="p"&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="n"&gt;dip&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;uint32&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;lut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumberOfObjects&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;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="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;auto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;it&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;msr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;FirstObject&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;do&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;it&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="mf"&gt;2.0&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;lut&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ObjectID&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;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ObjectID&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="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="n"&gt;it&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&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new_lab&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;dip&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;LookupTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lut&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lut&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="n"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lab&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is not any more complicated than in Python with NumPy, it&amp;rsquo;s just&amp;hellip; different.&lt;/p&gt;
&lt;p&gt;Either way, the result is the following, identical to the first image but with the smaller objects removed:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="A modified labeled image" src="/images/label_selection_output.png"&gt;&lt;/p&gt;
&lt;h2&gt;A simple API&lt;/h2&gt;
&lt;p&gt;Usually, when a concept is this simple, and it takes me more than a minute to work out how to do it, I get miffed. And when
it&amp;rsquo;s a highly generic concept, I want to add functionality in DIPlib to make it easy to do.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll spare you the messy details of the design processes. What I finally came up with is this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The comparison operators are overloaded to compare a measurement object column (e.g. the result of &lt;code&gt;msr["Size"]&lt;/code&gt;)
  to a value. The result is a new object, &lt;code&gt;dip::LabelMap&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The new object &lt;code&gt;dip::LabelMap&lt;/code&gt; maps labels to either themselves or to 0. It&amp;rsquo;s a specialized version of
  &lt;code&gt;dip::LookupTable&lt;/code&gt;, specifically for object labels and label images.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dip::LabelMap::Apply()&lt;/code&gt; applies the lookup table to a label image.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With this new functionality, that last section of C++ code can be written as:&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;dip&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;LabelMap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;map&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;msr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Size&amp;quot;&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="mf"&gt;2.0&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;Image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new_lab&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;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lab&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Neat, eh?&lt;/p&gt;
&lt;p&gt;But this is not all:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Bitwise logical operators (&lt;code&gt;&amp;amp;&lt;/code&gt;, &lt;code&gt;|&lt;/code&gt;, &lt;code&gt;^&lt;/code&gt; and &lt;code&gt;~&lt;/code&gt;) are overloaded to combine multiple &lt;code&gt;dip::LabelMap&lt;/code&gt; objects.
  This means we can be more refined in our selection:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;dip::LabelMap map = (msr[&amp;quot;Size&amp;quot;] &amp;gt;= 2.0) &amp;amp; (msr[&amp;quot;Circularity&amp;quot;] &amp;lt; 0.1);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The map object can also map labels to other labels. &lt;code&gt;map.Relabel()&lt;/code&gt; has the same functionality as &lt;code&gt;dip::Relabel(lab)&lt;/code&gt;,
  and we can further modify the mapping by manually assigning labels with &lt;code&gt;map[5] = 4&lt;/code&gt;. This should allow for quite
  interesting workflows. For example relabeling labeled tiles of a larger image before stitching them back together.
  Or reassigning labels of 2D slices of a 3D image, or of frames in a video, so that they match from slice to slice
  or frame to frame.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;map.Apply(msr)&lt;/code&gt; returns a new &lt;code&gt;dip::Measurement&lt;/code&gt; object with rows deleted and the remaining object IDs
  mapped according to the &lt;code&gt;dip::LabelMap&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="admonition ref"&gt;
&lt;p class="admonition-title"&gt;Implementation details&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re interested in seeing the implementation details, you&amp;rsquo;ll find them here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/DIPlib/diplib/blob/master/include/diplib/label_map.h"&gt;include/diplib/label_map.h&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/DIPlib/diplib/blob/master/src/regions/label_map.cpp"&gt;src/regions/label_map.cpp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/DIPlib/diplib/blob/7d31e6ee14ee521ae68a02c0de851f01af1cdc54/src/measurement/measurement.cpp#L441-L480"&gt;src/measurement/measurement.cpp, lines 441-480&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;All this functionality is identically available in Python, so the Python code becomes similar to the C++ 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;new_lab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Size&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.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;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lab&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I&amp;rsquo;m sure this functionality will expand in the future. A few notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I have not overloaded arithmetic operators for measurement columns, though that would be quite neat to have.
  It would require a new object class, distinct from the current one, to hold the result of the operation.
  The current column object references data in the &lt;code&gt;dip::Measurement&lt;/code&gt; object, and cannot be updated to hold its
  own data. If we create such a new class, we&amp;rsquo;d have to overload all functions that currently take a measurement
  column to also work with this new object &amp;ndash; quite a lot of work!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;But, it would be fairly simple to overload comparison operators to compare two measurement columns. Would this
  be generally useful?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I overloaded the bit-wise logical operators (&lt;code&gt;&amp;amp;&lt;/code&gt;) rather than the regular logical operators (&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;) because
  the latter, when overloaded, don&amp;rsquo;t do short-circuiting, which I think might lead to programming errors.
  But because we don&amp;rsquo;t really do a bit-wise operation, this could also be misleading. So don&amp;rsquo;t think of these
  operators as bit-wise logical operators. Instead, think of them as element-wise logical operators.
  For an integer, the elements are the bits. For a &lt;code&gt;dip::LabelMap&lt;/code&gt;, the elements are the labels.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Note that in Python, the bit-wise logical operators have higher precedence than the comparison operators.
  So you do need the parentheses in the expression &lt;code&gt;(msr["Size"] &amp;gt;= 2.0) &amp;amp; (msr["Circularity"] &amp;lt; 0.1)&lt;/code&gt;.
  In C++ this is not the case (though I like to be explicit and put in the parentheses any way).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Edit 2024-05-31&lt;/p&gt;
&lt;p&gt;A reader suggested that I warn Python users about &lt;a href="https://docs.python.org/3/reference/expressions.html#comparisons"&gt;operator chaining&lt;/a&gt;.
You cannot use operator chaining with these &lt;code&gt;dip.LabelMap&lt;/code&gt; objects in Python. In Python, &lt;code&gt;1 &amp;lt; x &amp;lt; 5&lt;/code&gt; evaluates
as &lt;code&gt;1 &amp;lt; x and x &amp;lt; 5&lt;/code&gt;. But because we cannot overload &lt;code&gt;and&lt;/code&gt;, this does not do what the user was expecting.
This is true also for NumPy arrays and DIPlib images, as well as, I imagine, many objects across many packages.
So don&amp;rsquo;t use operator chaining!&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Please let me know what you think!&lt;/p&gt;</content><category term="tutorials"></category><category term="DIPlib"></category><category term="PyDIP"></category><category term="measure"></category><category term="object"></category><category term="selection"></category><category term="lookup table"></category><category term="syntax"></category><category term="performance"></category></entry><entry><title>Implementing the convolution</title><link href="https://www.crisluengo.net/archives/1142" rel="alternate"></link><published>2023-12-28T00:00:00-07:00</published><updated>2023-12-28T00:00:00-07:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2023-12-28:/archives/1142</id><summary type="html">&lt;p&gt;I find myself regularly explaining how to efficiently implement a convolution (a linear filter). This blog
post will combine all my tips and tricks regarding convolution. I&amp;rsquo;ll start with the 1D case, then will
expand into the multi-dimensional case.&lt;/p&gt;
&lt;p&gt;In the 1D case, I&amp;rsquo;ll use &lt;span class="math"&gt;&lt;svg style="width: 0.353em; height: 0.663em; vertical-align: -0.013em; " viewBox="-.05 -6.5 3.53 6.63"&gt;
&lt;title&gt;
\(t\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq4-g1-67' d='M3.53-5.1H2.57L2.91-6.32C2.92-6.35 2.92-6.37 2.92-6.38C2.92-6.47 2.88-6.5 2.82-6.5C2.75-6.5 2.72-6.49 2.64-6.4C1.51-4.92 .68-5.17 .68-4.79C.68-4.78 .68-4.75 .69-4.72H1.57L.71-1.44C.68-1.29 .44-.55 .44-.32C.44-.06 .69 .13 1.01 .13C1.56 .13 1.95-.2 2.7-1.31L2.55-1.39C1.97-.64 1.77-.45 1.58-.45C1.48-.45 1.41-.55 1.41-.69C1.41-.7 1.41-.71 1.42-.75L2.47-4.72H3.47L3.53-5.1Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq4-page1'&gt;
&lt;use x='-.05' y='0' xlink:href='#eq4-g1-67'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; as the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I find myself regularly explaining how to efficiently implement a convolution (a linear filter). This blog
post will combine all my tips and tricks regarding convolution. I&amp;rsquo;ll start with the 1D case, then will
expand into the multi-dimensional case.&lt;/p&gt;
&lt;p&gt;In the 1D case, I&amp;rsquo;ll use &lt;span class="math"&gt;&lt;svg style="width: 0.353em; height: 0.663em; vertical-align: -0.013em; " viewBox="-.05 -6.5 3.53 6.63"&gt;
&lt;title&gt;
\(t\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq4-g1-67' d='M3.53-5.1H2.57L2.91-6.32C2.92-6.35 2.92-6.37 2.92-6.38C2.92-6.47 2.88-6.5 2.82-6.5C2.75-6.5 2.72-6.49 2.64-6.4C1.51-4.92 .68-5.17 .68-4.79C.68-4.78 .68-4.75 .69-4.72H1.57L.71-1.44C.68-1.29 .44-.55 .44-.32C.44-.06 .69 .13 1.01 .13C1.56 .13 1.95-.2 2.7-1.31L2.55-1.39C1.97-.64 1.77-.45 1.58-.45C1.48-.45 1.41-.55 1.41-.69C1.41-.7 1.41-.71 1.42-.75L2.47-4.72H3.47L3.53-5.1Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq4-page1'&gt;
&lt;use x='-.05' y='0' xlink:href='#eq4-g1-67'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; as the variable, x(t) is a discrete input signal of finite length (a set of samples)
and only defined for integer &lt;span class="math"&gt;&lt;svg style="width: 4.648em; height: 1.095em; vertical-align: -0.241em; " viewBox="-.05 -8.54 46.48 10.95"&gt;
&lt;title&gt;
\(t \in [0, N)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq5-g8-48' d='M5.67-3.93C5.67-6.37 4.59-8.05 3.03-8.05C2.37-8.05 1.87-7.85 1.43-7.43C.74-6.77 .29-5.4 .29-4C.29-2.7 .68-1.31 1.24-.64C1.68-.12 2.29 .17 2.98 .17C3.59 .17 4.1-.04 4.53-.45C5.22-1.11 5.67-2.49 5.67-3.93ZM4.53-3.91C4.53-1.42 4-.14 2.98-.14S1.43-1.42 1.43-3.89C1.43-6.42 1.97-7.74 2.99-7.74C3.99-7.74 4.53-6.4 4.53-3.91Z'/&gt;
&lt;path id='eq5-g1-50' d='M5.76-.1V-.69H3.5C2.43-.69 1.38-1.57 1.24-2.76H5.56V-3.36H1.24C1.37-4.49 2.31-5.43 3.5-5.43H5.76V-6.03H3.55C1.89-6.03 .62-4.65 .62-3.06S1.89-.1 3.55-.1H5.76Z'/&gt;
&lt;path id='eq5-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;path id='eq5-g1-187' d='M3.56 2H2.54C2.14 2 1.95 1.8 1.95 1.38V-7.6C1.95-7.97 2.11-8.12 2.49-8.12H3.56V-8.42H1.05V2.3H3.56V2Z'/&gt;
&lt;path id='eq5-g4-35' d='M8.66-7.59V-7.78H6.29V-7.59C6.97-7.53 7.12-7.41 7.12-6.98C7.12-6.84 7.1-6.71 7.02-6.46L6.99-6.36L5.78-1.83L3.3-7.78H1.38V-7.59C1.94-7.54 2.19-7.38 2.41-6.92L.99-1.95C.54-.43 .44-.29-.24-.19V0H2.12V-.19C1.5-.24 1.29-.37 1.29-.7C1.29-.86 1.32-1.1 1.39-1.35L2.75-6.37L5.49 .18H5.71L7.42-5.81C7.86-7.35 7.91-7.42 8.66-7.59Z'/&gt;
&lt;path id='eq5-g4-67' d='M3.53-5.1H2.57L2.91-6.32C2.92-6.35 2.92-6.37 2.92-6.38C2.92-6.47 2.88-6.5 2.82-6.5C2.75-6.5 2.72-6.49 2.64-6.4C1.51-4.92 .68-5.17 .68-4.79C.68-4.78 .68-4.75 .69-4.72H1.57L.71-1.44C.68-1.29 .44-.55 .44-.32C.44-.06 .69 .13 1.01 .13C1.56 .13 1.95-.2 2.7-1.31L2.55-1.39C1.97-.64 1.77-.45 1.58-.45C1.48-.45 1.41-.55 1.41-.69C1.41-.7 1.41-.71 1.42-.75L2.47-4.72H3.47L3.53-5.1Z'/&gt;
&lt;path id='eq5-g4-150' d='M2.32-.07C2.32-1.06 1.63-1.21 1.36-1.21C1.06-1.21 .67-1.04 .67-.52C.67-.05 1.1 .07 1.41 .07C1.49 .07 1.55 .06 1.58 .05C1.63 .04 1.67 .02 1.69 .02C1.77 .02 1.86 .08 1.86 .19C1.86 .42 1.67 .95 .88 1.45L.99 1.68C1.35 1.56 2.32 .77 2.32-.07Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq5-page1'&gt;
&lt;use x='-.05' y='0' xlink:href='#eq5-g4-67'/&gt;
&lt;use x='7.53' y='0' xlink:href='#eq5-g1-50'/&gt;
&lt;use x='18.21' y='0' xlink:href='#eq5-g1-187'/&gt;
&lt;use x='22.55' y='0' xlink:href='#eq5-g8-48'/&gt;
&lt;use x='28.53' y='0' xlink:href='#eq5-g4-150'/&gt;
&lt;use x='34.1' y='0' xlink:href='#eq5-g4-35'/&gt;
&lt;use x='43.04' y='0' xlink:href='#eq5-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;. We&amp;rsquo;ll consider &lt;span class="math"&gt;&lt;svg style="width: 4.272em; height: 1.059em; vertical-align: -0.246em; " viewBox=".19 -8.13 42.72 10.59"&gt;
&lt;title&gt;
\(y = x * h\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq6-g4-3' d='M4.87-3.98C4.87-4.18 4.7-4.39 4.49-4.39C4.22-4.39 3.24-3.42 2.89-3.22C2.89-3.88 3.23-4.55 3.23-4.95C3.23-5.2 3.06-5.36 2.81-5.36C2.57-5.36 2.43-5.19 2.43-4.95C2.43-4.49 2.72-3.86 2.72-3.22C2.33-3.44 1.41-4.44 1.16-4.44C.94-4.44 .75-4.24 .75-4.03C.75-3.61 2.16-3.32 2.62-3.06C2.12-2.78 1.19-2.63 .95-2.48C.83-2.39 .74-2.29 .74-2.13C.74-1.92 .91-1.73 1.12-1.73C1.39-1.73 2.36-2.69 2.72-2.89C2.72-2.24 2.38-1.57 2.38-1.16C2.38-.92 2.56-.75 2.8-.75S3.18-.93 3.18-1.16C3.18-1.64 2.91-2.26 2.91-2.89C3.28-2.68 4.22-1.68 4.45-1.68C4.67-1.68 4.86-1.88 4.86-2.1C4.86-2.25 4.75-2.36 4.63-2.43C4.32-2.63 3.5-2.76 2.99-3.06C3.55-3.39 4.31-3.44 4.66-3.64C4.79-3.72 4.87-3.82 4.87-3.98Z'/&gt;
&lt;path id='eq6-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='eq6-g7-17' d='M5.69-1.25L5.54-1.39C4.9-.58 4.75-.45 4.56-.45C4.45-.45 4.37-.55 4.37-.67C4.37-.8 4.56-1.54 4.76-2.16C5.12-3.28 5.34-4.16 5.34-4.45C5.34-4.93 5.01-5.25 4.55-5.25C3.78-5.25 2.93-4.48 1.82-2.74L3.25-8.08L3.19-8.13C2.5-7.98 2.04-7.9 1.32-7.81V-7.63C1.38-7.63 1.45-7.63 1.52-7.63C1.82-7.63 2.12-7.61 2.12-7.35C2.12-7.07 1.93-6.46 1.83-6.1L.23 0H1.12C1.6-1.83 1.75-2.24 2.23-2.97C2.85-3.91 3.66-4.65 4.07-4.65C4.25-4.65 4.41-4.5 4.41-4.35C4.41-4.3 4.37-4.14 4.32-3.95L3.67-1.49C3.51-.91 3.43-.54 3.43-.39C3.43-.08 3.63 .11 3.95 .11C4.56 .11 4.97-.21 5.69-1.25Z'/&gt;
&lt;path id='eq6-g7-71' d='M4.95-1.23L4.79-1.32C4.69-1.2 4.63-1.14 4.53-1C4.25-.64 4.12-.52 3.97-.52C3.8-.52 3.69-.68 3.61-1.01C3.59-1.12 3.57-1.18 3.56-1.2C3.28-2.32 3.13-2.97 3.13-3.14C3.66-4.06 4.09-4.59 4.3-4.59C4.37-4.59 4.48-4.55 4.59-4.49C4.73-4.41 4.81-4.38 4.92-4.38C5.16-4.38 5.32-4.56 5.32-4.81C5.32-5.07 5.12-5.25 4.84-5.25C4.31-5.25 3.87-4.82 3.04-3.55L2.91-4.2C2.74-5.01 2.61-5.25 2.29-5.25C2.01-5.25 1.63-5.16 .89-4.91L.76-4.86L.81-4.68L1.01-4.73C1.24-4.79 1.38-4.81 1.48-4.81C1.77-4.81 1.85-4.7 2.01-3.99L2.36-2.53L1.38-1.13C1.13-.77 .91-.56 .77-.56C.7-.56 .58-.6 .46-.67C.31-.75 .19-.79 .08-.79C-.15-.79-.32-.61-.32-.37C-.32-.06-.1 .13 .27 .13S.79 .02 1.38-.69L2.45-2.1L2.81-.67C2.97-.05 3.12 .13 3.5 .13C3.95 .13 4.26-.15 4.95-1.23Z'/&gt;
&lt;path id='eq6-g7-72' d='M5.07-4.6C5.07-4.95 4.78-5.25 4.42-5.25C4.14-5.25 3.95-5.07 3.95-4.81C3.95-4.62 4.05-4.5 4.29-4.35C4.51-4.22 4.6-4.11 4.6-3.94C4.6-3.47 4.17-2.55 3.14-.86L2.91-2.24C2.73-3.3 2.06-5.25 1.88-5.25H1.83L1.73-5.24L.56-5.04L.18-4.97V-4.76C.32-4.8 .42-4.81 .55-4.81C1.02-4.81 1.24-4.63 1.46-4.05C1.79-3.24 2.44-.57 2.44-.1C2.44 .04 2.39 .18 2.32 .32C2.23 .48 1.69 1.18 1.48 1.41C1.2 1.7 1.06 1.8 .91 1.8C.82 1.8 .75 1.76 .62 1.67C.44 1.52 .32 1.46 .18 1.46C-.08 1.46-.29 1.67-.29 1.93C-.29 2.24-.04 2.45 .32 2.45C1 2.45 2.29 1.08 3.51-.96C4.51-2.61 5.07-3.92 5.07-4.6Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq6-page1'&gt;
&lt;use x='.48' y='0' xlink:href='#eq6-g7-72'/&gt;
&lt;use x='9.56' y='0' xlink:href='#eq6-g1-61'/&gt;
&lt;use x='19.96' y='0' xlink:href='#eq6-g7-71'/&gt;
&lt;use x='28.5' y='0' xlink:href='#eq6-g4-3'/&gt;
&lt;use x='37.22' y='0' xlink:href='#eq6-g7-17'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;, where &lt;span class="math"&gt;&lt;svg style="width: 0.569em; height: 0.824em; vertical-align: -0.011em; " viewBox=".43 -8.13 5.69 8.24"&gt;
&lt;title&gt;
\(h\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq7-g1-17' d='M5.69-1.25L5.54-1.39C4.9-.58 4.75-.45 4.56-.45C4.45-.45 4.37-.55 4.37-.67C4.37-.8 4.56-1.54 4.76-2.16C5.12-3.28 5.34-4.16 5.34-4.45C5.34-4.93 5.01-5.25 4.55-5.25C3.78-5.25 2.93-4.48 1.82-2.74L3.25-8.08L3.19-8.13C2.5-7.98 2.04-7.9 1.32-7.81V-7.63C1.38-7.63 1.45-7.63 1.52-7.63C1.82-7.63 2.12-7.61 2.12-7.35C2.12-7.07 1.93-6.46 1.83-6.1L.23 0H1.12C1.6-1.83 1.75-2.24 2.23-2.97C2.85-3.91 3.66-4.65 4.07-4.65C4.25-4.65 4.41-4.5 4.41-4.35C4.41-4.3 4.37-4.14 4.32-3.95L3.67-1.49C3.51-.91 3.43-.54 3.43-.39C3.43-.08 3.63 .11 3.95 .11C4.56 .11 4.97-.21 5.69-1.25Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq7-page1'&gt;
&lt;use x='.43' y='0' xlink:href='#eq7-g1-17'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; is the kernel and &lt;span class="math"&gt;&lt;svg style="width: 0.536em; height: 0.771em; vertical-align: -0.246em; " viewBox=".19 -5.25 5.36 7.71"&gt;
&lt;title&gt;
\(y\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq8-g1-72' d='M5.07-4.6C5.07-4.95 4.78-5.25 4.42-5.25C4.14-5.25 3.95-5.07 3.95-4.81C3.95-4.62 4.05-4.5 4.29-4.35C4.51-4.22 4.6-4.11 4.6-3.94C4.6-3.47 4.17-2.55 3.14-.86L2.91-2.24C2.73-3.3 2.06-5.25 1.88-5.25H1.83L1.73-5.24L.56-5.04L.18-4.97V-4.76C.32-4.8 .42-4.81 .55-4.81C1.02-4.81 1.24-4.63 1.46-4.05C1.79-3.24 2.44-.57 2.44-.1C2.44 .04 2.39 .18 2.32 .32C2.23 .48 1.69 1.18 1.48 1.41C1.2 1.7 1.06 1.8 .91 1.8C.82 1.8 .75 1.76 .62 1.67C.44 1.52 .32 1.46 .18 1.46C-.08 1.46-.29 1.67-.29 1.93C-.29 2.24-.04 2.45 .32 2.45C1 2.45 2.29 1.08 3.51-.96C4.51-2.61 5.07-3.92 5.07-4.6Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq8-page1'&gt;
&lt;use x='.48' y='0' xlink:href='#eq8-g1-72'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; is the
result of convolving the signal and the kernel. We care here only for the case where &lt;span class="math"&gt;&lt;svg style="width: 0.536em; height: 0.771em; vertical-align: -0.246em; " viewBox=".19 -5.25 5.36 7.71"&gt;
&lt;title&gt;
\(y\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq9-g1-72' d='M5.07-4.6C5.07-4.95 4.78-5.25 4.42-5.25C4.14-5.25 3.95-5.07 3.95-4.81C3.95-4.62 4.05-4.5 4.29-4.35C4.51-4.22 4.6-4.11 4.6-3.94C4.6-3.47 4.17-2.55 3.14-.86L2.91-2.24C2.73-3.3 2.06-5.25 1.88-5.25H1.83L1.73-5.24L.56-5.04L.18-4.97V-4.76C.32-4.8 .42-4.81 .55-4.81C1.02-4.81 1.24-4.63 1.46-4.05C1.79-3.24 2.44-.57 2.44-.1C2.44 .04 2.39 .18 2.32 .32C2.23 .48 1.69 1.18 1.48 1.41C1.2 1.7 1.06 1.8 .91 1.8C.82 1.8 .75 1.76 .62 1.67C.44 1.52 .32 1.46 .18 1.46C-.08 1.46-.29 1.67-.29 1.93C-.29 2.24-.04 2.45 .32 2.45C1 2.45 2.29 1.08 3.51-.96C4.51-2.61 5.07-3.92 5.07-4.6Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq9-page1'&gt;
&lt;use x='.48' y='0' xlink:href='#eq9-g1-72'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; has the same number
of samples as &lt;span class="math"&gt;&lt;svg style="width: 0.565em; height: 0.538em; vertical-align: -0.013em; " viewBox="-.32 -5.25 5.65 5.38"&gt;
&lt;title&gt;
\(x\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq10-g1-71' d='M4.95-1.23L4.79-1.32C4.69-1.2 4.63-1.14 4.53-1C4.25-.64 4.12-.52 3.97-.52C3.8-.52 3.69-.68 3.61-1.01C3.59-1.12 3.57-1.18 3.56-1.2C3.28-2.32 3.13-2.97 3.13-3.14C3.66-4.06 4.09-4.59 4.3-4.59C4.37-4.59 4.48-4.55 4.59-4.49C4.73-4.41 4.81-4.38 4.92-4.38C5.16-4.38 5.32-4.56 5.32-4.81C5.32-5.07 5.12-5.25 4.84-5.25C4.31-5.25 3.87-4.82 3.04-3.55L2.91-4.2C2.74-5.01 2.61-5.25 2.29-5.25C2.01-5.25 1.63-5.16 .89-4.91L.76-4.86L.81-4.68L1.01-4.73C1.24-4.79 1.38-4.81 1.48-4.81C1.77-4.81 1.85-4.7 2.01-3.99L2.36-2.53L1.38-1.13C1.13-.77 .91-.56 .77-.56C.7-.56 .58-.6 .46-.67C.31-.75 .19-.79 .08-.79C-.15-.79-.32-.61-.32-.37C-.32-.06-.1 .13 .27 .13S.79 .02 1.38-.69L2.45-2.1L2.81-.67C2.97-.05 3.12 .13 3.5 .13C3.95 .13 4.26-.15 4.95-1.23Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq10-page1'&gt;
&lt;use x='0' y='0' xlink:href='#eq10-g1-71'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;1D convolution&lt;/h2&gt;
&lt;p&gt;There are two different ways that a 1D convolution can be defined. There&amp;rsquo;s the &lt;em&gt;Infinite Impulse Response&lt;/em&gt; (&lt;em&gt;IIR&lt;/em&gt;)
and the &lt;em&gt;Finite Impulse Response&lt;/em&gt; (&lt;em&gt;FIR&lt;/em&gt;) filters.&lt;/p&gt;
&lt;p&gt;An IIR filter uses previous output values to compute the current output value. That is, it is recursive. For example,&lt;/p&gt;
&lt;div class="math"&gt;&lt;svg style="width: 18.783em; height: 1.099em; vertical-align: 0.003em; " viewBox="99.93 -11.02 187.83 10.99"&gt;
&lt;title&gt;
\[ y(t) = a x(t) + b y(t-1) + c y(t-2) \quad .\]
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq1-g11-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='eq1-g11-50' d='M5.66-1.63L5.5-1.69C5.06-1.01 4.91-.91 4.37-.91H1.52L3.53-3C4.59-4.11 5.05-5.01 5.05-5.94C5.05-7.13 4.09-8.05 2.85-8.05C2.19-8.05 1.57-7.79 1.13-7.31C.75-6.91 .57-6.53 .37-5.68L.62-5.62C1.1-6.79 1.52-7.17 2.35-7.17C3.35-7.17 4.03-6.49 4.03-5.49C4.03-4.56 3.48-3.45 2.48-2.39L.36-.14V0H5L5.66-1.63Z'/&gt;
&lt;path id='eq1-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='eq1-g4-0' d='M6.84-2.73V-3.39H.74V-2.73H6.84Z'/&gt;
&lt;path id='eq1-g4-184' d='M2.97-3.41H.36V-2.62H2.97V0H3.75V-2.62H6.36V-3.41H3.75V-6.03H2.97V-3.41Z'/&gt;
&lt;path id='eq1-g4-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='eq1-g4-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;path id='eq1-g7-48' d='M5.67-1.19L5.53-1.31L5.19-.98C4.82-.6 4.68-.49 4.57-.49C4.48-.49 4.41-.56 4.41-.64C4.41-.88 4.91-2.93 5.47-4.97C5.5-5.09 5.51-5.11 5.54-5.22L5.46-5.25L4.73-5.17L4.69-5.13L4.56-4.56C4.47-5 4.12-5.25 3.61-5.25C2.02-5.25 .2-3.08 .2-1.19C.2-.36 .66 .13 1.42 .13C2.25 .13 2.76-.26 3.81-1.74C3.56-.76 3.54-.67 3.54-.37C3.54-.02 3.68 .12 4.01 .12C4.49 .12 4.78-.11 5.67-1.19ZM4.35-4.26C4.35-3.26 3.74-1.86 2.93-.98C2.64-.66 2.24-.45 1.89-.45C1.46-.45 1.2-.79 1.2-1.33C1.2-1.98 1.62-3.14 2.12-3.89C2.6-4.6 3.13-4.99 3.62-4.99C3.64-4.99 3.66-4.99 3.68-4.99C4.09-4.97 4.35-4.68 4.35-4.26Z'/&gt;
&lt;path id='eq1-g7-49' d='M5.63-3.82C5.63-4.66 5.05-5.25 4.25-5.25C3.42-5.25 2.8-4.76 1.95-3.45L3.19-8.08L3.13-8.13C2.54-8.03 2.11-7.96 1.31-7.86V-7.66C2.01-7.63 2.08-7.6 2.08-7.34C2.08-7.23 2.06-7.1 1.98-6.82L1.92-6.6L1.89-6.52L.27-.55V-.5C.27-.23 1.18 .13 1.86 .13C3.66 .13 5.63-1.94 5.63-3.82ZM4.62-3.64C4.62-2.86 4.03-1.58 3.31-.85C2.87-.39 2.36-.14 1.85-.14C1.48-.14 1.3-.27 1.3-.55C1.3-1.26 1.66-2.41 2.16-3.3C2.68-4.22 3.22-4.67 3.79-4.67C4.31-4.67 4.62-4.28 4.62-3.64Z'/&gt;
&lt;path id='eq1-g7-50' d='M4.36-1.14L4.17-1.26C3.51-.56 3.05-.3 2.47-.3C1.79-.3 1.38-.8 1.38-1.66C1.38-2.67 1.8-3.73 2.47-4.43C2.81-4.79 3.29-5 3.76-5C4.04-5 4.2-4.91 4.2-4.76C4.2-4.7 4.19-4.65 4.13-4.54C4.05-4.38 4.03-4.31 4.03-4.19C4.03-3.91 4.2-3.74 4.49-3.74C4.82-3.74 5.06-3.97 5.06-4.29C5.06-4.85 4.54-5.25 3.81-5.25C2.02-5.25 .36-3.51 .36-1.66C.36-.52 1 .13 2.11 .13C3 .13 3.63-.23 4.36-1.14Z'/&gt;
&lt;path id='eq1-g7-67' d='M3.53-5.1H2.57L2.91-6.32C2.92-6.35 2.92-6.37 2.92-6.38C2.92-6.47 2.88-6.5 2.82-6.5C2.75-6.5 2.72-6.49 2.64-6.4C1.51-4.92 .68-5.17 .68-4.79C.68-4.78 .68-4.75 .69-4.72H1.57L.71-1.44C.68-1.29 .44-.55 .44-.32C.44-.06 .69 .13 1.01 .13C1.56 .13 1.95-.2 2.7-1.31L2.55-1.39C1.97-.64 1.77-.45 1.58-.45C1.48-.45 1.41-.55 1.41-.69C1.41-.7 1.41-.71 1.42-.75L2.47-4.72H3.47L3.53-5.1Z'/&gt;
&lt;path id='eq1-g7-71' d='M4.95-1.23L4.79-1.32C4.69-1.2 4.63-1.14 4.53-1C4.25-.64 4.12-.52 3.97-.52C3.8-.52 3.69-.68 3.61-1.01C3.59-1.12 3.57-1.18 3.56-1.2C3.28-2.32 3.13-2.97 3.13-3.14C3.66-4.06 4.09-4.59 4.3-4.59C4.37-4.59 4.48-4.55 4.59-4.49C4.73-4.41 4.81-4.38 4.92-4.38C5.16-4.38 5.32-4.56 5.32-4.81C5.32-5.07 5.12-5.25 4.84-5.25C4.31-5.25 3.87-4.82 3.04-3.55L2.91-4.2C2.74-5.01 2.61-5.25 2.29-5.25C2.01-5.25 1.63-5.16 .89-4.91L.76-4.86L.81-4.68L1.01-4.73C1.24-4.79 1.38-4.81 1.48-4.81C1.77-4.81 1.85-4.7 2.01-3.99L2.36-2.53L1.38-1.13C1.13-.77 .91-.56 .77-.56C.7-.56 .58-.6 .46-.67C.31-.75 .19-.79 .08-.79C-.15-.79-.32-.61-.32-.37C-.32-.06-.1 .13 .27 .13S.79 .02 1.38-.69L2.45-2.1L2.81-.67C2.97-.05 3.12 .13 3.5 .13C3.95 .13 4.26-.15 4.95-1.23Z'/&gt;
&lt;path id='eq1-g7-72' d='M5.07-4.6C5.07-4.95 4.78-5.25 4.42-5.25C4.14-5.25 3.95-5.07 3.95-4.81C3.95-4.62 4.05-4.5 4.29-4.35C4.51-4.22 4.6-4.11 4.6-3.94C4.6-3.47 4.17-2.55 3.14-.86L2.91-2.24C2.73-3.3 2.06-5.25 1.88-5.25H1.83L1.73-5.24L.56-5.04L.18-4.97V-4.76C.32-4.8 .42-4.81 .55-4.81C1.02-4.81 1.24-4.63 1.46-4.05C1.79-3.24 2.44-.57 2.44-.1C2.44 .04 2.39 .18 2.32 .32C2.23 .48 1.69 1.18 1.48 1.41C1.2 1.7 1.06 1.8 .91 1.8C.82 1.8 .75 1.76 .62 1.67C.44 1.52 .32 1.46 .18 1.46C-.08 1.46-.29 1.67-.29 1.93C-.29 2.24-.04 2.45 .32 2.45C1 2.45 2.29 1.08 3.51-.96C4.51-2.61 5.07-3.92 5.07-4.6Z'/&gt;
&lt;path id='eq1-g7-149' d='M2.16-.51C2.16-.88 1.85-1.19 1.49-1.19S.83-.89 .83-.51C.83-.06 1.24 .13 1.49 .13S2.16-.07 2.16-.51Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq1-page1'&gt;
&lt;use x='100.22' y='-2.48' xlink:href='#eq1-g7-72'/&gt;
&lt;use x='106.58' y='-2.48' xlink:href='#eq1-g4-185'/&gt;
&lt;use x='110.99' y='-2.48' xlink:href='#eq1-g7-67'/&gt;
&lt;use x='115.01' y='-2.48' xlink:href='#eq1-g4-186'/&gt;
&lt;use x='122.91' y='-2.48' xlink:href='#eq1-g1-61'/&gt;
&lt;use x='133.46' y='-2.48' xlink:href='#eq1-g7-48'/&gt;
&lt;use x='139.88' y='-2.48' xlink:href='#eq1-g7-71'/&gt;
&lt;use x='146.37' y='-2.48' xlink:href='#eq1-g4-185'/&gt;
&lt;use x='150.78' y='-2.48' xlink:href='#eq1-g7-67'/&gt;
&lt;use x='154.79' y='-2.48' xlink:href='#eq1-g4-186'/&gt;
&lt;use x='162.03' y='-2.48' xlink:href='#eq1-g4-184'/&gt;
&lt;use x='171.72' y='-2.48' xlink:href='#eq1-g7-49'/&gt;
&lt;use x='178.61' y='-2.48' xlink:href='#eq1-g7-72'/&gt;
&lt;use x='184.97' y='-2.48' xlink:href='#eq1-g4-185'/&gt;
&lt;use x='189.39' y='-2.48' xlink:href='#eq1-g7-67'/&gt;
&lt;use x='196.06' y='-2.48' xlink:href='#eq1-g4-0'/&gt;
&lt;use x='206.32' y='-2.48' xlink:href='#eq1-g11-49'/&gt;
&lt;use x='212.3' y='-2.48' xlink:href='#eq1-g4-186'/&gt;
&lt;use x='219.53' y='-2.48' xlink:href='#eq1-g4-184'/&gt;
&lt;use x='229.06' y='-2.48' xlink:href='#eq1-g7-50'/&gt;
&lt;use x='235.2' y='-2.48' xlink:href='#eq1-g7-72'/&gt;
&lt;use x='241.56' y='-2.48' xlink:href='#eq1-g4-185'/&gt;
&lt;use x='245.97' y='-2.48' xlink:href='#eq1-g7-67'/&gt;
&lt;use x='252.65' y='-2.48' xlink:href='#eq1-g4-0'/&gt;
&lt;use x='262.91' y='-2.48' xlink:href='#eq1-g11-50'/&gt;
&lt;use x='268.89' y='-2.48' xlink:href='#eq1-g4-186'/&gt;
&lt;use x='285.61' y='-2.48' xlink:href='#eq1-g7-149'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/div&gt;
&lt;p&gt;To see what the impulse resonse of this filter is, we&amp;rsquo;d have to apply it to an impulse signal (a Dirac delta). If
we were to do this, we&amp;rsquo;d find that the impulse resonse (or kernel) &lt;span class="math"&gt;&lt;svg style="width: 1.864em; height: 1.095em; vertical-align: -0.241em; " viewBox=".43 -8.54 18.64 10.95"&gt;
&lt;title&gt;
\(h(t)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq11-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='eq11-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;path id='eq11-g4-17' d='M5.69-1.25L5.54-1.39C4.9-.58 4.75-.45 4.56-.45C4.45-.45 4.37-.55 4.37-.67C4.37-.8 4.56-1.54 4.76-2.16C5.12-3.28 5.34-4.16 5.34-4.45C5.34-4.93 5.01-5.25 4.55-5.25C3.78-5.25 2.93-4.48 1.82-2.74L3.25-8.08L3.19-8.13C2.5-7.98 2.04-7.9 1.32-7.81V-7.63C1.38-7.63 1.45-7.63 1.52-7.63C1.82-7.63 2.12-7.61 2.12-7.35C2.12-7.07 1.93-6.46 1.83-6.1L.23 0H1.12C1.6-1.83 1.75-2.24 2.23-2.97C2.85-3.91 3.66-4.65 4.07-4.65C4.25-4.65 4.41-4.5 4.41-4.35C4.41-4.3 4.37-4.14 4.32-3.95L3.67-1.49C3.51-.91 3.43-.54 3.43-.39C3.43-.08 3.63 .11 3.95 .11C4.56 .11 4.97-.21 5.69-1.25Z'/&gt;
&lt;path id='eq11-g4-67' d='M3.53-5.1H2.57L2.91-6.32C2.92-6.35 2.92-6.37 2.92-6.38C2.92-6.47 2.88-6.5 2.82-6.5C2.75-6.5 2.72-6.49 2.64-6.4C1.51-4.92 .68-5.17 .68-4.79C.68-4.78 .68-4.75 .69-4.72H1.57L.71-1.44C.68-1.29 .44-.55 .44-.32C.44-.06 .69 .13 1.01 .13C1.56 .13 1.95-.2 2.7-1.31L2.55-1.39C1.97-.64 1.77-.45 1.58-.45C1.48-.45 1.41-.55 1.41-.69C1.41-.7 1.41-.71 1.42-.75L2.47-4.72H3.47L3.53-5.1Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq11-page1'&gt;
&lt;use x='.43' y='0' xlink:href='#eq11-g4-17'/&gt;
&lt;use x='7.24' y='0' xlink:href='#eq11-g1-185'/&gt;
&lt;use x='11.66' y='0' xlink:href='#eq11-g4-67'/&gt;
&lt;use x='15.67' y='0' xlink:href='#eq11-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; is zero for &lt;span class="math"&gt;&lt;svg style="width: 2.445em; height: 0.822em; vertical-align: -0.017em; " viewBox="-.05 -8.05 24.45 8.22"&gt;
&lt;title&gt;
\(t&amp;lt;0\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq12-g5-48' d='M5.67-3.93C5.67-6.37 4.59-8.05 3.03-8.05C2.37-8.05 1.87-7.85 1.43-7.43C.74-6.77 .29-5.4 .29-4C.29-2.7 .68-1.31 1.24-.64C1.68-.12 2.29 .17 2.98 .17C3.59 .17 4.1-.04 4.53-.45C5.22-1.11 5.67-2.49 5.67-3.93ZM4.53-3.91C4.53-1.42 4-.14 2.98-.14S1.43-1.42 1.43-3.89C1.43-6.42 1.97-7.74 2.99-7.74C3.99-7.74 4.53-6.4 4.53-3.91Z'/&gt;
&lt;path id='eq12-g1-67' d='M3.53-5.1H2.57L2.91-6.32C2.92-6.35 2.92-6.37 2.92-6.38C2.92-6.47 2.88-6.5 2.82-6.5C2.75-6.5 2.72-6.49 2.64-6.4C1.51-4.92 .68-5.17 .68-4.79C.68-4.78 .68-4.75 .69-4.72H1.57L.71-1.44C.68-1.29 .44-.55 .44-.32C.44-.06 .69 .13 1.01 .13C1.56 .13 1.95-.2 2.7-1.31L2.55-1.39C1.97-.64 1.77-.45 1.58-.45C1.48-.45 1.41-.55 1.41-.69C1.41-.7 1.41-.71 1.42-.75L2.47-4.72H3.47L3.53-5.1Z'/&gt;
&lt;path id='eq12-g1-159' d='M6.84-.07V-.74L1.88-3.06L6.84-5.38V-6.05L.74-3.18V-2.94L6.84-.07Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq12-page1'&gt;
&lt;use x='-.05' y='0' xlink:href='#eq12-g1-67'/&gt;
&lt;use x='7.54' y='0' xlink:href='#eq12-g1-159'/&gt;
&lt;use x='18.73' y='0' xlink:href='#eq12-g5-48'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;, and non-zero for
&lt;span class="math"&gt;&lt;svg style="width: 3.152em; height: 0.822em; vertical-align: -0.017em; " viewBox="-.05 -8.05 31.52 8.22"&gt;
&lt;title&gt;
\(t&amp;gt;=0\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq13-g8-48' d='M5.67-3.93C5.67-6.37 4.59-8.05 3.03-8.05C2.37-8.05 1.87-7.85 1.43-7.43C.74-6.77 .29-5.4 .29-4C.29-2.7 .68-1.31 1.24-.64C1.68-.12 2.29 .17 2.98 .17C3.59 .17 4.1-.04 4.53-.45C5.22-1.11 5.67-2.49 5.67-3.93ZM4.53-3.91C4.53-1.42 4-.14 2.98-.14S1.43-1.42 1.43-3.89C1.43-6.42 1.97-7.74 2.99-7.74C3.99-7.74 4.53-6.4 4.53-3.91Z'/&gt;
&lt;path id='eq13-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='eq13-g4-67' d='M3.53-5.1H2.57L2.91-6.32C2.92-6.35 2.92-6.37 2.92-6.38C2.92-6.47 2.88-6.5 2.82-6.5C2.75-6.5 2.72-6.49 2.64-6.4C1.51-4.92 .68-5.17 .68-4.79C.68-4.78 .68-4.75 .69-4.72H1.57L.71-1.44C.68-1.29 .44-.55 .44-.32C.44-.06 .69 .13 1.01 .13C1.56 .13 1.95-.2 2.7-1.31L2.55-1.39C1.97-.64 1.77-.45 1.58-.45C1.48-.45 1.41-.55 1.41-.69C1.41-.7 1.41-.71 1.42-.75L2.47-4.72H3.47L3.53-5.1Z'/&gt;
&lt;path id='eq13-g4-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='eq13-page1'&gt;
&lt;use x='-.05' y='0' xlink:href='#eq13-g4-67'/&gt;
&lt;use x='7.54' y='0' xlink:href='#eq13-g4-161'/&gt;
&lt;use x='15.41' y='0' xlink:href='#eq13-g1-61'/&gt;
&lt;use x='25.81' y='0' xlink:href='#eq13-g8-48'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;. That is, it is &lt;em&gt;causal&lt;/em&gt; (it doesn&amp;rsquo;t depend on any future data), and it is infinite (it never really ends).&lt;/p&gt;
&lt;p&gt;An IIR filter can also be &lt;em&gt;anti-causal&lt;/em&gt; (does not depend on any past data), such a filter would be computed backwards
in time.&lt;/p&gt;
&lt;p&gt;A FIR filer uses only a finite set of input values to compute the current output value. For example,&lt;/p&gt;
&lt;div class="math"&gt;&lt;svg style="width: 18.627em; height: 1.099em; vertical-align: 0.003em; " viewBox="100.72 -11.02 186.27 10.99"&gt;
&lt;title&gt;
\[ y(t) = a x(t-1) + b x(t) + c x(t+1) \quad .\]
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq2-g11-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='eq2-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='eq2-g4-0' d='M6.84-2.73V-3.39H.74V-2.73H6.84Z'/&gt;
&lt;path id='eq2-g4-184' d='M2.97-3.41H.36V-2.62H2.97V0H3.75V-2.62H6.36V-3.41H3.75V-6.03H2.97V-3.41Z'/&gt;
&lt;path id='eq2-g4-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-g4-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;path id='eq2-g7-48' d='M5.67-1.19L5.53-1.31L5.19-.98C4.82-.6 4.68-.49 4.57-.49C4.48-.49 4.41-.56 4.41-.64C4.41-.88 4.91-2.93 5.47-4.97C5.5-5.09 5.51-5.11 5.54-5.22L5.46-5.25L4.73-5.17L4.69-5.13L4.56-4.56C4.47-5 4.12-5.25 3.61-5.25C2.02-5.25 .2-3.08 .2-1.19C.2-.36 .66 .13 1.42 .13C2.25 .13 2.76-.26 3.81-1.74C3.56-.76 3.54-.67 3.54-.37C3.54-.02 3.68 .12 4.01 .12C4.49 .12 4.78-.11 5.67-1.19ZM4.35-4.26C4.35-3.26 3.74-1.86 2.93-.98C2.64-.66 2.24-.45 1.89-.45C1.46-.45 1.2-.79 1.2-1.33C1.2-1.98 1.62-3.14 2.12-3.89C2.6-4.6 3.13-4.99 3.62-4.99C3.64-4.99 3.66-4.99 3.68-4.99C4.09-4.97 4.35-4.68 4.35-4.26Z'/&gt;
&lt;path id='eq2-g7-49' d='M5.63-3.82C5.63-4.66 5.05-5.25 4.25-5.25C3.42-5.25 2.8-4.76 1.95-3.45L3.19-8.08L3.13-8.13C2.54-8.03 2.11-7.96 1.31-7.86V-7.66C2.01-7.63 2.08-7.6 2.08-7.34C2.08-7.23 2.06-7.1 1.98-6.82L1.92-6.6L1.89-6.52L.27-.55V-.5C.27-.23 1.18 .13 1.86 .13C3.66 .13 5.63-1.94 5.63-3.82ZM4.62-3.64C4.62-2.86 4.03-1.58 3.31-.85C2.87-.39 2.36-.14 1.85-.14C1.48-.14 1.3-.27 1.3-.55C1.3-1.26 1.66-2.41 2.16-3.3C2.68-4.22 3.22-4.67 3.79-4.67C4.31-4.67 4.62-4.28 4.62-3.64Z'/&gt;
&lt;path id='eq2-g7-50' d='M4.36-1.14L4.17-1.26C3.51-.56 3.05-.3 2.47-.3C1.79-.3 1.38-.8 1.38-1.66C1.38-2.67 1.8-3.73 2.47-4.43C2.81-4.79 3.29-5 3.76-5C4.04-5 4.2-4.91 4.2-4.76C4.2-4.7 4.19-4.65 4.13-4.54C4.05-4.38 4.03-4.31 4.03-4.19C4.03-3.91 4.2-3.74 4.49-3.74C4.82-3.74 5.06-3.97 5.06-4.29C5.06-4.85 4.54-5.25 3.81-5.25C2.02-5.25 .36-3.51 .36-1.66C.36-.52 1 .13 2.11 .13C3 .13 3.63-.23 4.36-1.14Z'/&gt;
&lt;path id='eq2-g7-67' d='M3.53-5.1H2.57L2.91-6.32C2.92-6.35 2.92-6.37 2.92-6.38C2.92-6.47 2.88-6.5 2.82-6.5C2.75-6.5 2.72-6.49 2.64-6.4C1.51-4.92 .68-5.17 .68-4.79C.68-4.78 .68-4.75 .69-4.72H1.57L.71-1.44C.68-1.29 .44-.55 .44-.32C.44-.06 .69 .13 1.01 .13C1.56 .13 1.95-.2 2.7-1.31L2.55-1.39C1.97-.64 1.77-.45 1.58-.45C1.48-.45 1.41-.55 1.41-.69C1.41-.7 1.41-.71 1.42-.75L2.47-4.72H3.47L3.53-5.1Z'/&gt;
&lt;path id='eq2-g7-71' d='M4.95-1.23L4.79-1.32C4.69-1.2 4.63-1.14 4.53-1C4.25-.64 4.12-.52 3.97-.52C3.8-.52 3.69-.68 3.61-1.01C3.59-1.12 3.57-1.18 3.56-1.2C3.28-2.32 3.13-2.97 3.13-3.14C3.66-4.06 4.09-4.59 4.3-4.59C4.37-4.59 4.48-4.55 4.59-4.49C4.73-4.41 4.81-4.38 4.92-4.38C5.16-4.38 5.32-4.56 5.32-4.81C5.32-5.07 5.12-5.25 4.84-5.25C4.31-5.25 3.87-4.82 3.04-3.55L2.91-4.2C2.74-5.01 2.61-5.25 2.29-5.25C2.01-5.25 1.63-5.16 .89-4.91L.76-4.86L.81-4.68L1.01-4.73C1.24-4.79 1.38-4.81 1.48-4.81C1.77-4.81 1.85-4.7 2.01-3.99L2.36-2.53L1.38-1.13C1.13-.77 .91-.56 .77-.56C.7-.56 .58-.6 .46-.67C.31-.75 .19-.79 .08-.79C-.15-.79-.32-.61-.32-.37C-.32-.06-.1 .13 .27 .13S.79 .02 1.38-.69L2.45-2.1L2.81-.67C2.97-.05 3.12 .13 3.5 .13C3.95 .13 4.26-.15 4.95-1.23Z'/&gt;
&lt;path id='eq2-g7-72' d='M5.07-4.6C5.07-4.95 4.78-5.25 4.42-5.25C4.14-5.25 3.95-5.07 3.95-4.81C3.95-4.62 4.05-4.5 4.29-4.35C4.51-4.22 4.6-4.11 4.6-3.94C4.6-3.47 4.17-2.55 3.14-.86L2.91-2.24C2.73-3.3 2.06-5.25 1.88-5.25H1.83L1.73-5.24L.56-5.04L.18-4.97V-4.76C.32-4.8 .42-4.81 .55-4.81C1.02-4.81 1.24-4.63 1.46-4.05C1.79-3.24 2.44-.57 2.44-.1C2.44 .04 2.39 .18 2.32 .32C2.23 .48 1.69 1.18 1.48 1.41C1.2 1.7 1.06 1.8 .91 1.8C.82 1.8 .75 1.76 .62 1.67C.44 1.52 .32 1.46 .18 1.46C-.08 1.46-.29 1.67-.29 1.93C-.29 2.24-.04 2.45 .32 2.45C1 2.45 2.29 1.08 3.51-.96C4.51-2.61 5.07-3.92 5.07-4.6Z'/&gt;
&lt;path id='eq2-g7-149' d='M2.16-.51C2.16-.88 1.85-1.19 1.49-1.19S.83-.89 .83-.51C.83-.06 1.24 .13 1.49 .13S2.16-.07 2.16-.51Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq2-page1'&gt;
&lt;use x='101' y='-2.48' xlink:href='#eq2-g7-72'/&gt;
&lt;use x='107.36' y='-2.48' xlink:href='#eq2-g4-185'/&gt;
&lt;use x='111.77' y='-2.48' xlink:href='#eq2-g7-67'/&gt;
&lt;use x='115.79' y='-2.48' xlink:href='#eq2-g4-186'/&gt;
&lt;use x='123.69' y='-2.48' xlink:href='#eq2-g1-61'/&gt;
&lt;use x='134.24' y='-2.48' xlink:href='#eq2-g7-48'/&gt;
&lt;use x='140.66' y='-2.48' xlink:href='#eq2-g7-71'/&gt;
&lt;use x='147.15' y='-2.48' xlink:href='#eq2-g4-185'/&gt;
&lt;use x='151.56' y='-2.48' xlink:href='#eq2-g7-67'/&gt;
&lt;use x='158.23' y='-2.48' xlink:href='#eq2-g4-0'/&gt;
&lt;use x='168.49' y='-2.48' xlink:href='#eq2-g11-49'/&gt;
&lt;use x='174.47' y='-2.48' xlink:href='#eq2-g4-186'/&gt;
&lt;use x='181.71' y='-2.48' xlink:href='#eq2-g4-184'/&gt;
&lt;use x='191.39' y='-2.48' xlink:href='#eq2-g7-49'/&gt;
&lt;use x='197.81' y='-2.48' xlink:href='#eq2-g7-71'/&gt;
&lt;use x='204.3' y='-2.48' xlink:href='#eq2-g4-185'/&gt;
&lt;use x='208.71' y='-2.48' xlink:href='#eq2-g7-67'/&gt;
&lt;use x='212.73' y='-2.48' xlink:href='#eq2-g4-186'/&gt;
&lt;use x='219.96' y='-2.48' xlink:href='#eq2-g4-184'/&gt;
&lt;use x='229.49' y='-2.48' xlink:href='#eq2-g7-50'/&gt;
&lt;use x='235.15' y='-2.48' xlink:href='#eq2-g7-71'/&gt;
&lt;use x='241.64' y='-2.48' xlink:href='#eq2-g4-185'/&gt;
&lt;use x='246.05' y='-2.48' xlink:href='#eq2-g7-67'/&gt;
&lt;use x='252.73' y='-2.48' xlink:href='#eq2-g4-184'/&gt;
&lt;use x='262.12' y='-2.48' xlink:href='#eq2-g11-49'/&gt;
&lt;use x='268.1' y='-2.48' xlink:href='#eq2-g4-186'/&gt;
&lt;use x='284.83' y='-2.48' xlink:href='#eq2-g7-149'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/div&gt;
&lt;p&gt;A FIR filter can also be causal or anti-causal, but it can also be neither, if it uses past and future input samples.&lt;/p&gt;
&lt;h3&gt;Implementation&lt;/h3&gt;
&lt;p&gt;Both the FIR and IIR filters can be implemented quite trivially, except for a few details we&amp;rsquo;ll discuss below.
But most output samples we can compute simply by applying the equation that defines the filter. In the FIR case,
each output value can be computed independently of others, and so is trivial to parallelize. The FIR filter,
having a recursive definition, must be computed left to right (for the causal case) or right to left (for anti-causal
case).&lt;/p&gt;
&lt;p&gt;In some cases, there are some tricks worth thinking about. For example, if the FIR kernel is symmetric,
the computations can be rearranged to reduce cost:&lt;/p&gt;
&lt;div class="math"&gt;&lt;svg style="width: 19.349em; height: 2.838em; vertical-align: 0.193em; " viewBox="97.1 -30.31 193.49 28.38"&gt;
&lt;title&gt;
\[
\begin{split}
y(t) &amp;amp;= a x(t-1) + b x(t) + a x(t+1) \\
     &amp;amp;= a \left\{ x(t-1) + x(t+1) \right\} + b x(t) \quad .
\end{split}
\]
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq3-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='eq3-g11-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='eq3-g4-0' d='M6.84-2.73V-3.39H.74V-2.73H6.84Z'/&gt;
&lt;path id='eq3-g4-102' d='M2.91-6.68C2.91-7.12 3.26-8 4.22-8.24C4.28-8.27 4.32-8.3 4.32-8.36C4.32-8.48 4.23-8.5 4.1-8.48C3.17-8.34 2.06-7.58 2.05-6.78V-4.66C2.05-4.24 1.98-3.64 1.81-3.54C1.38-3.24 .94-3.22 .68-3.22C.62-3.2 .57-3.13 .57-3.07C.57-2.95 .64-2.93 .76-2.92C1.55-2.88 1.88-2.51 2.01-1.98C2.05-1.86 2.05-1.85 2.05-1.45V.45C2.05 .91 1.93 1.44 2.5 1.85C2.97 2.17 3.63 2.36 4.1 2.36C4.23 2.36 4.32 2.36 4.32 2.23C4.32 2.12 4.25 2.14 4.13 2.11C3.33 1.91 3.1 1.49 2.94 .93C2.91 .83 2.91 .81 2.91 .43V-1.62C2.91-2.11 2.81-2.3 2.43-2.64C2.18-2.87 2.07-2.98 1.74-3.07C2.72-3.31 2.91-3.81 2.91-4.44V-6.68Z'/&gt;
&lt;path id='eq3-g4-103' d='M1.99-6.68V-4.44C1.99-3.81 2.18-3.31 3.16-3.07C2.82-2.98 2.72-2.87 2.47-2.64C2.08-2.3 1.99-2.11 1.99-1.62V.43C1.99 .81 1.99 .83 1.95 .93C1.8 1.49 1.56 1.91 .76 2.11C.64 2.14 .57 2.12 .57 2.23C.57 2.36 .67 2.36 .8 2.36C1.26 2.36 1.93 2.17 2.39 1.85C2.97 1.44 2.85 .91 2.85 .45V-1.45C2.85-1.85 2.85-1.86 2.88-1.98C3.01-2.51 3.35-2.88 4.13-2.92C4.25-2.93 4.32-2.95 4.32-3.07C4.32-3.13 4.28-3.2 4.22-3.22C3.95-3.22 3.51-3.24 3.08-3.54C2.92-3.64 2.85-4.24 2.85-4.66V-6.78C2.83-7.58 1.73-8.34 .8-8.48C.67-8.5 .57-8.48 .57-8.36C.57-8.3 .62-8.27 .68-8.24C1.63-8 1.99-7.12 1.99-6.68Z'/&gt;
&lt;path id='eq3-g4-184' d='M2.97-3.41H.36V-2.62H2.97V0H3.75V-2.62H6.36V-3.41H3.75V-6.03H2.97V-3.41Z'/&gt;
&lt;path id='eq3-g4-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-g4-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;path id='eq3-g7-48' d='M5.67-1.19L5.53-1.31L5.19-.98C4.82-.6 4.68-.49 4.57-.49C4.48-.49 4.41-.56 4.41-.64C4.41-.88 4.91-2.93 5.47-4.97C5.5-5.09 5.51-5.11 5.54-5.22L5.46-5.25L4.73-5.17L4.69-5.13L4.56-4.56C4.47-5 4.12-5.25 3.61-5.25C2.02-5.25 .2-3.08 .2-1.19C.2-.36 .66 .13 1.42 .13C2.25 .13 2.76-.26 3.81-1.74C3.56-.76 3.54-.67 3.54-.37C3.54-.02 3.68 .12 4.01 .12C4.49 .12 4.78-.11 5.67-1.19ZM4.35-4.26C4.35-3.26 3.74-1.86 2.93-.98C2.64-.66 2.24-.45 1.89-.45C1.46-.45 1.2-.79 1.2-1.33C1.2-1.98 1.62-3.14 2.12-3.89C2.6-4.6 3.13-4.99 3.62-4.99C3.64-4.99 3.66-4.99 3.68-4.99C4.09-4.97 4.35-4.68 4.35-4.26Z'/&gt;
&lt;path id='eq3-g7-49' d='M5.63-3.82C5.63-4.66 5.05-5.25 4.25-5.25C3.42-5.25 2.8-4.76 1.95-3.45L3.19-8.08L3.13-8.13C2.54-8.03 2.11-7.96 1.31-7.86V-7.66C2.01-7.63 2.08-7.6 2.08-7.34C2.08-7.23 2.06-7.1 1.98-6.82L1.92-6.6L1.89-6.52L.27-.55V-.5C.27-.23 1.18 .13 1.86 .13C3.66 .13 5.63-1.94 5.63-3.82ZM4.62-3.64C4.62-2.86 4.03-1.58 3.31-.85C2.87-.39 2.36-.14 1.85-.14C1.48-.14 1.3-.27 1.3-.55C1.3-1.26 1.66-2.41 2.16-3.3C2.68-4.22 3.22-4.67 3.79-4.67C4.31-4.67 4.62-4.28 4.62-3.64Z'/&gt;
&lt;path id='eq3-g7-67' d='M3.53-5.1H2.57L2.91-6.32C2.92-6.35 2.92-6.37 2.92-6.38C2.92-6.47 2.88-6.5 2.82-6.5C2.75-6.5 2.72-6.49 2.64-6.4C1.51-4.92 .68-5.17 .68-4.79C.68-4.78 .68-4.75 .69-4.72H1.57L.71-1.44C.68-1.29 .44-.55 .44-.32C.44-.06 .69 .13 1.01 .13C1.56 .13 1.95-.2 2.7-1.31L2.55-1.39C1.97-.64 1.77-.45 1.58-.45C1.48-.45 1.41-.55 1.41-.69C1.41-.7 1.41-.71 1.42-.75L2.47-4.72H3.47L3.53-5.1Z'/&gt;
&lt;path id='eq3-g7-71' d='M4.95-1.23L4.79-1.32C4.69-1.2 4.63-1.14 4.53-1C4.25-.64 4.12-.52 3.97-.52C3.8-.52 3.69-.68 3.61-1.01C3.59-1.12 3.57-1.18 3.56-1.2C3.28-2.32 3.13-2.97 3.13-3.14C3.66-4.06 4.09-4.59 4.3-4.59C4.37-4.59 4.48-4.55 4.59-4.49C4.73-4.41 4.81-4.38 4.92-4.38C5.16-4.38 5.32-4.56 5.32-4.81C5.32-5.07 5.12-5.25 4.84-5.25C4.31-5.25 3.87-4.82 3.04-3.55L2.91-4.2C2.74-5.01 2.61-5.25 2.29-5.25C2.01-5.25 1.63-5.16 .89-4.91L.76-4.86L.81-4.68L1.01-4.73C1.24-4.79 1.38-4.81 1.48-4.81C1.77-4.81 1.85-4.7 2.01-3.99L2.36-2.53L1.38-1.13C1.13-.77 .91-.56 .77-.56C.7-.56 .58-.6 .46-.67C.31-.75 .19-.79 .08-.79C-.15-.79-.32-.61-.32-.37C-.32-.06-.1 .13 .27 .13S.79 .02 1.38-.69L2.45-2.1L2.81-.67C2.97-.05 3.12 .13 3.5 .13C3.95 .13 4.26-.15 4.95-1.23Z'/&gt;
&lt;path id='eq3-g7-72' d='M5.07-4.6C5.07-4.95 4.78-5.25 4.42-5.25C4.14-5.25 3.95-5.07 3.95-4.81C3.95-4.62 4.05-4.5 4.29-4.35C4.51-4.22 4.6-4.11 4.6-3.94C4.6-3.47 4.17-2.55 3.14-.86L2.91-2.24C2.73-3.3 2.06-5.25 1.88-5.25H1.83L1.73-5.24L.56-5.04L.18-4.97V-4.76C.32-4.8 .42-4.81 .55-4.81C1.02-4.81 1.24-4.63 1.46-4.05C1.79-3.24 2.44-.57 2.44-.1C2.44 .04 2.39 .18 2.32 .32C2.23 .48 1.69 1.18 1.48 1.41C1.2 1.7 1.06 1.8 .91 1.8C.82 1.8 .75 1.76 .62 1.67C.44 1.52 .32 1.46 .18 1.46C-.08 1.46-.29 1.67-.29 1.93C-.29 2.24-.04 2.45 .32 2.45C1 2.45 2.29 1.08 3.51-.96C4.51-2.61 5.07-3.92 5.07-4.6Z'/&gt;
&lt;path id='eq3-g7-149' d='M2.16-.51C2.16-.88 1.85-1.19 1.49-1.19S.83-.89 .83-.51C.83-.06 1.24 .13 1.49 .13S2.16-.07 2.16-.51Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq3-page1'&gt;
&lt;use x='97.39' y='-21.77' xlink:href='#eq3-g7-72'/&gt;
&lt;use x='103.75' y='-21.77' xlink:href='#eq3-g4-185'/&gt;
&lt;use x='108.16' y='-21.77' xlink:href='#eq3-g7-67'/&gt;
&lt;use x='112.18' y='-21.77' xlink:href='#eq3-g4-186'/&gt;
&lt;use x='120.08' y='-21.77' xlink:href='#eq3-g1-61'/&gt;
&lt;use x='130.63' y='-21.77' xlink:href='#eq3-g7-48'/&gt;
&lt;use x='137.05' y='-21.77' xlink:href='#eq3-g7-71'/&gt;
&lt;use x='143.54' y='-21.77' xlink:href='#eq3-g4-185'/&gt;
&lt;use x='147.95' y='-21.77' xlink:href='#eq3-g7-67'/&gt;
&lt;use x='154.62' y='-21.77' xlink:href='#eq3-g4-0'/&gt;
&lt;use x='164.88' y='-21.77' xlink:href='#eq3-g11-49'/&gt;
&lt;use x='170.86' y='-21.77' xlink:href='#eq3-g4-186'/&gt;
&lt;use x='178.1' y='-21.77' xlink:href='#eq3-g4-184'/&gt;
&lt;use x='187.78' y='-21.77' xlink:href='#eq3-g7-49'/&gt;
&lt;use x='194.2' y='-21.77' xlink:href='#eq3-g7-71'/&gt;
&lt;use x='200.69' y='-21.77' xlink:href='#eq3-g4-185'/&gt;
&lt;use x='205.1' y='-21.77' xlink:href='#eq3-g7-67'/&gt;
&lt;use x='209.12' y='-21.77' xlink:href='#eq3-g4-186'/&gt;
&lt;use x='216.35' y='-21.77' xlink:href='#eq3-g4-184'/&gt;
&lt;use x='225.91' y='-21.77' xlink:href='#eq3-g7-48'/&gt;
&lt;use x='232.33' y='-21.77' xlink:href='#eq3-g7-71'/&gt;
&lt;use x='238.81' y='-21.77' xlink:href='#eq3-g4-185'/&gt;
&lt;use x='243.22' y='-21.77' xlink:href='#eq3-g7-67'/&gt;
&lt;use x='249.9' y='-21.77' xlink:href='#eq3-g4-184'/&gt;
&lt;use x='259.3' y='-21.77' xlink:href='#eq3-g11-49'/&gt;
&lt;use x='265.27' y='-21.77' xlink:href='#eq3-g4-186'/&gt;
&lt;use x='120.08' y='-4.33' xlink:href='#eq3-g1-61'/&gt;
&lt;use x='130.63' y='-4.33' xlink:href='#eq3-g7-48'/&gt;
&lt;use x='139.34' y='-4.35' xlink:href='#eq3-g4-102'/&gt;
&lt;use x='144.56' y='-4.33' xlink:href='#eq3-g7-71'/&gt;
&lt;use x='151.04' y='-4.33' xlink:href='#eq3-g4-185'/&gt;
&lt;use x='155.45' y='-4.33' xlink:href='#eq3-g7-67'/&gt;
&lt;use x='162.13' y='-4.33' xlink:href='#eq3-g4-0'/&gt;
&lt;use x='172.39' y='-4.33' xlink:href='#eq3-g11-49'/&gt;
&lt;use x='178.36' y='-4.33' xlink:href='#eq3-g4-186'/&gt;
&lt;use x='185.6' y='-4.33' xlink:href='#eq3-g4-184'/&gt;
&lt;use x='195' y='-4.33' xlink:href='#eq3-g7-71'/&gt;
&lt;use x='201.48' y='-4.33' xlink:href='#eq3-g4-185'/&gt;
&lt;use x='205.9' y='-4.33' xlink:href='#eq3-g7-67'/&gt;
&lt;use x='212.57' y='-4.33' xlink:href='#eq3-g4-184'/&gt;
&lt;use x='221.97' y='-4.33' xlink:href='#eq3-g11-49'/&gt;
&lt;use x='227.95' y='-4.33' xlink:href='#eq3-g4-186'/&gt;
&lt;use x='232.82' y='-4.35' xlink:href='#eq3-g4-103'/&gt;
&lt;use x='240.69' y='-4.33' xlink:href='#eq3-g4-184'/&gt;
&lt;use x='250.38' y='-4.33' xlink:href='#eq3-g7-49'/&gt;
&lt;use x='256.8' y='-4.33' xlink:href='#eq3-g7-71'/&gt;
&lt;use x='263.29' y='-4.33' xlink:href='#eq3-g4-185'/&gt;
&lt;use x='267.7' y='-4.33' xlink:href='#eq3-g7-67'/&gt;
&lt;use x='271.71' y='-4.33' xlink:href='#eq3-g4-186'/&gt;
&lt;use x='288.44' y='-4.33' xlink:href='#eq3-g7-149'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/div&gt;
&lt;p&gt;For longer kernels the savings can be significant.&lt;/p&gt;
&lt;h3&gt;Boundary condition&lt;/h3&gt;
&lt;p&gt;For both types of filter, we need to decide what happens when we need to read values outside the domain.
In the FIR case, for &lt;span class="math"&gt;&lt;svg style="width: 2.341em; height: 0.822em; vertical-align: -0.017em; " viewBox="-.05 -8.05 23.41 8.22"&gt;
&lt;title&gt;
\(t=0\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq14-g8-48' d='M5.67-3.93C5.67-6.37 4.59-8.05 3.03-8.05C2.37-8.05 1.87-7.85 1.43-7.43C.74-6.77 .29-5.4 .29-4C.29-2.7 .68-1.31 1.24-.64C1.68-.12 2.29 .17 2.98 .17C3.59 .17 4.1-.04 4.53-.45C5.22-1.11 5.67-2.49 5.67-3.93ZM4.53-3.91C4.53-1.42 4-.14 2.98-.14S1.43-1.42 1.43-3.89C1.43-6.42 1.97-7.74 2.99-7.74C3.99-7.74 4.53-6.4 4.53-3.91Z'/&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-67' d='M3.53-5.1H2.57L2.91-6.32C2.92-6.35 2.92-6.37 2.92-6.38C2.92-6.47 2.88-6.5 2.82-6.5C2.75-6.5 2.72-6.49 2.64-6.4C1.51-4.92 .68-5.17 .68-4.79C.68-4.78 .68-4.75 .69-4.72H1.57L.71-1.44C.68-1.29 .44-.55 .44-.32C.44-.06 .69 .13 1.01 .13C1.56 .13 1.95-.2 2.7-1.31L2.55-1.39C1.97-.64 1.77-.45 1.58-.45C1.48-.45 1.41-.55 1.41-.69C1.41-.7 1.41-.71 1.42-.75L2.47-4.72H3.47L3.53-5.1Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq14-page1'&gt;
&lt;use x='-.05' y='0' xlink:href='#eq14-g4-67'/&gt;
&lt;use x='7.29' y='0' xlink:href='#eq14-g1-61'/&gt;
&lt;use x='17.69' y='0' xlink:href='#eq14-g8-48'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; we need to fill in a value for &lt;span class="math"&gt;&lt;svg style="width: 2.824em; height: 1.095em; vertical-align: -0.241em; " viewBox="-.32 -8.54 28.24 10.95"&gt;
&lt;title&gt;
\(x(-1)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq15-g8-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='eq15-g1-0' d='M6.84-2.73V-3.39H.74V-2.73H6.84Z'/&gt;
&lt;path id='eq15-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='eq15-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;path id='eq15-g4-71' d='M4.95-1.23L4.79-1.32C4.69-1.2 4.63-1.14 4.53-1C4.25-.64 4.12-.52 3.97-.52C3.8-.52 3.69-.68 3.61-1.01C3.59-1.12 3.57-1.18 3.56-1.2C3.28-2.32 3.13-2.97 3.13-3.14C3.66-4.06 4.09-4.59 4.3-4.59C4.37-4.59 4.48-4.55 4.59-4.49C4.73-4.41 4.81-4.38 4.92-4.38C5.16-4.38 5.32-4.56 5.32-4.81C5.32-5.07 5.12-5.25 4.84-5.25C4.31-5.25 3.87-4.82 3.04-3.55L2.91-4.2C2.74-5.01 2.61-5.25 2.29-5.25C2.01-5.25 1.63-5.16 .89-4.91L.76-4.86L.81-4.68L1.01-4.73C1.24-4.79 1.38-4.81 1.48-4.81C1.77-4.81 1.85-4.7 2.01-3.99L2.36-2.53L1.38-1.13C1.13-.77 .91-.56 .77-.56C.7-.56 .58-.6 .46-.67C.31-.75 .19-.79 .08-.79C-.15-.79-.32-.61-.32-.37C-.32-.06-.1 .13 .27 .13S.79 .02 1.38-.69L2.45-2.1L2.81-.67C2.97-.05 3.12 .13 3.5 .13C3.95 .13 4.26-.15 4.95-1.23Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq15-page1'&gt;
&lt;use x='0' y='0' xlink:href='#eq15-g4-71'/&gt;
&lt;use x='6.49' y='0' xlink:href='#eq15-g1-185'/&gt;
&lt;use x='10.94' y='0' xlink:href='#eq15-g1-0'/&gt;
&lt;use x='18.55' y='0' xlink:href='#eq15-g8-49'/&gt;
&lt;use x='24.53' y='0' xlink:href='#eq15-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;, which we do not know (there are many ways to
choose these values, what is best depends on the application, we won&amp;rsquo;t go into that here).
In the IIR case, we need to initialize &lt;span class="math"&gt;&lt;svg style="width: 1.847em; height: 1.099em; vertical-align: -0.245em; " viewBox=".19 -8.54 18.47 10.99"&gt;
&lt;title&gt;
\(y(t)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq16-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='eq16-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;path id='eq16-g4-67' d='M3.53-5.1H2.57L2.91-6.32C2.92-6.35 2.92-6.37 2.92-6.38C2.92-6.47 2.88-6.5 2.82-6.5C2.75-6.5 2.72-6.49 2.64-6.4C1.51-4.92 .68-5.17 .68-4.79C.68-4.78 .68-4.75 .69-4.72H1.57L.71-1.44C.68-1.29 .44-.55 .44-.32C.44-.06 .69 .13 1.01 .13C1.56 .13 1.95-.2 2.7-1.31L2.55-1.39C1.97-.64 1.77-.45 1.58-.45C1.48-.45 1.41-.55 1.41-.69C1.41-.7 1.41-.71 1.42-.75L2.47-4.72H3.47L3.53-5.1Z'/&gt;
&lt;path id='eq16-g4-72' d='M5.07-4.6C5.07-4.95 4.78-5.25 4.42-5.25C4.14-5.25 3.95-5.07 3.95-4.81C3.95-4.62 4.05-4.5 4.29-4.35C4.51-4.22 4.6-4.11 4.6-3.94C4.6-3.47 4.17-2.55 3.14-.86L2.91-2.24C2.73-3.3 2.06-5.25 1.88-5.25H1.83L1.73-5.24L.56-5.04L.18-4.97V-4.76C.32-4.8 .42-4.81 .55-4.81C1.02-4.81 1.24-4.63 1.46-4.05C1.79-3.24 2.44-.57 2.44-.1C2.44 .04 2.39 .18 2.32 .32C2.23 .48 1.69 1.18 1.48 1.41C1.2 1.7 1.06 1.8 .91 1.8C.82 1.8 .75 1.76 .62 1.67C.44 1.52 .32 1.46 .18 1.46C-.08 1.46-.29 1.67-.29 1.93C-.29 2.24-.04 2.45 .32 2.45C1 2.45 2.29 1.08 3.51-.96C4.51-2.61 5.07-3.92 5.07-4.6Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq16-page1'&gt;
&lt;use x='.48' y='0' xlink:href='#eq16-g4-72'/&gt;
&lt;use x='6.84' y='0' xlink:href='#eq16-g1-185'/&gt;
&lt;use x='11.25' y='0' xlink:href='#eq16-g4-67'/&gt;
&lt;use x='15.27' y='0' xlink:href='#eq16-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; for the set of values of &lt;span class="math"&gt;&lt;svg style="width: 2.445em; height: 0.822em; vertical-align: -0.017em; " viewBox="-.05 -8.05 24.45 8.22"&gt;
&lt;title&gt;
\(t&amp;lt;0\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq17-g5-48' d='M5.67-3.93C5.67-6.37 4.59-8.05 3.03-8.05C2.37-8.05 1.87-7.85 1.43-7.43C.74-6.77 .29-5.4 .29-4C.29-2.7 .68-1.31 1.24-.64C1.68-.12 2.29 .17 2.98 .17C3.59 .17 4.1-.04 4.53-.45C5.22-1.11 5.67-2.49 5.67-3.93ZM4.53-3.91C4.53-1.42 4-.14 2.98-.14S1.43-1.42 1.43-3.89C1.43-6.42 1.97-7.74 2.99-7.74C3.99-7.74 4.53-6.4 4.53-3.91Z'/&gt;
&lt;path id='eq17-g1-67' d='M3.53-5.1H2.57L2.91-6.32C2.92-6.35 2.92-6.37 2.92-6.38C2.92-6.47 2.88-6.5 2.82-6.5C2.75-6.5 2.72-6.49 2.64-6.4C1.51-4.92 .68-5.17 .68-4.79C.68-4.78 .68-4.75 .69-4.72H1.57L.71-1.44C.68-1.29 .44-.55 .44-.32C.44-.06 .69 .13 1.01 .13C1.56 .13 1.95-.2 2.7-1.31L2.55-1.39C1.97-.64 1.77-.45 1.58-.45C1.48-.45 1.41-.55 1.41-.69C1.41-.7 1.41-.71 1.42-.75L2.47-4.72H3.47L3.53-5.1Z'/&gt;
&lt;path id='eq17-g1-159' d='M6.84-.07V-.74L1.88-3.06L6.84-5.38V-6.05L.74-3.18V-2.94L6.84-.07Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq17-page1'&gt;
&lt;use x='-.05' y='0' xlink:href='#eq17-g1-67'/&gt;
&lt;use x='7.54' y='0' xlink:href='#eq17-g1-159'/&gt;
&lt;use x='18.73' y='0' xlink:href='#eq17-g5-48'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; that we will need to read.&lt;/p&gt;
&lt;p&gt;But what matters is how we determine whether we can read a given value or not. A conditional expression is very
expensive in modern hardware. Code like&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;t&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="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;sum&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;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&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;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&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;or&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;sum&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;x&lt;/span&gt;&lt;span class="p"&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;t&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="o"&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="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;will significantly slow down execution. We want to avoid these tests for the bulk of the signal that is sufficiently
far from the boundary, where we know for sure we can read all required input values. There are two ways of
accomplishing this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Copy the input signal into a larger buffer (i.e. add padding), and fill in the samples outside the known signal
    in our desired way. We can now safely read &lt;code&gt;x[-1]&lt;/code&gt; for example. In the IIR case this translates to creating
    a larger output buffer to write in and read from.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Write three separate loops: the first one applies the filter for the first few output values where the boundary
    plays a role, a second loop processes the bulk of the signal and has no conditional statements, and a third loop
    produces the last few output values where the boundary again plays a role (in the skeleton code below, &lt;code&gt;N&lt;/code&gt; is the
    number of samples in &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt;, &lt;code&gt;K&lt;/code&gt; is the number of samples in &lt;code&gt;h&lt;/code&gt;, we assume &lt;code&gt;K&lt;/code&gt; is odd and the origin of
    the kernel is in the center):&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="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="mi"&gt;0&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;margin&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;K&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="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// floor division!&lt;/span&gt;
&lt;span class="k"&gt;for&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;i&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="n"&gt;margin&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="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="c1"&gt;// compute convolution applying boundary condition&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// on the left side of the input&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="p"&gt;(;&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;&amp;lt;&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="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;margin&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="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="c1"&gt;// compute convolution without any conditional statements&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="p"&gt;(;&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;&amp;lt;&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="w"&gt; &lt;/span&gt;&lt;span class="o"&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="c1"&gt;// compute convolution applying boundary condition&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// on the right side of the input&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;[I would typically not use &lt;code&gt;int&lt;/code&gt; for indexing or loops, but wanted to keep the code above simple.]&lt;/p&gt;
&lt;h3&gt;Fast Fourier Transform&lt;/h3&gt;
&lt;p&gt;A FIR filter can be implemented efficiently using the FFT only if the kernel is large. The FFT is relatively expensive,
and not worth using for smaller kernels.&lt;/p&gt;
&lt;p&gt;To use this method, pad the kernel &lt;code&gt;h&lt;/code&gt; with zeros to the same size as the input &lt;code&gt;x&lt;/code&gt;, apply the FFT to both, multiply
them (remember the output of the FFT is complex, we must properly multiply the complex numbers), then compute the
inverse transform of the result. The inverse transform should be real-valued, but the imaginary component will likely
have some very small values in it due to floating-point rounding errors. Simply ignore the imaginary component, do not
take the magnitude (absolute value) of the complex numbers.&lt;/p&gt;
&lt;p&gt;When using the FFT, remember that the FFT takes the first sample as the origin. For the signal it doesn&amp;rsquo;t matter where
the origin is, but for the kernel this is indeed very important. If we define the kernel with the origin in the middle,
a careless transformation of the kernel will result in the output signal being shifted.&lt;/p&gt;
&lt;p&gt;Assuming again a kernel &lt;code&gt;h&lt;/code&gt; with &lt;code&gt;K&lt;/code&gt; samples and the origin in the middle (at &lt;code&gt;K/2&lt;/code&gt;, using floor division),
we must pad the kernel as follows:&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;padded_h&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;cmalloc&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="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;padded_h&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// initialized to 0&lt;/span&gt;
&lt;span class="n"&gt;memcopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;padded_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;h&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;K&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;K&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="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;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;memcopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;padded_h&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;N&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;K&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="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;K&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The convolution is now applied with (pseudocode):&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;X&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;fft&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="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;H&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;fft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;padded_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;N&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Y&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;malloc&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="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Y&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="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="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="p"&gt;;&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;&amp;lt;&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="w"&gt; &lt;/span&gt;&lt;span class="o"&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="n"&gt;Y&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;X&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;H&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="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;y&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;ifft&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="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="c1"&gt;// if y is complex, keep the real part&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But note that the boundary condition imposed by the FFT is periodic: &lt;span class="math"&gt;&lt;svg style="width: 5.928em; height: 1.095em; vertical-align: -0.241em; " viewBox="-.32 -8.54 59.28 10.95"&gt;
&lt;title&gt;
\(x(N) = x(0)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq18-g11-48' d='M5.67-3.93C5.67-6.37 4.59-8.05 3.03-8.05C2.37-8.05 1.87-7.85 1.43-7.43C.74-6.77 .29-5.4 .29-4C.29-2.7 .68-1.31 1.24-.64C1.68-.12 2.29 .17 2.98 .17C3.59 .17 4.1-.04 4.53-.45C5.22-1.11 5.67-2.49 5.67-3.93ZM4.53-3.91C4.53-1.42 4-.14 2.98-.14S1.43-1.42 1.43-3.89C1.43-6.42 1.97-7.74 2.99-7.74C3.99-7.74 4.53-6.4 4.53-3.91Z'/&gt;
&lt;path id='eq18-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='eq18-g4-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-g4-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;path id='eq18-g7-35' d='M8.66-7.59V-7.78H6.29V-7.59C6.97-7.53 7.12-7.41 7.12-6.98C7.12-6.84 7.1-6.71 7.02-6.46L6.99-6.36L5.78-1.83L3.3-7.78H1.38V-7.59C1.94-7.54 2.19-7.38 2.41-6.92L.99-1.95C.54-.43 .44-.29-.24-.19V0H2.12V-.19C1.5-.24 1.29-.37 1.29-.7C1.29-.86 1.32-1.1 1.39-1.35L2.75-6.37L5.49 .18H5.71L7.42-5.81C7.86-7.35 7.91-7.42 8.66-7.59Z'/&gt;
&lt;path id='eq18-g7-71' d='M4.95-1.23L4.79-1.32C4.69-1.2 4.63-1.14 4.53-1C4.25-.64 4.12-.52 3.97-.52C3.8-.52 3.69-.68 3.61-1.01C3.59-1.12 3.57-1.18 3.56-1.2C3.28-2.32 3.13-2.97 3.13-3.14C3.66-4.06 4.09-4.59 4.3-4.59C4.37-4.59 4.48-4.55 4.59-4.49C4.73-4.41 4.81-4.38 4.92-4.38C5.16-4.38 5.32-4.56 5.32-4.81C5.32-5.07 5.12-5.25 4.84-5.25C4.31-5.25 3.87-4.82 3.04-3.55L2.91-4.2C2.74-5.01 2.61-5.25 2.29-5.25C2.01-5.25 1.63-5.16 .89-4.91L.76-4.86L.81-4.68L1.01-4.73C1.24-4.79 1.38-4.81 1.48-4.81C1.77-4.81 1.85-4.7 2.01-3.99L2.36-2.53L1.38-1.13C1.13-.77 .91-.56 .77-.56C.7-.56 .58-.6 .46-.67C.31-.75 .19-.79 .08-.79C-.15-.79-.32-.61-.32-.37C-.32-.06-.1 .13 .27 .13S.79 .02 1.38-.69L2.45-2.1L2.81-.67C2.97-.05 3.12 .13 3.5 .13C3.95 .13 4.26-.15 4.95-1.23Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq18-page1'&gt;
&lt;use x='0' y='0' xlink:href='#eq18-g7-71'/&gt;
&lt;use x='6.49' y='0' xlink:href='#eq18-g4-185'/&gt;
&lt;use x='11.4' y='0' xlink:href='#eq18-g7-35'/&gt;
&lt;use x='20.34' y='0' xlink:href='#eq18-g4-186'/&gt;
&lt;use x='28.24' y='0' xlink:href='#eq18-g1-61'/&gt;
&lt;use x='38.64' y='0' xlink:href='#eq18-g7-71'/&gt;
&lt;use x='45.13' y='0' xlink:href='#eq18-g4-185'/&gt;
&lt;use x='49.58' y='0' xlink:href='#eq18-g11-48'/&gt;
&lt;use x='55.56' y='0' xlink:href='#eq18-g4-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;. This is usually not a desireable
boundary condition. So we must pad the input before applying the FFT. We can pad &lt;code&gt;x&lt;/code&gt; on both sides by &lt;code&gt;K/2&lt;/code&gt;, using
our desired method (&lt;code&gt;h&lt;/code&gt; must be padded to this new length!), and crop the final result back to the original
size. Since we&amp;rsquo;re padding anyway, we can choose a slightly larger size that will be cheaper to compute the FFT for.
The FFT is most efficient when the length &lt;code&gt;N&lt;/code&gt; can be decomposed into a product of small integers
(typically 2, 3 and 5, maybe also 7 depending on the implementation we&amp;rsquo;re using).&lt;/p&gt;
&lt;h2&gt;nD convolution&lt;/h2&gt;
&lt;p&gt;The nD case is more or less the same as the 1D case. I don&amp;rsquo;t know of any nD IIR filters, so I&amp;rsquo;ll discuss FIR filters
only. Applying the convolution requires looping over multiple dimensions, and we need to take care of boundary conditions
in all dimensions. Again, the simple way is to copy the image into a larger buffer, adding pixels outside the original
boundary. The more complex way would require 3&lt;sup&gt;n&lt;/sup&gt; loops (for n dimensions): each of the three loops discussed in the
1D case would have three loops inside it for the second dimension, etc. Each inner loop handles a corner, and edge
or the central part of the image. For the 2D case this might still be doable, but it becomes increasingly difficult
for higher dimensions. The copy required to pad the image doesn&amp;rsquo;t sound so bad after all&amp;hellip;&lt;/p&gt;
&lt;p&gt;But there is an additional trick that comes into play in the nD case: some kernels can be decomposed into a set of
1D kernels.&lt;/p&gt;
&lt;h3&gt;Kernel decomposition&lt;/h3&gt;
&lt;p&gt;We know that the convolution is associative. That is, &lt;code&gt;(x * h_1) * h_2) = x * (h_1 * h_2)&lt;/code&gt;. This can be used in two
ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;When applying multiple convolutions in sequence, we can combine the kernels together, and apply them all as a
single convolution. This saves in memory accesses, since we&amp;rsquo;re looping over the image only once. In 1D, if one kernel
has size &lt;code&gt;K&lt;/code&gt; and the other has size &lt;code&gt;M&lt;/code&gt;, then the combined kernel has size &lt;code&gt;K + M - 1&lt;/code&gt;, so there is not really
a computational advantage other than the more efficient memory access. When using the FFT, the core advantage is
the fewer FFTs one needs to compute.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If we can decompose a kernel into a set of 1D kernels, we can significantly reduce the computational cost. For
example, a kernel of &lt;span class="math"&gt;&lt;svg style="width: 3.058em; height: 0.784em; vertical-align: -0.006em; " viewBox=".18 -7.78 30.58 7.84"&gt;
&lt;title&gt;
\(K \times K\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq19-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='eq19-g4-32' d='M7.36 0V-.19C6.61-.26 6.54-.3 6.21-.86L4.13-4.47L7.71-7.16C8.05-7.41 8.31-7.54 8.6-7.59V-7.78H5.96V-7.59L6.28-7.55C6.56-7.53 6.68-7.46 6.68-7.32C6.68-7.04 6.03-6.41 4.97-5.66L3.16-4.39L3.79-6.71C3.97-7.3 4.29-7.54 4.99-7.59V-7.78H1.74V-7.59C2.49-7.52 2.66-7.42 2.66-7.07C2.66-6.9 2.62-6.66 2.53-6.34L1.06-1.07C.85-.36 .79-.3 .08-.19V0H3.04V-.19C2.29-.27 2.18-.35 2.18-.71C2.18-.85 2.19-.93 2.27-1.18L2.35-1.43L3.08-4.14L4.53-1.67C4.81-1.18 4.98-.76 4.98-.54C4.98-.36 4.81-.26 4.43-.23L4.09-.19V0H7.36Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq19-page1'&gt;
&lt;use x='.18' y='0' xlink:href='#eq19-g4-32'/&gt;
&lt;use x='11.72' y='0' xlink:href='#eq19-g1-2'/&gt;
&lt;use x='22.16' y='0' xlink:href='#eq19-g4-32'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; pixels takes &lt;span class="math"&gt;&lt;svg style="width: 1.303em; height: 1.024em; vertical-align: -0.000em; " viewBox=".18 -10.24 13.03 10.24"&gt;
&lt;title&gt;
\(K^2\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq20-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='eq20-g1-32' d='M7.36 0V-.19C6.61-.26 6.54-.3 6.21-.86L4.13-4.47L7.71-7.16C8.05-7.41 8.31-7.54 8.6-7.59V-7.78H5.96V-7.59L6.28-7.55C6.56-7.53 6.68-7.46 6.68-7.32C6.68-7.04 6.03-6.41 4.97-5.66L3.16-4.39L3.79-6.71C3.97-7.3 4.29-7.54 4.99-7.59V-7.78H1.74V-7.59C2.49-7.52 2.66-7.42 2.66-7.07C2.66-6.9 2.62-6.66 2.53-6.34L1.06-1.07C.85-.36 .79-.3 .08-.19V0H3.04V-.19C2.29-.27 2.18-.35 2.18-.71C2.18-.85 2.19-.93 2.27-1.18L2.35-1.43L3.08-4.14L4.53-1.67C4.81-1.18 4.98-.76 4.98-.54C4.98-.36 4.81-.26 4.43-.23L4.09-.19V0H7.36Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq20-page1'&gt;
&lt;use x='.18' y='0' xlink:href='#eq20-g1-32'/&gt;
&lt;use x='9.06' y='-4.34' xlink:href='#eq20-g5-50'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; multiplications and additions to compute, but if we can decompose
it into two 1D kernels of length &lt;span class="math"&gt;&lt;svg style="width: 0.860em; height: 0.778em; vertical-align: -0.000em; " viewBox=".18 -7.78 8.6 7.78"&gt;
&lt;title&gt;
\(K\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq21-g1-32' d='M7.36 0V-.19C6.61-.26 6.54-.3 6.21-.86L4.13-4.47L7.71-7.16C8.05-7.41 8.31-7.54 8.6-7.59V-7.78H5.96V-7.59L6.28-7.55C6.56-7.53 6.68-7.46 6.68-7.32C6.68-7.04 6.03-6.41 4.97-5.66L3.16-4.39L3.79-6.71C3.97-7.3 4.29-7.54 4.99-7.59V-7.78H1.74V-7.59C2.49-7.52 2.66-7.42 2.66-7.07C2.66-6.9 2.62-6.66 2.53-6.34L1.06-1.07C.85-.36 .79-.3 .08-.19V0H3.04V-.19C2.29-.27 2.18-.35 2.18-.71C2.18-.85 2.19-.93 2.27-1.18L2.35-1.43L3.08-4.14L4.53-1.67C4.81-1.18 4.98-.76 4.98-.54C4.98-.36 4.81-.26 4.43-.23L4.09-.19V0H7.36Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq21-page1'&gt;
&lt;use x='.18' y='0' xlink:href='#eq21-g1-32'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;, then the cost is reduced  to &lt;span class="math"&gt;&lt;svg style="width: 1.476em; height: 0.805em; vertical-align: -0.000em; " viewBox="0 -8.05 14.76 8.05"&gt;
&lt;title&gt;
\(2K\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq22-g1-32' d='M7.36 0V-.19C6.61-.26 6.54-.3 6.21-.86L4.13-4.47L7.71-7.16C8.05-7.41 8.31-7.54 8.6-7.59V-7.78H5.96V-7.59L6.28-7.55C6.56-7.53 6.68-7.46 6.68-7.32C6.68-7.04 6.03-6.41 4.97-5.66L3.16-4.39L3.79-6.71C3.97-7.3 4.29-7.54 4.99-7.59V-7.78H1.74V-7.59C2.49-7.52 2.66-7.42 2.66-7.07C2.66-6.9 2.62-6.66 2.53-6.34L1.06-1.07C.85-.36 .79-.3 .08-.19V0H3.04V-.19C2.29-.27 2.18-.35 2.18-.71C2.18-.85 2.19-.93 2.27-1.18L2.35-1.43L3.08-4.14L4.53-1.67C4.81-1.18 4.98-.76 4.98-.54C4.98-.36 4.81-.26 4.43-.23L4.09-.19V0H7.36Z'/&gt;
&lt;path id='eq22-g5-50' d='M5.66-1.63L5.5-1.69C5.06-1.01 4.91-.91 4.37-.91H1.52L3.53-3C4.59-4.11 5.05-5.01 5.05-5.94C5.05-7.13 4.09-8.05 2.85-8.05C2.19-8.05 1.57-7.79 1.13-7.31C.75-6.91 .57-6.53 .37-5.68L.62-5.62C1.1-6.79 1.52-7.17 2.35-7.17C3.35-7.17 4.03-6.49 4.03-5.49C4.03-4.56 3.48-3.45 2.48-2.39L.36-.14V0H5L5.66-1.63Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq22-page1'&gt;
&lt;use x='0' y='0' xlink:href='#eq22-g5-50'/&gt;
&lt;use x='6.16' y='0' xlink:href='#eq22-g1-32'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; multiplications and additions. The larger &lt;span class="math"&gt;&lt;svg style="width: 0.860em; height: 0.778em; vertical-align: -0.000em; " viewBox=".18 -7.78 8.6 7.78"&gt;
&lt;title&gt;
\(K\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq23-g1-32' d='M7.36 0V-.19C6.61-.26 6.54-.3 6.21-.86L4.13-4.47L7.71-7.16C8.05-7.41 8.31-7.54 8.6-7.59V-7.78H5.96V-7.59L6.28-7.55C6.56-7.53 6.68-7.46 6.68-7.32C6.68-7.04 6.03-6.41 4.97-5.66L3.16-4.39L3.79-6.71C3.97-7.3 4.29-7.54 4.99-7.59V-7.78H1.74V-7.59C2.49-7.52 2.66-7.42 2.66-7.07C2.66-6.9 2.62-6.66 2.53-6.34L1.06-1.07C.85-.36 .79-.3 .08-.19V0H3.04V-.19C2.29-.27 2.18-.35 2.18-.71C2.18-.85 2.19-.93 2.27-1.18L2.35-1.43L3.08-4.14L4.53-1.67C4.81-1.18 4.98-.76 4.98-.54C4.98-.36 4.81-.26 4.43-.23L4.09-.19V0H7.36Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq23-page1'&gt;
&lt;use x='.18' y='0' xlink:href='#eq23-g1-32'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;,
the larger the savings. Kernels that can be decomposed like this are the square kernel with uniform weights,
the Gaussian kernel and all its derivaties, the Sobel kernels, etc. These kernels are called &lt;em&gt;separable&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;However, applying two 1D kernels in succession does require iterating over the image twice, which adds to the cost.
So very small kernels like Sobel are not worth decomposing.&lt;/p&gt;
&lt;h3&gt;The Gaussian filter&lt;/h3&gt;
&lt;p&gt;But the Gaussian kernel certainly is worth decomposing. Decomposing the Gaussian kernel makes it trivial to implement
a Gaussian filter for an image with an arbitrary number of dimensions. We only need to implement the 1D Gaussian,
then just loop over the lines along each of the image dimensions. The 1D Gaussian can be implemented as a FIR filter,
as an IIR filter, and through the FFT. Which one to pick depends on the sigma: The FIR filter increases in cost
with the sigma. The IIR filter is quite expensive, but independent of sigma. And so for sigma above some value, the
IIR filter is the most efficient method. The FFT method is always the most expensive, but is useful when the sigma
is so small that it cannot be implemented correctly using a FIR filter (the lower limit for sampling a Gaussian
kernel is a sigma of about 0.8 pixels).&lt;/p&gt;
&lt;h2&gt;In closing&lt;/h2&gt;
&lt;p&gt;To know which implementation is faster, which tricks to apply, you must always measure. There are rules of thumb you
can use, but each machine is different, and each situation is unique. Try different things and time them all!&lt;/p&gt;
&lt;p&gt;This is a graph comparing the speed of the three methods to compute the Gaussian filter, as implemented in DIPlib,
on my machine. We apply the filter to image of 2000x2000 pixels, for different sigma:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="Graph comparing speed of different implementations of the Gaussian filter" src="/images/gaussian_filtering_speed.png"&gt;&lt;/p&gt;
&lt;p&gt;On my machine, for a sigma of 10 pixels, the FIR and IIR filters are equally fast. It is never faster to use the
FFT to compute a Gaussian filter. Would these conclusions be different on your machine?&lt;/p&gt;</content><category term="tutorials"></category><category term="convolution"></category></entry><entry><title>Color maps for image display</title><link href="https://www.crisluengo.net/archives/1141" rel="alternate"></link><published>2023-10-22T00:00:00-06:00</published><updated>2023-10-22T00:00:00-06:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2023-10-22:/archives/1141</id><summary type="html">&lt;p&gt;Today&amp;rsquo;s post discusses different possible color maps you can use to display a scalar image
(i.e. an image with a single channel, often referred to as a grayscale image).
A color map is necessary to translate the pixel values of the image into colors to show on the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Today&amp;rsquo;s post discusses different possible color maps you can use to display a scalar image
(i.e. an image with a single channel, often referred to as a grayscale image).
A color map is necessary to translate the pixel values of the image into colors to show on the screen.
When using a grayscale color map, each pixel is represented by its own gray value.
More colorful color maps are often employed to either make the display more attractive, to enhance our
ability to distinguish specific values, or to highlight specific transitions.
Unfortunately, many color maps in frequent use also distort the image we perceive.&lt;/p&gt;
&lt;p&gt;There are four general categories that I&amp;rsquo;ll cover in turn: the grayscale color map, rainbow color maps,
linear (or sequential) color maps, and special-purpose color maps.&lt;/p&gt;
&lt;h2&gt;The grayscale color map&lt;/h2&gt;
&lt;p&gt;This is an obvious choice, considering we&amp;rsquo;re displaying a grayscale image. And it usually is the choice I make
when working with arbitrary images. It gives me the best ability to judge the shape encoded by the image data.
For example, the first image here is a Gaussian blob; I can recognize it as such from the image. Next is a Gaussian
with a clipped peak; again I can tell the peak is flat. Finally, a blob with a different shape; it is immediately
apparent to me that this is not a Gaussian, that its tail is heavier than that of a Gaussian and its peak is sharper.&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="Gaussian with grayscale color map" src="/images/cm_gray.png"&gt;
&lt;img alt="Clipped Gaussian with grayscale color map" src="/images/cm_gray_clip.png"&gt;
&lt;img alt="Heavy-tailed blog with grayscale color map" src="/images/cm_gray_heavy.png"&gt;&lt;/p&gt;
&lt;p&gt;Some image display tools will use the grayscale color map by default for grayscale images.
But some prefer a more colorful color map as the default.
For example Matplotlib uses &amp;ldquo;viridis&amp;rdquo; by default. This is a perceptually uniform linear color map
(&lt;a href="#linear-color-maps"&gt;more on that below&lt;/a&gt;),
so not a terrible choice, but I think it causes more confusion among novices than the grayscale color map.&lt;/p&gt;
&lt;p&gt;Note that a properly configured display is required for the grayscale color map (or any other color map) to produce
a useful image on the screen.
Modern displays, straight from the factory, are much more likely to show the expected intensities than the old
cathode ray tubes. We don&amp;rsquo;t need to calibrate display gamma anymore.
But for proper color perception one still needs to use the display in a properly illuminated environment,
and calibrate the display&amp;rsquo;s colors to that environmental illumination.
This means that when we limit ourselves to grayscale, the image will likely look the same on any modern display,
but when using color, different uncalibrated displays might show different things.&lt;/p&gt;
&lt;h2&gt;Rainbow color maps&lt;/h2&gt;
&lt;p&gt;Much has been written about the problems with the rainbow color maps.
See for example Borland and Taylor&amp;nbsp;&lt;a href="#ref-borland"&gt;[1]&lt;/a&gt;, and the references therein. The main arguments are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No natural ordering to the colors: does red represent a larger value than green or a smaller one?&lt;/li&gt;
&lt;li&gt;Not perceptually uniform: at some points, the color map has too much contrast, at others it doesn&amp;rsquo;t have much at all.
  This means that some image features can be hidden, and some artificial features can appear.&lt;/li&gt;
&lt;li&gt;Some colors, such as yellow or cyan, appear brighter and therefore draw more attention than other colors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is the Gaussian from before with MATLAB&amp;rsquo;s &amp;ldquo;jet&amp;rdquo; color map applied:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="Gaussian with jet color map" src="/images/cm_jet.png"&gt;&lt;/p&gt;
&lt;p&gt;Besides the main points argued above, the peak of the Gaussian is a lot darker than other parts, to me the shape
for this blob is a donut, not a simple peak.&lt;/p&gt;
&lt;p&gt;Still, this has not stopped people from using them. Rainbow color maps are still used frequently (though luckily
much less frequently than in the past). And because of that, some people have attempted to improve on them
to reduce some of their worst properties. In particular, I&amp;rsquo;m aware of these modern rainbow color maps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://colorcet.com/gallery.html#rainbow"&gt;ColorCET rainbow color maps&lt;/a&gt; by Peter Kovesi (2015),&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fabiocrameri.ch/batlow/"&gt;batlow color map&lt;/a&gt; by Fabio Crameri (2018), and&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.research.google/2019/08/turbo-improved-rainbow-colormap-for.html"&gt;turbo color map&lt;/a&gt; by Anton Mikhailov (2019).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ColorCET is a large collection of color maps, produced with methods described in a paper&amp;nbsp;&lt;a href="#ref-kovesi"&gt;[2]&lt;/a&gt;.
There is a lot we know about perception of color, which can be translated into perceptually-uniform color maps.
Now, a rainbow color map can never be perceptually uniform, but the ones in ColorCET are certainly much better
approximations.&lt;/p&gt;
&lt;p&gt;Likewise, &amp;ldquo;batlow&amp;rdquo; comes from a larger collection of color maps also produced according to scientific
principles&amp;nbsp;&lt;a href="#ref-crameri"&gt;[3]&lt;/a&gt;. In contrast, &amp;ldquo;turbo&amp;rdquo; was created by eye-balling something that looked
good to its author. Here is the Gaussian again, with the three color maps applied:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="Gaussian with CET rainbow color map" src="/images/cm_CET_rainbow.png"&gt;
&lt;img alt="Gaussian with batlow color map" src="/images/cm_batlow.png"&gt;
&lt;img alt="Gaussian with turbo color map" src="/images/cm_turbo.png"&gt;&lt;/p&gt;
&lt;p&gt;None of these is an ideal representation of the data. &amp;ldquo;Batlow&amp;rdquo; comes closest, though it also is the least like
a rainbow color map. In fact, I think it belongs more to the next section, since its intensity increases from
dark to light. But it is sold as a rainbow color map by its author.&lt;/p&gt;
&lt;p&gt;We see that the ColorCET rainbow color map solves some of the problems of older rainbow color maps, though it
still produces rings where none exist in the data. It&amp;rsquo;s author refers to it as &amp;ldquo;the least worst rainbow colour
map I can devise.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Turbo&amp;rdquo; is more uniform than &amp;ldquo;jet&amp;rdquo;, but it still keeps the problem of high values having dark colors. It is the
only one of the three that makes the peak look like a donut. To me, this is by far the worst of the batch.
Guess which one of these color maps is built into MATLAB and Matplotlib and a whole lot of other tools?
That&amp;rsquo;s right, &amp;ldquo;turbo&amp;rdquo; is! Its author works at Google, and published it on the Google Research blog,
and that is of course much more important than understanding the science, or the color map actually being any good.&lt;/p&gt;
&lt;p&gt;The advantage of rainbow color maps over grayscale or linear color maps is that one can name the colors, which
makes it easier to look them up in the reference or compare them across images. On the downside, these clearly
identifiable colors introduce cognitive plateaus (even if perceptually they are not).&lt;/p&gt;
&lt;h2 class="link_target" id="linear-color-maps"&gt;Linear (or sequential) color maps&lt;/h2&gt;
&lt;p&gt;To add color to a grayscale image in a proper way, one needs to devise a color map that doesn&amp;rsquo;t fall in any of
the pitfalls of the rainbow color map. Having the lightness (intensity) of the colors increase linearly from
dark to bright does several things. First of all, it introduces a unique, intuitive ordering to the colors.
Additionally, it allows the figure to be printed in black-and-white, and still maintain its properties. Next,
we want to change the hue from one color to another, sometimes through a third color. Doing so in a perceptually
uniform manner is not trivial, but we understand enough about color perception that such a transition can
be computed.&lt;/p&gt;
&lt;p&gt;The main reason to add the hue in this case is to improve the intensity resolution; color improves our ability
to see small differences in intensity without reducing the range of intensities displayed.&lt;/p&gt;
&lt;p&gt;Many authors have proposed manually-generated color maps approximating perceptual linearity.
But, as far as I know, the first such color maps generated algorithmically using color perception theory
were Peter Kovesi&amp;rsquo;s &lt;a href="https://colorcet.com"&gt;ColorCET&lt;/a&gt;&amp;nbsp;&lt;a href="#ref-kovesi"&gt;[2]&lt;/a&gt;. Matplotlib also introduced
several such color maps, its default is &amp;ldquo;viridis&amp;rdquo;. Some years later, MATLAB introduced the &amp;ldquo;parula&amp;rdquo;
color map as its new default.&lt;/p&gt;
&lt;p&gt;Here is the Gaussian with these three color maps (I picked my favorite from the ColorCET collection):&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="Gaussian with CET linear color map" src="/images/cm_CET_linear.png"&gt;
&lt;img alt="Gaussian with viridis color map" src="/images/cm_viridis.png"&gt;
&lt;img alt="Gaussian with parula color map" src="/images/cm_parula.png"&gt;&lt;/p&gt;
&lt;p&gt;It is clear that &amp;ldquo;parula&amp;rdquo; is the inferior of the three here, with quite a strong orange band.
But none of them allow me to recognize the shape as well as the grayscale color map. Granted, I have a lot
of experience looking at images (and in particular Gaussian blobs) displayed with a grayscale color map.
Would I be able to see the Gaussian shape equally clearly with one of these color maps if I had the
same level of experience looking at images displayed with that color map? Maybe!&lt;/p&gt;
&lt;h2&gt;Special-purpose color maps&lt;/h2&gt;
&lt;p&gt;Color can do a lot more than improving out ability to discern small differences in intensities.
We can also use it to label distinct intensity ranges. Below are three examples.&lt;/p&gt;
&lt;h3&gt;Cyclic color maps&lt;/h3&gt;
&lt;p&gt;If the image&amp;rsquo;s intensities represent angles, we want the color for 360° and 0° to be the same.
This requires a cyclic color map (i.e. it must be periodic).&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take as an example the angle of the gradient of the Gaussian image used above.
When using a grayscale color map, it has a sharp transition at 180°
(the angle computed is in the range of -180° to 180°). Using one of the cyclic color maps from ColorCET,
the sharp transition is no longer there.&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="Angles with grayscale color map" src="/images/cm_gray_angles.png"&gt;
&lt;img alt="Angles with CET cyclic color map" src="/images/cm_cyclic.png"&gt;&lt;/p&gt;
&lt;p&gt;We can now identify green as 0°, yellow as 90°, magenta as 180°, and blue as -90°.&lt;/p&gt;
&lt;h3&gt;Divergent color maps&lt;/h3&gt;
&lt;p&gt;Similarly, when the sign of the image intensities is relevant, we can represent negative values with a different
color from positive values. In this case, our color map is two linear color maps, both start at the same color
(usually gray, black or white) for 0, and have increasingly bright and/or saturated colors for larger magnitudes.&lt;/p&gt;
&lt;p&gt;&lt;a href="/archives/56"&gt;I experimented with these divergent color maps in a post many years ago&lt;/a&gt;. This was before
the ColorCET color maps were published, and before I learned the little bit I know now about color perception.&lt;/p&gt;
&lt;p&gt;For example, this is the difference between the Gaussian blob and the heavy-tailed blob we displayed earlier:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="Differences with grayscale color map" src="/images/cm_gray_diverging.png"&gt;
&lt;img alt="Differences with CET diverging color map" src="/images/cm_CET_diverging.png"&gt;&lt;/p&gt;
&lt;p&gt;The neutral gray when using the diverging color map indicates where zero is.&lt;/p&gt;
&lt;h3&gt;Sharp transitions&lt;/h3&gt;
&lt;p&gt;Sometimes one specific gray value is important to highlight. For example, this satellite image represents surface
height. It is really hard to see what part of the world is represented, unless we know what we&amp;rsquo;re looking for:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="Satellite data with diverging color map" src="/images/cm_map_diverging.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Using a color map with a jump at height of 0, coloring negative values with different colors from positive
values, the outline of land mass becomes immediately obvious, and anyone will be able to recognize, without
difficulty, what part of the world was imaged:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="Satellite data with color map with a jump" src="/images/cm_map_jump.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Another example is the color map often used on microscopes (especially fluorescent microscopes) where pixels
with a value of 0 are shown in blue, and pixels with a value of 255 are shown in red. The blue pixels thus
indicate underexposed pixels, and red ones overexposed pixels. The operator can then adjust gain and offset
to reduce these blue and red pixels as much as possible. Here&amp;rsquo;s an example of an image with this color
map applied:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="The &amp;quot;cermet&amp;quot; image with the saturation color map" src="/images/cm_saturation.png"&gt;&lt;/p&gt;
&lt;p&gt;Finally, this is a nice example from &lt;a href="https://matplotlib.org/cmocean/"&gt;this page&lt;/a&gt;, where a grayscale color map
is enhanced to highlight regions where oxygen saturation is too high or too low. The color map has yellow and
red colors for these regions:&lt;/p&gt;
&lt;p class="centering"&gt;&lt;img alt="Example map with oxygen concentration mapped" src="/images/cm_cmocean.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So, for me, the go-to color map will always be the grayscale color map. It is what best allows me to recognize
the shape formed by the image intensities. To see smaller intensity differences, the first thing I do is change
the intensity range that is mapped to the black-white range on the screen. Of course this cannot be done in
a publication, where interaction with the display is typically not possible.&lt;/p&gt;
&lt;p&gt;Adding color can improve the resolution of the color map, and it can also highlight specific values. For signed
difference images I typically use a diverging color map, where 0 is recognizable and the hue encodes the sign.
And for angles I typically use a 4-color cyclic color map, where each cardinal direction has a specific color
we can name. But it is important, in all these cases, that the color map be perceptually uniform, otherwise we
will distort our perception of the data (of course sometimes we might want to lie about our data, which is possible
to do with non-uniform color maps).&lt;/p&gt;
&lt;p&gt;Finally, in some very specific cases, one might want to build a custom color map specifically designed to highlight
some chosen intensities. But this is something one would do for publication, not for data exploration.&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-borland"&gt;David Borland and Russel M. Taylor II, &amp;ldquo;Rainbow Color Map (Still) Considered Harmful&amp;rdquo;,
    IEEE Computer Graphics and Applications 27(2), 2007 [&lt;a href="https://doi.org/10.1109/MCG.2007.323435"&gt;DOI&lt;/a&gt;]
    [&lt;a href="https://github.com/djoshea/matlab-utils/blob/master/libs/perceptuallyImprovedColormaps/Rainbow%20Color%20Map%20-Still-%20Considered%20Harmful.pdf"&gt;PDF&lt;/a&gt;]&lt;/li&gt;
&lt;li class="link_target" id="ref-kovesi"&gt;Peter Kovesi, &amp;ldquo;Good Colour Maps: How to Design Them&amp;rdquo;,
    &lt;a href="https://arxiv.org/abs/1509.03700"&gt;arXiv:1509.03700 [cs.GR]&lt;/a&gt;, 2015&lt;/li&gt;
&lt;li class="link_target" id="ref-crameri"&gt;Fabio Crameri, &amp;ldquo;Scientific colour maps&amp;rdquo;, Zenodo, 2018 [&lt;a href="http://doi.org/10.5281/zenodo.1243862"&gt;DOI&lt;/a&gt;]&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="tutorials"></category><category term="color map"></category><category term="color"></category></entry><entry><title>OpenCV is not designed for quantification</title><link href="https://www.crisluengo.net/archives/1140" rel="alternate"></link><published>2023-01-02T00:00:00-07:00</published><updated>2023-01-02T00:00:00-07:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2023-01-02:/archives/1140</id><summary type="html">&lt;p&gt;&lt;a href="https://opencv.org"&gt;OpenCV&lt;/a&gt; is undoubtedly the most popular library for image processing and computer vision.
According to its website,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;OpenCV is a highly optimized library with focus on real-time applications.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is, it is built to do real-time computer vision, not precise measurement. This means that OpenCV prioritizes
speed over precision. There …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://opencv.org"&gt;OpenCV&lt;/a&gt; is undoubtedly the most popular library for image processing and computer vision.
According to its website,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;OpenCV is a highly optimized library with focus on real-time applications.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is, it is built to do real-time computer vision, not precise measurement. This means that OpenCV prioritizes
speed over precision. There are many applications where this is the right choice. But there are applications where
it isn&amp;rsquo;t: in my line of work, precision is important.&lt;/p&gt;
&lt;p&gt;Over the years I have collected links to questions on &lt;a href="https://stackoverflow.com"&gt;Stack Overflow&lt;/a&gt; that were prompted
by OpenCV&amp;rsquo;s lack of precision. In today&amp;rsquo;s blog post I wanted to revisit some of these unexpected results and their cause.
This is not meant as an OpenCV-bashing post (though I do enjoy a good bashing now and again), but rather an exploration
of the limitations of OpenCV, and maybe a demonstration of why a library like &lt;a href="https://diplib.org"&gt;DIPlib&lt;/a&gt; has
a place in a world dominated by OpenCV.&lt;/p&gt;
&lt;p&gt;What I &lt;em&gt;am&lt;/em&gt; criticizing here is people trying to use OpenCV for applications where a library such as DIPlib would be
a more appropriate choice. If you need precise results or accurate measurements use DIPlib, not OpenCV.&lt;/p&gt;
&lt;h2&gt;Rounding in interpolation&lt;/h2&gt;
&lt;p&gt;In &lt;a href="https://stackoverflow.com/a/42881329/7328782"&gt;this answer&lt;/a&gt;, Andras Deak shows how OpenCV&amp;rsquo;s interpolation
output is quantized. It looks like they use fixed-point arithmetic, with a precision of 1/32. I don&amp;rsquo;t doubt
this speeds up computations, but it did produce unexpected results at least once.&lt;/p&gt;
&lt;p&gt;This reproduces the findings. It applies linear interpolation to an image with some pixels set to 0 and some pixels set to 1.
It generates 100 new samples in between the last 0 pixel and its neighboring 1 pixel. One would expect the result to be a ramp,
a straight line going from 0 to 1. Instead, we see a staircases with 33 steps. Each step is exactly 1/32 high.&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;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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cv2&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;plt&lt;/span&gt;

&lt;span class="nb"&gt;input&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;zeros&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;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;dtype&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;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="mi"&gt;50&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="n"&gt;grid_x&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;linspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;49&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&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;float32&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:]&lt;/span&gt;
&lt;span class="n"&gt;grid_y&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;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid_x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&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;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;grid_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;grid_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interpolation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INTER_LINEAR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&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="p"&gt;:],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&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="Stair-case effect resulting from linear interpolation" src="/images/opencv_interpolation.png"&gt;&lt;/p&gt;
&lt;h2&gt;Color space conversion is lossy&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://stackoverflow.com/q/62494652/7328782"&gt;This question&lt;/a&gt; is from someone surprised at the round trip RGB
(or rather BGR, why reverse the order?) to HSV and then back to RGB causes visible color banding.&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;cv2&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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;plt&lt;/span&gt;

&lt;span class="n"&gt;x&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;arange&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="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="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;meshgrid&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;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;z&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;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;img&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;stack&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;165&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3&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="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;axis&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;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;astype&lt;/span&gt;&lt;span class="p"&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;uint8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;imghsv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cvtColor&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;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COLOR_BGR2HSV&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;imgback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cvtColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imghsv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COLOR_HSV2BGR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subplots&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="n"&gt;ax&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;imshow&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;ax&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;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&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="Color banding after the round trip to HSV" src="/images/opencv_color_banding.png"&gt;&lt;/p&gt;
&lt;p&gt;The problem is caused by rounding, which apparently happens mostly in the hue channel. OpenCV
rounds the 360 degrees of hue to 180 integer values, meaning that 30% of the dynamic range on the hue
channel is not used. That said, I&amp;rsquo;m not sure if mapping the 360 degrees to all 256 values of the 8-bit
integer would be sufficient to make the round trip not introduce visible artifacts. DIPlib outputs
floating-point images after color conversion by default, as does scikit-image. Using floating-point images
is the only way to guarantee a lossless roundtrip.&lt;/p&gt;
&lt;h2&gt;Perimeter measurement is biased and imprecise&lt;/h2&gt;
&lt;p&gt;The more problematic precision issues are the ones where it is not done on purpose to save time. For example,
OpenCV&amp;rsquo;s object perimeter measurement is imprecise and biased, even though computing it properly is not a significantly
more expensive. &lt;a href="/archives/310"&gt;I wrote about computing the perimeter many years ago&lt;/a&gt;. OpenCV measures the length
of the polygon that goes through each boundary pixel. This measurement is correct only for axis-aligned rectangles
and rectangles rotated by 45 degrees. It overestimates the length of any other perimeter.&lt;/p&gt;
&lt;p&gt;This experiment computes the perimeter of a square at many orientations, averaging over different sub-pixel shifts
to get a better estimate of the average measurement error:&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;cv2&lt;/span&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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;plt&lt;/span&gt;

&lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
&lt;span class="n"&gt;angles&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;linspace&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;np&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;perimeter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;xx&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;CreateXCoordinate&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;256&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;yy&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;CreateYCoordinate&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;256&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;rng&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;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_rng&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;phi&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;angles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cv_perimeter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;dip_perimeter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;jj&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&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;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&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;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;offset&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;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;phi&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;yy&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;offset&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;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;phi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;offset&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;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;phi&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;yy&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;offset&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;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;phi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&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;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&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;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;arr&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;asarray&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;dtype&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;uint8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;cnt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findContours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RETR_EXTERNAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CHAIN_APPROX_NONE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;cv_perimeter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arcLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnt&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="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;msr&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;MeasurementTool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&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;features&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Perimeter&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;dip_perimeter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;msr&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="s1"&gt;&amp;#39;Perimeter&amp;#39;&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;perimeter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;cv_perimeter&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;span class="n"&gt;dip_perimeter&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;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;perimeter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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="n"&gt;np&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&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="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&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="s1"&gt;&amp;#39;k:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xlim&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;np&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xticks&lt;/span&gt;&lt;span class="p"&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;linspace&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;np&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="mi"&gt;2&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="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;π/8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;π/4&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;3π/4&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;π/2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;legend&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;OpenCV&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;DIPlib&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;angle&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;estimated perimeter&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Perimeter of rotated square&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&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 perimeter of rotated squares, as measured by OpenCV and DIPlib" src="/images/opencv_perimeter_square.png"&gt;&lt;/p&gt;
&lt;p&gt;As I mentioned earlier, OpenCV gets it exactly right for 0 and 45 degree angles, and overestimates the perimeter
for all other angles. In contrast, the corner counting method used by DIPlib is unbiased (averaged over all angles,
it gets the right value), and the maximum error is much smaller than for the naive method.&lt;/p&gt;
&lt;p&gt;The poor perimeter estimate recently stumped two people, posting questions on Stack Overflow about it less than
a week apart (&lt;a href="https://stackoverflow.com/q/74523496/7328782"&gt;#1&lt;/a&gt;, &lt;a href="https://stackoverflow.com/q/74580811/7328782"&gt;#2&lt;/a&gt;).
Both were trying to compute the circularity measure, &lt;span class="math"&gt;&lt;svg style="width: 3.828em; height: 1.269em; vertical-align: -0.245em; " viewBox="0 -10.24 38.28 12.69"&gt;
&lt;title&gt;
\(4\pi A / p^2\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq3-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='eq3-g8-52' d='M4.12-1.46V-2.02H3.23V-5.9H2.85L.1-2.02V-1.46H2.56V0H3.23V-1.46H4.12ZM2.55-2.02H.45L2.55-5.01V-2.02Z'/&gt;
&lt;path id='eq3-g1-157' d='M.74 2.3H1.36L4.74-8.42H4.12L.74 2.3Z'/&gt;
&lt;path id='eq3-g4-22' d='M6.72 0V-.19C6.04-.25 5.96-.35 5.81-1.21L4.69-7.96H4.38L.93-2C-.01-.42-.13-.29-.61-.19V0H1.62V-.19C1.01-.25 .92-.31 .92-.61C.92-.83 .95-.94 1.16-1.35L1.83-2.69H4.45L4.69-1.13C4.7-1.02 4.72-.92 4.72-.82C4.72-.37 4.55-.26 3.79-.19V0H6.72ZM4.39-3.12H2.08L3.87-6.22L4.39-3.12Z'/&gt;
&lt;path id='eq3-g4-63' d='M5.59-3.76C5.59-4.69 5.07-5.25 4.22-5.25C3.44-5.25 2.87-4.87 2.18-3.91L2.55-5.2C2.55-5.2 2.53-5.25 2.49-5.25H2.48L.64-4.99L.67-4.81C1.02-4.81 1.44-4.81 1.44-4.49C1.44-4.35 1.02-2.7 .6-1.12L-.1 1.55C-.21 2.1-.38 2.24-.89 2.25V2.44H1.55V2.26C.99 2.26 .79 2.17 .79 1.91C.79 1.74 .99 .85 1.23-.07C1.52 .08 1.74 .13 2.01 .13C3.75 .13 5.59-1.86 5.59-3.76ZM4.51-3.73C4.51-2.94 4.19-1.95 3.69-1.23C3.18-.48 2.6-.1 1.97-.1C1.63-.1 1.39-.27 1.39-.54C1.39-.94 1.81-2.49 2.17-3.38C2.48-4.17 3.14-4.74 3.73-4.74C3.74-4.74 3.76-4.74 3.78-4.74C4.29-4.72 4.51-4.41 4.51-3.73Z'/&gt;
&lt;path id='eq3-g4-99' d='M6.21-5.25H2.08C.99-5.25 .33-3.94 .24-3.72H.37C.43-3.8 .87-4.38 1.49-4.38H2.11L1.27-1.87C1.01-1.08 .04-1.05 .04-.24V-.18C.04 .02 .19 .13 .48 .13C.93 .13 1.36-.15 1.94-1.85L2.81-4.38H4.01L3.43-1.82C3.35-1.48 3.3-1.18 3.3-.94V-.83C3.3-.32 3.66 .13 4.24 .13C4.51 .13 4.84 .05 4.84-.35V-.39C4.84-1.1 4.12-1 4.12-1.67C4.12-1.76 4.13-1.87 4.17-2L4.75-4.38H6L6.21-5.25Z'/&gt;
&lt;use id='eq3-g11-52' xlink:href='#eq3-g8-52' transform='scale(1.36)'/&gt;
&lt;/defs&gt;
&lt;g id='eq3-page1'&gt;
&lt;use x='0' y='0' xlink:href='#eq3-g11-52'/&gt;
&lt;use x='6.25' y='0' xlink:href='#eq3-g4-99'/&gt;
&lt;use x='13.84' y='0' xlink:href='#eq3-g4-22'/&gt;
&lt;use x='21.3' y='0' xlink:href='#eq3-g1-157'/&gt;
&lt;use x='27.68' y='0' xlink:href='#eq3-g4-63'/&gt;
&lt;use x='34.13' y='-4.34' xlink:href='#eq3-g8-50'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; (with &lt;span class="math"&gt;&lt;svg style="width: 0.732em; height: 0.796em; vertical-align: -0.000em; " viewBox=".29 -7.96 7.32 7.96"&gt;
&lt;title&gt;
\(A\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq4-g1-22' d='M6.72 0V-.19C6.04-.25 5.96-.35 5.81-1.21L4.69-7.96H4.38L.93-2C-.01-.42-.13-.29-.61-.19V0H1.62V-.19C1.01-.25 .92-.31 .92-.61C.92-.83 .95-.94 1.16-1.35L1.83-2.69H4.45L4.69-1.13C4.7-1.02 4.72-.92 4.72-.82C4.72-.37 4.55-.26 3.79-.19V0H6.72ZM4.39-3.12H2.08L3.87-6.22L4.39-3.12Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq4-page1'&gt;
&lt;use x='.9' y='0' xlink:href='#eq4-g1-22'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; the area and &lt;span class="math"&gt;&lt;svg style="width: 0.648em; height: 0.769em; vertical-align: -0.244em; " viewBox="-.02 -5.25 6.48 7.69"&gt;
&lt;title&gt;
\(p\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq5-g1-63' d='M5.59-3.76C5.59-4.69 5.07-5.25 4.22-5.25C3.44-5.25 2.87-4.87 2.18-3.91L2.55-5.2C2.55-5.2 2.53-5.25 2.49-5.25H2.48L.64-4.99L.67-4.81C1.02-4.81 1.44-4.81 1.44-4.49C1.44-4.35 1.02-2.7 .6-1.12L-.1 1.55C-.21 2.1-.38 2.24-.89 2.25V2.44H1.55V2.26C.99 2.26 .79 2.17 .79 1.91C.79 1.74 .99 .85 1.23-.07C1.52 .08 1.74 .13 2.01 .13C3.75 .13 5.59-1.86 5.59-3.76ZM4.51-3.73C4.51-2.94 4.19-1.95 3.69-1.23C3.18-.48 2.6-.1 1.97-.1C1.63-.1 1.39-.27 1.39-.54C1.39-.94 1.81-2.49 2.17-3.38C2.48-4.17 3.14-4.74 3.73-4.74C3.74-4.74 3.76-4.74 3.78-4.74C4.29-4.72 4.51-4.41 4.51-3.73Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq5-page1'&gt;
&lt;use x='.87' y='0' xlink:href='#eq5-g1-63'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; the perimeter).
This is supposed to be 1 for a circle, and smaller for other shapes. But because OpenCV overestimates the
perimeter of the circle, this measure never reaches 1 (except for very small circles, where it severely underestimates
the perimeter).&lt;/p&gt;
&lt;p&gt;This experiment draws circles of different radii, with different sub-pixel shifts, and measures their perimeter
using OpenCV and DIPlib:&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;cv2&lt;/span&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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;plt&lt;/span&gt;

&lt;span class="n"&gt;radii&lt;/span&gt; &lt;span class="o"&gt;=&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="mi"&gt;5&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="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;sz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&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;radii&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;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
&lt;span class="n"&gt;xx&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;CreateXCoordinate&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="n"&gt;sz&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;yy&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;CreateYCoordinate&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="n"&gt;sz&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;rng&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;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_rng&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;radii&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cv_perimeter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;dip_perimeter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;jj&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&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;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&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;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;offset&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="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yy&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;offset&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="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;R&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;arr&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;asarray&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;dtype&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;uint8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;cnt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findContours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RETR_EXTERNAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CHAIN_APPROX_NONE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;cv_perimeter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arcLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnt&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="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;msr&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;MeasurementTool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&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;features&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Perimeter&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;dip_perimeter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;msr&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="s1"&gt;&amp;#39;Perimeter&amp;#39;&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;true_perimeter&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;np&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;R&lt;/span&gt;
    &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;cv_perimeter&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;true_perimeter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dip_perimeter&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;true_perimeter&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;radii&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&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;200&lt;/span&gt;&lt;span class="p"&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;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;k:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xscale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;log&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;legend&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;OpenCV&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;DIPlib&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;radius (px)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;measured perimeter as fraction of true perimeter&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Error in circle perimeter measurement&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&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 error in perimeter measurement of circles, as function of radius, for OpenCV and DIPlib" src="/images/opencv_perimeter_circle.png"&gt;&lt;/p&gt;
&lt;p&gt;This shows that OpenCV tends to overestimate the circle perimeter by a factor of 6% for larger circles, whereas the
unbiased estimator that DIPlib uses tends towards the right value for larger circles. For very small circles OpenCV
underestimates the perimeter. This is because it measures the length of the polygon formed by the centers of the
border pixels of the object. This polygon is slightly smaller than the object itself, as it doesn&amp;rsquo;t go around all
the object pixels, it goes through the outermost pixels of the object. For circles this introduces a bias in the
perimeter length of &lt;span class="math"&gt;&lt;svg style="width: 0.621em; height: 0.538em; vertical-align: -0.013em; " viewBox=".27 -5.25 6.21 5.38"&gt;
&lt;title&gt;
\(\pi\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq6-g1-99' d='M6.21-5.25H2.08C.99-5.25 .33-3.94 .24-3.72H.37C.43-3.8 .87-4.38 1.49-4.38H2.11L1.27-1.87C1.01-1.08 .04-1.05 .04-.24V-.18C.04 .02 .19 .13 .48 .13C.93 .13 1.36-.15 1.94-1.85L2.81-4.38H4.01L3.43-1.82C3.35-1.48 3.3-1.18 3.3-.94V-.83C3.3-.32 3.66 .13 4.24 .13C4.51 .13 4.84 .05 4.84-.35V-.39C4.84-1.1 4.12-1 4.12-1.67C4.12-1.76 4.13-1.87 4.17-2L4.75-4.38H6L6.21-5.25Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq6-page1'&gt;
&lt;use x='.27' y='0' xlink:href='#eq6-g1-99'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; (the radius is 0.5 pixels too small, and &lt;span class="math"&gt;&lt;svg style="width: 8.623em; height: 1.095em; vertical-align: -0.241em; " viewBox="0 -8.54 86.23 10.95"&gt;
&lt;title&gt;
\(2\pi r - 2\pi (r-0.5)\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq7-g1-0' d='M6.84-2.73V-3.39H.74V-2.73H6.84Z'/&gt;
&lt;path id='eq7-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='eq7-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;path id='eq7-g4-65' d='M4.91-4.65C4.91-5 4.68-5.25 4.35-5.25C4.07-5.25 3.75-5.07 3.44-4.75C2.95-4.24 2.48-3.55 2.29-3.07L2.12-2.64L2.75-5.23L2.72-5.25L.87-4.93V-4.73C.96-4.73 1.07-4.73 1.17-4.73C1.44-4.73 1.69-4.7 1.69-4.45C1.69-4.29 1.69-4.29 1.5-3.48L.54 0H1.44C2.05-2 2.24-2.48 2.76-3.36C3.18-4.09 3.53-4.48 3.74-4.48C3.82-4.48 3.87-4.43 3.93-4.31C4.03-4.11 4.12-4.05 4.36-4.05C4.72-4.05 4.91-4.26 4.91-4.65Z'/&gt;
&lt;path id='eq7-g4-99' d='M6.21-5.25H2.08C.99-5.25 .33-3.94 .24-3.72H.37C.43-3.8 .87-4.38 1.49-4.38H2.11L1.27-1.87C1.01-1.08 .04-1.05 .04-.24V-.18C.04 .02 .19 .13 .48 .13C.93 .13 1.36-.15 1.94-1.85L2.81-4.38H4.01L3.43-1.82C3.35-1.48 3.3-1.18 3.3-.94V-.83C3.3-.32 3.66 .13 4.24 .13C4.51 .13 4.84 .05 4.84-.35V-.39C4.84-1.1 4.12-1 4.12-1.67C4.12-1.76 4.13-1.87 4.17-2L4.75-4.38H6L6.21-5.25Z'/&gt;
&lt;path id='eq7-g4-149' d='M2.16-.51C2.16-.88 1.85-1.19 1.49-1.19S.83-.89 .83-.51C.83-.06 1.24 .13 1.49 .13S2.16-.07 2.16-.51Z'/&gt;
&lt;path id='eq7-g8-48' d='M5.67-3.93C5.67-6.37 4.59-8.05 3.03-8.05C2.37-8.05 1.87-7.85 1.43-7.43C.74-6.77 .29-5.4 .29-4C.29-2.7 .68-1.31 1.24-.64C1.68-.12 2.29 .17 2.98 .17C3.59 .17 4.1-.04 4.53-.45C5.22-1.11 5.67-2.49 5.67-3.93ZM4.53-3.91C4.53-1.42 4-.14 2.98-.14S1.43-1.42 1.43-3.89C1.43-6.42 1.97-7.74 2.99-7.74C3.99-7.74 4.53-6.4 4.53-3.91Z'/&gt;
&lt;path id='eq7-g8-50' d='M5.66-1.63L5.5-1.69C5.06-1.01 4.91-.91 4.37-.91H1.52L3.53-3C4.59-4.11 5.05-5.01 5.05-5.94C5.05-7.13 4.09-8.05 2.85-8.05C2.19-8.05 1.57-7.79 1.13-7.31C.75-6.91 .57-6.53 .37-5.68L.62-5.62C1.1-6.79 1.52-7.17 2.35-7.17C3.35-7.17 4.03-6.49 4.03-5.49C4.03-4.56 3.48-3.45 2.48-2.39L.36-.14V0H5L5.66-1.63Z'/&gt;
&lt;path id='eq7-g8-53' d='M5.22-8.11L5.11-8.19C4.93-7.94 4.81-7.88 4.56-7.88H2.07L.77-5.06C.76-5.04 .76-5 .76-5C.76-4.94 .81-4.91 .91-4.91C1.29-4.91 1.76-4.82 2.25-4.67C3.62-4.23 4.25-3.49 4.25-2.31C4.25-1.17 3.53-.27 2.6-.27C2.36-.27 2.16-.36 1.8-.62C1.42-.89 1.14-1.01 .89-1.01C.55-1.01 .38-.87 .38-.57C.38-.12 .94 .17 1.83 .17C2.83 .17 3.69-.15 4.29-.76C4.84-1.3 5.09-1.98 5.09-2.88C5.09-3.74 4.86-4.29 4.26-4.88C3.74-5.41 3.06-5.68 1.66-5.93L2.16-6.94H4.49C4.68-6.94 4.73-6.97 4.76-7.05L5.22-8.11Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq7-page1'&gt;
&lt;use x='0' y='0' xlink:href='#eq7-g8-50'/&gt;
&lt;use x='6.25' y='0' xlink:href='#eq7-g4-99'/&gt;
&lt;use x='12.76' y='0' xlink:href='#eq7-g4-65'/&gt;
&lt;use x='20.96' y='0' xlink:href='#eq7-g1-0'/&gt;
&lt;use x='31.22' y='0' xlink:href='#eq7-g8-50'/&gt;
&lt;use x='37.47' y='0' xlink:href='#eq7-g4-99'/&gt;
&lt;use x='44.77' y='0' xlink:href='#eq7-g1-185'/&gt;
&lt;use x='49.03' y='0' xlink:href='#eq7-g4-65'/&gt;
&lt;use x='57.24' y='0' xlink:href='#eq7-g1-0'/&gt;
&lt;use x='67.5' y='0' xlink:href='#eq7-g8-48'/&gt;
&lt;use x='73.67' y='0' xlink:href='#eq7-g4-149'/&gt;
&lt;use x='76.86' y='0' xlink:href='#eq7-g8-53'/&gt;
&lt;use x='82.84' y='0' xlink:href='#eq7-g1-186'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; is &lt;span class="math"&gt;&lt;svg style="width: 0.621em; height: 0.538em; vertical-align: -0.013em; " viewBox=".27 -5.25 6.21 5.38"&gt;
&lt;title&gt;
\(\pi\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq8-g1-99' d='M6.21-5.25H2.08C.99-5.25 .33-3.94 .24-3.72H.37C.43-3.8 .87-4.38 1.49-4.38H2.11L1.27-1.87C1.01-1.08 .04-1.05 .04-.24V-.18C.04 .02 .19 .13 .48 .13C.93 .13 1.36-.15 1.94-1.85L2.81-4.38H4.01L3.43-1.82C3.35-1.48 3.3-1.18 3.3-.94V-.83C3.3-.32 3.66 .13 4.24 .13C4.51 .13 4.84 .05 4.84-.35V-.39C4.84-1.1 4.12-1 4.12-1.67C4.12-1.76 4.13-1.87 4.17-2L4.75-4.38H6L6.21-5.25Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq8-page1'&gt;
&lt;use x='.27' y='0' xlink:href='#eq8-g1-99'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;). This bias
is small in comparison to the bias due to the length overestimation for larger circles, but for very small circles
it dominates.&lt;/p&gt;
&lt;h2&gt;Non-symmetric circular structuring elements&lt;/h2&gt;
&lt;p&gt;My PhD thesis was about using morphological openings and closings to measure grain sizes, using what is called
a granulometry. If I had used OpenCV for my thesis, I would have had to roll my own circular structuring elements.
Because OpenCV&amp;rsquo;s are not circularly symmetric. If you create any circular structuring element, then rotate it 90
degrees (or equivalently, transpose the array), then you get a different shape:&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;cv2&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="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;plt&lt;/span&gt;

&lt;span class="n"&gt;kernel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getStructuringElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MORPH_ELLIPSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;comparison&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;dstack&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transpose&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transpose&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comparison&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&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="Comparing a circular structuring element with its tranposed" src="/images/opencv_circle.png"&gt;&lt;/p&gt;
&lt;p&gt;The white and red parts together is the generated kernel, the transposed kernel is the white and cyan parts. &lt;/p&gt;
&lt;p&gt;This has already been &lt;a href="https://github.com/opencv/opencv/issues/22042"&gt;filed as a bug&lt;/a&gt;. And as I&amp;rsquo;ve said before,
we shouldn&amp;rsquo;t fault a large, complex library like OpenCV for having bugs. But that this type of bug exists does
illustrate that the library&amp;rsquo;s focus is not on precise results; if it were, this bug would never have gotten
though testing.&lt;/p&gt;
&lt;h2&gt;Non-idempotent morphological opening&lt;/h2&gt;
&lt;p&gt;Another strange issue with the morphological opening and closing is shown in
&lt;a href="https://stackoverflow.com/q/72525674/7328782"&gt;this question&lt;/a&gt;. The image boundary is not treated correctly,
causing repeated applications of the same filter to continue affecting the image. The opening and closing are
supposed to be idempotent, meaning that applying the same filter multiple times in sequence should have the same
effect as applying it only once.&lt;/p&gt;
&lt;p&gt;I wouldn&amp;rsquo;t fault anyone for a bug, what prompts me to include it here is that this was
&lt;a href="https://github.com/opencv/opencv/issues/22075"&gt;reported as a bug&lt;/a&gt;, but the ticket was dismissed as a &amp;ldquo;question
or other no-action item&amp;rdquo;, which I interpret as nobody being interested in fixing it. This is an issue that matters
in many cases, but none of those cases are intended use cases of OpenCV. Typical users of OpenCV wouldn&amp;rsquo;t care
about the difference.&lt;/p&gt;
&lt;div class="admonition blue"&gt;
&lt;p class="admonition-title"&gt;Pillow&lt;/p&gt;
&lt;p&gt;OpenCV is not the only image processing package with issues. For example,
&lt;a href="https://github.com/python-pillow/Pillow/issues/3134"&gt;Pillow&amp;rsquo;s convolution mirrors the kernel&lt;/a&gt;
&lt;a href="https://github.com/python-pillow/Pillow/issues/3678"&gt;along only one dimension&lt;/a&gt;,
which is just a simple bug that I wouldn&amp;rsquo;t fault anyone for.
Except that it exists since the early days of PIL, and the maintainer says it cannot be fixed!&lt;/p&gt;
&lt;/div&gt;
&lt;h2&gt;One point of criticism&lt;/h2&gt;
&lt;p&gt;All of that wasn&amp;rsquo;t criticism, OpenCV developers made choices favouring speed over precision. Those choices
obviously will produce surprises if one wants to use OpenCV for applications that require precision.
You just need to use the right tool for the job!&lt;/p&gt;
&lt;p&gt;However, given that OpenCV is designed around speed, it is indeed very strange that it doesn&amp;rsquo;t always implement
the fastest known algorithms.&lt;/p&gt;
&lt;p&gt;Take for example the Gaussian filter, arguably the most important basic filter in any image processing toolbox.
This experiment times the computation of the Gaussian filter with a sigma of 5 along both axes, repeated 1000 times.
Both libraries implement the filter quite similarly: They compute the kernel size differently based on the sigma
parameter, but in this case they both produce a Gaussian kernel with 31 samples along each axis. Both libraries
compute the Gaussian as a separable filter, as one would expect.&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;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="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;cv2&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;timeit&lt;/span&gt;

&lt;span class="n"&gt;rng&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;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_rng&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;integers&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="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;astype&lt;/span&gt;&lt;span class="p"&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;float32&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="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GaussianBlur&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;ksize&lt;/span&gt;&lt;span class="o"&gt;=&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;sigmaX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&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="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lambda&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;Gauss&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;sigmas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On my machine, DIPlib&amp;rsquo;s filter is about twice as fast (6.87 s for OpenCV, 3.17 s for DIPlib).&lt;/p&gt;
&lt;p&gt;As I understand it, OpenCV does not use the symmetry of the 1D Gaussian kernel when computing the convolution.
For a general 1D kernel &lt;span class="math"&gt;&lt;svg style="width: 0.569em; height: 0.824em; vertical-align: -0.011em; " viewBox=".43 -8.13 5.69 8.24"&gt;
&lt;title&gt;
\(h\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq9-g1-17' d='M5.69-1.25L5.54-1.39C4.9-.58 4.75-.45 4.56-.45C4.45-.45 4.37-.55 4.37-.67C4.37-.8 4.56-1.54 4.76-2.16C5.12-3.28 5.34-4.16 5.34-4.45C5.34-4.93 5.01-5.25 4.55-5.25C3.78-5.25 2.93-4.48 1.82-2.74L3.25-8.08L3.19-8.13C2.5-7.98 2.04-7.9 1.32-7.81V-7.63C1.38-7.63 1.45-7.63 1.52-7.63C1.82-7.63 2.12-7.61 2.12-7.35C2.12-7.07 1.93-6.46 1.83-6.1L.23 0H1.12C1.6-1.83 1.75-2.24 2.23-2.97C2.85-3.91 3.66-4.65 4.07-4.65C4.25-4.65 4.41-4.5 4.41-4.35C4.41-4.3 4.37-4.14 4.32-3.95L3.67-1.49C3.51-.91 3.43-.54 3.43-.39C3.43-.08 3.63 .11 3.95 .11C4.56 .11 4.97-.21 5.69-1.25Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq9-page1'&gt;
&lt;use x='.43' y='0' xlink:href='#eq9-g1-17'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; with &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; samples, applied to an image line &lt;span class="math"&gt;&lt;svg style="width: 0.680em; height: 1.054em; vertical-align: -0.246em; " viewBox=".33 -8.08 6.8 10.54"&gt;
&lt;title&gt;
\(f\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq11-g1-53' d='M5.05-7.3C5.05-7.74 4.61-8.08 4.03-8.08C3.45-8.08 3-7.83 2.57-7.3C2.2-6.82 1.95-6.26 1.6-5.1H.5L.42-4.72H1.49L.44 .29C.17 1.57-.21 2.22-.71 2.22C-.86 2.22-.95 2.13-.95 2.02C-.95 1.98-.94 1.95-.91 1.89C-.86 1.82-.85 1.77-.85 1.7C-.85 1.45-1.06 1.25-1.31 1.25S-1.75 1.46-1.75 1.74C-1.75 2.16-1.35 2.47-.81 2.47C.25 2.47 1.07 1.3 1.57-.91L2.43-4.72H3.73L3.8-5.1H2.51C2.86-6.98 3.3-7.81 3.97-7.81C4.13-7.81 4.23-7.75 4.23-7.66C4.23-7.66 4.22-7.6 4.18-7.55C4.12-7.46 4.11-7.4 4.11-7.3C4.11-7.02 4.29-6.82 4.55-6.82C4.82-6.82 5.05-7.04 5.05-7.3Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq11-page1'&gt;
&lt;use x='2.08' y='0' xlink:href='#eq11-g1-53'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; at &lt;span class="math"&gt;&lt;svg style="width: 0.329em; height: 0.779em; vertical-align: -0.013em; " viewBox="-.24 -7.66 3.29 7.79"&gt;
&lt;title&gt;
\(i\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq12-g1-56' d='M2.8-1.23L2.64-1.36C2.13-.68 1.87-.43 1.67-.43C1.57-.43 1.48-.51 1.48-.61C1.48-.8 1.6-1.16 1.67-1.39L2.72-5.23L2.68-5.25C1.48-5.03 1.24-4.99 .77-4.95V-4.76C1.42-4.75 1.52-4.72 1.52-4.48C1.52-4.38 1.49-4.18 1.42-3.95L.85-1.85C.66-1.13 .58-.79 .58-.55C.58-.11 .77 .13 1.13 .13C1.69 .13 2.12-.23 2.8-1.23ZM3.29-7C3.29-7.35 3.01-7.66 2.69-7.66S2.13-7.4 2.13-7.03C2.13-6.65 2.36-6.4 2.7-6.4C3.01-6.4 3.29-6.67 3.29-7Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq12-page1'&gt;
&lt;use x='-.24' y='0' xlink:href='#eq12-g1-56'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt;, we compute&lt;/p&gt;
&lt;div class="math"&gt;&lt;svg style="width: 26.877em; height: 1.089em; vertical-align: 0.001em; " viewBox="59.54 -10.9 268.77 10.89"&gt;
&lt;title&gt;
\[
    f[i] h[0] + f[i+1] h[1] + \cdots + f[i+k-1] h[k-1] \quad .
\]
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq1-g8-48' d='M5.67-3.93C5.67-6.37 4.59-8.05 3.03-8.05C2.37-8.05 1.87-7.85 1.43-7.43C.74-6.77 .29-5.4 .29-4C.29-2.7 .68-1.31 1.24-.64C1.68-.12 2.29 .17 2.98 .17C3.59 .17 4.1-.04 4.53-.45C5.22-1.11 5.67-2.49 5.67-3.93ZM4.53-3.91C4.53-1.42 4-.14 2.98-.14S1.43-1.42 1.43-3.89C1.43-6.42 1.97-7.74 2.99-7.74C3.99-7.74 4.53-6.4 4.53-3.91Z'/&gt;
&lt;path id='eq1-g8-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='eq1-g1-0' d='M6.84-2.73V-3.39H.74V-2.73H6.84Z'/&gt;
&lt;path id='eq1-g1-1' d='M2.16-3.05C2.16-3.42 1.85-3.73 1.49-3.73S.83-3.43 .83-3.05C.83-2.6 1.24-2.41 1.49-2.41S2.16-2.61 2.16-3.05Z'/&gt;
&lt;path id='eq1-g1-184' d='M2.97-3.41H.36V-2.62H2.97V0H3.75V-2.62H6.36V-3.41H3.75V-6.03H2.97V-3.41Z'/&gt;
&lt;path id='eq1-g1-187' d='M3.56 2H2.54C2.14 2 1.95 1.8 1.95 1.38V-7.6C1.95-7.97 2.11-8.12 2.49-8.12H3.56V-8.42H1.05V2.3H3.56V2Z'/&gt;
&lt;path id='eq1-g1-188' d='M.4 2V2.3H2.92V-8.42H.4V-8.12H1.43C1.82-8.12 2.01-7.92 2.01-7.5V1.48C2.01 1.85 1.85 2 1.48 2H.4Z'/&gt;
&lt;path id='eq1-g4-17' d='M5.69-1.25L5.54-1.39C4.9-.58 4.75-.45 4.56-.45C4.45-.45 4.37-.55 4.37-.67C4.37-.8 4.56-1.54 4.76-2.16C5.12-3.28 5.34-4.16 5.34-4.45C5.34-4.93 5.01-5.25 4.55-5.25C3.78-5.25 2.93-4.48 1.82-2.74L3.25-8.08L3.19-8.13C2.5-7.98 2.04-7.9 1.32-7.81V-7.63C1.38-7.63 1.45-7.63 1.52-7.63C1.82-7.63 2.12-7.61 2.12-7.35C2.12-7.07 1.93-6.46 1.83-6.1L.23 0H1.12C1.6-1.83 1.75-2.24 2.23-2.97C2.85-3.91 3.66-4.65 4.07-4.65C4.25-4.65 4.41-4.5 4.41-4.35C4.41-4.3 4.37-4.14 4.32-3.95L3.67-1.49C3.51-.91 3.43-.54 3.43-.39C3.43-.08 3.63 .11 3.95 .11C4.56 .11 4.97-.21 5.69-1.25Z'/&gt;
&lt;path id='eq1-g4-53' d='M5.05-7.3C5.05-7.74 4.61-8.08 4.03-8.08C3.45-8.08 3-7.83 2.57-7.3C2.2-6.82 1.95-6.26 1.6-5.1H.5L.42-4.72H1.49L.44 .29C.17 1.57-.21 2.22-.71 2.22C-.86 2.22-.95 2.13-.95 2.02C-.95 1.98-.94 1.95-.91 1.89C-.86 1.82-.85 1.77-.85 1.7C-.85 1.45-1.06 1.25-1.31 1.25S-1.75 1.46-1.75 1.74C-1.75 2.16-1.35 2.47-.81 2.47C.25 2.47 1.07 1.3 1.57-.91L2.43-4.72H3.73L3.8-5.1H2.51C2.86-6.98 3.3-7.81 3.97-7.81C4.13-7.81 4.23-7.75 4.23-7.66C4.23-7.66 4.22-7.6 4.18-7.55C4.12-7.46 4.11-7.4 4.11-7.3C4.11-7.02 4.29-6.82 4.55-6.82C4.82-6.82 5.05-7.04 5.05-7.3Z'/&gt;
&lt;path id='eq1-g4-56' d='M2.8-1.23L2.64-1.36C2.13-.68 1.87-.43 1.67-.43C1.57-.43 1.48-.51 1.48-.61C1.48-.8 1.6-1.16 1.67-1.39L2.72-5.23L2.68-5.25C1.48-5.03 1.24-4.99 .77-4.95V-4.76C1.42-4.75 1.52-4.72 1.52-4.48C1.52-4.38 1.49-4.18 1.42-3.95L.85-1.85C.66-1.13 .58-.79 .58-.55C.58-.11 .77 .13 1.13 .13C1.69 .13 2.12-.23 2.8-1.23ZM3.29-7C3.29-7.35 3.01-7.66 2.69-7.66S2.13-7.4 2.13-7.03C2.13-6.65 2.36-6.4 2.7-6.4C3.01-6.4 3.29-6.67 3.29-7Z'/&gt;
&lt;path id='eq1-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='eq1-g4-149' d='M2.16-.51C2.16-.88 1.85-1.19 1.49-1.19S.83-.89 .83-.51C.83-.06 1.24 .13 1.49 .13S2.16-.07 2.16-.51Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq1-page1'&gt;
&lt;use x='61.29' y='-2.48' xlink:href='#eq1-g4-53'/&gt;
&lt;use x='67.43' y='-2.48' xlink:href='#eq1-g1-187'/&gt;
&lt;use x='71.53' y='-2.48' xlink:href='#eq1-g4-56'/&gt;
&lt;use x='75.31' y='-2.48' xlink:href='#eq1-g1-188'/&gt;
&lt;use x='80.44' y='-2.48' xlink:href='#eq1-g4-17'/&gt;
&lt;use x='87.37' y='-2.48' xlink:href='#eq1-g1-187'/&gt;
&lt;use x='91.71' y='-2.48' xlink:href='#eq1-g8-48'/&gt;
&lt;use x='97.69' y='-2.48' xlink:href='#eq1-g1-188'/&gt;
&lt;use x='105.04' y='-2.48' xlink:href='#eq1-g1-184'/&gt;
&lt;use x='116.52' y='-2.48' xlink:href='#eq1-g4-53'/&gt;
&lt;use x='122.67' y='-2.48' xlink:href='#eq1-g1-187'/&gt;
&lt;use x='126.77' y='-2.48' xlink:href='#eq1-g4-56'/&gt;
&lt;use x='133.2' y='-2.48' xlink:href='#eq1-g1-184'/&gt;
&lt;use x='142.6' y='-2.48' xlink:href='#eq1-g8-49'/&gt;
&lt;use x='148.58' y='-2.48' xlink:href='#eq1-g1-188'/&gt;
&lt;use x='153.71' y='-2.48' xlink:href='#eq1-g4-17'/&gt;
&lt;use x='160.64' y='-2.48' xlink:href='#eq1-g1-187'/&gt;
&lt;use x='164.98' y='-2.48' xlink:href='#eq1-g8-49'/&gt;
&lt;use x='170.96' y='-2.48' xlink:href='#eq1-g1-188'/&gt;
&lt;use x='178.32' y='-2.48' xlink:href='#eq1-g1-184'/&gt;
&lt;use x='187.84' y='-2.48' xlink:href='#eq1-g1-1'/&gt;
&lt;use x='193.06' y='-2.48' xlink:href='#eq1-g1-1'/&gt;
&lt;use x='198.28' y='-2.48' xlink:href='#eq1-g1-1'/&gt;
&lt;use x='204.04' y='-2.48' xlink:href='#eq1-g1-184'/&gt;
&lt;use x='215.52' y='-2.48' xlink:href='#eq1-g4-53'/&gt;
&lt;use x='221.67' y='-2.48' xlink:href='#eq1-g1-187'/&gt;
&lt;use x='225.77' y='-2.48' xlink:href='#eq1-g4-56'/&gt;
&lt;use x='232.2' y='-2.48' xlink:href='#eq1-g1-184'/&gt;
&lt;use x='242.09' y='-2.48' xlink:href='#eq1-g4-58'/&gt;
&lt;use x='251.01' y='-2.48' xlink:href='#eq1-g1-0'/&gt;
&lt;use x='261.27' y='-2.48' xlink:href='#eq1-g8-49'/&gt;
&lt;use x='267.25' y='-2.48' xlink:href='#eq1-g1-188'/&gt;
&lt;use x='272.38' y='-2.48' xlink:href='#eq1-g4-17'/&gt;
&lt;use x='279.31' y='-2.48' xlink:href='#eq1-g1-187'/&gt;
&lt;use x='284.14' y='-2.48' xlink:href='#eq1-g4-58'/&gt;
&lt;use x='293.06' y='-2.48' xlink:href='#eq1-g1-0'/&gt;
&lt;use x='303.32' y='-2.48' xlink:href='#eq1-g8-49'/&gt;
&lt;use x='309.3' y='-2.48' xlink:href='#eq1-g1-188'/&gt;
&lt;use x='326.15' y='-2.48' xlink:href='#eq1-g4-149'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/div&gt;
&lt;p&gt;But if we know that &lt;span class="math"&gt;&lt;svg style="width: 0.569em; height: 0.824em; vertical-align: -0.011em; " viewBox=".43 -8.13 5.69 8.24"&gt;
&lt;title&gt;
\(h\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq13-g1-17' d='M5.69-1.25L5.54-1.39C4.9-.58 4.75-.45 4.56-.45C4.45-.45 4.37-.55 4.37-.67C4.37-.8 4.56-1.54 4.76-2.16C5.12-3.28 5.34-4.16 5.34-4.45C5.34-4.93 5.01-5.25 4.55-5.25C3.78-5.25 2.93-4.48 1.82-2.74L3.25-8.08L3.19-8.13C2.5-7.98 2.04-7.9 1.32-7.81V-7.63C1.38-7.63 1.45-7.63 1.52-7.63C1.82-7.63 2.12-7.61 2.12-7.35C2.12-7.07 1.93-6.46 1.83-6.1L.23 0H1.12C1.6-1.83 1.75-2.24 2.23-2.97C2.85-3.91 3.66-4.65 4.07-4.65C4.25-4.65 4.41-4.5 4.41-4.35C4.41-4.3 4.37-4.14 4.32-3.95L3.67-1.49C3.51-.91 3.43-.54 3.43-.39C3.43-.08 3.63 .11 3.95 .11C4.56 .11 4.97-.21 5.69-1.25Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq13-page1'&gt;
&lt;use x='.43' y='0' xlink:href='#eq13-g1-17'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; is symmetric, with &lt;span class="math"&gt;&lt;svg style="width: 7.594em; height: 1.072em; vertical-align: -0.230em; " viewBox=".43 -8.42 75.94 10.72"&gt;
&lt;title&gt;
\(h[0] = h[k-1]\)
&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-0' d='M6.84-2.73V-3.39H.74V-2.73H6.84Z'/&gt;
&lt;path id='eq14-g4-187' d='M3.56 2H2.54C2.14 2 1.95 1.8 1.95 1.38V-7.6C1.95-7.97 2.11-8.12 2.49-8.12H3.56V-8.42H1.05V2.3H3.56V2Z'/&gt;
&lt;path id='eq14-g4-188' d='M.4 2V2.3H2.92V-8.42H.4V-8.12H1.43C1.82-8.12 2.01-7.92 2.01-7.5V1.48C2.01 1.85 1.85 2 1.48 2H.4Z'/&gt;
&lt;path id='eq14-g11-48' d='M5.67-3.93C5.67-6.37 4.59-8.05 3.03-8.05C2.37-8.05 1.87-7.85 1.43-7.43C.74-6.77 .29-5.4 .29-4C.29-2.7 .68-1.31 1.24-.64C1.68-.12 2.29 .17 2.98 .17C3.59 .17 4.1-.04 4.53-.45C5.22-1.11 5.67-2.49 5.67-3.93ZM4.53-3.91C4.53-1.42 4-.14 2.98-.14S1.43-1.42 1.43-3.89C1.43-6.42 1.97-7.74 2.99-7.74C3.99-7.74 4.53-6.4 4.53-3.91Z'/&gt;
&lt;path id='eq14-g11-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='eq14-g7-17' d='M5.69-1.25L5.54-1.39C4.9-.58 4.75-.45 4.56-.45C4.45-.45 4.37-.55 4.37-.67C4.37-.8 4.56-1.54 4.76-2.16C5.12-3.28 5.34-4.16 5.34-4.45C5.34-4.93 5.01-5.25 4.55-5.25C3.78-5.25 2.93-4.48 1.82-2.74L3.25-8.08L3.19-8.13C2.5-7.98 2.04-7.9 1.32-7.81V-7.63C1.38-7.63 1.45-7.63 1.52-7.63C1.82-7.63 2.12-7.61 2.12-7.35C2.12-7.07 1.93-6.46 1.83-6.1L.23 0H1.12C1.6-1.83 1.75-2.24 2.23-2.97C2.85-3.91 3.66-4.65 4.07-4.65C4.25-4.65 4.41-4.5 4.41-4.35C4.41-4.3 4.37-4.14 4.32-3.95L3.67-1.49C3.51-.91 3.43-.54 3.43-.39C3.43-.08 3.63 .11 3.95 .11C4.56 .11 4.97-.21 5.69-1.25Z'/&gt;
&lt;path id='eq14-g7-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='eq14-page1'&gt;
&lt;use x='.43' y='0' xlink:href='#eq14-g7-17'/&gt;
&lt;use x='7.36' y='0' xlink:href='#eq14-g4-187'/&gt;
&lt;use x='11.7' y='0' xlink:href='#eq14-g11-48'/&gt;
&lt;use x='17.68' y='0' xlink:href='#eq14-g4-188'/&gt;
&lt;use x='25.7' y='0' xlink:href='#eq14-g1-61'/&gt;
&lt;use x='36.53' y='0' xlink:href='#eq14-g7-17'/&gt;
&lt;use x='43.46' y='0' xlink:href='#eq14-g4-187'/&gt;
&lt;use x='48.29' y='0' xlink:href='#eq14-g7-58'/&gt;
&lt;use x='57.21' y='0' xlink:href='#eq14-g4-0'/&gt;
&lt;use x='67.47' y='0' xlink:href='#eq14-g11-49'/&gt;
&lt;use x='73.45' y='0' xlink:href='#eq14-g4-188'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; and so on, then we can instead compute (assuming &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='eq15-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='eq15-page1'&gt;
&lt;use x='.49' y='0' xlink:href='#eq15-g1-58'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; is odd)&lt;/p&gt;
&lt;div class="math"&gt;&lt;svg style="width: 32.223em; height: 1.102em; vertical-align: 0.002em; " viewBox="33.05 -11.04 322.23 11.02"&gt;
&lt;title&gt;
\[
    \left( f[i] + f[i+k-1] \right) h[0] + \left( f[i+1] + f[i+k-2] \right) h[1] + \cdots \quad ,
\]
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq2-g8-48' d='M5.67-3.93C5.67-6.37 4.59-8.05 3.03-8.05C2.37-8.05 1.87-7.85 1.43-7.43C.74-6.77 .29-5.4 .29-4C.29-2.7 .68-1.31 1.24-.64C1.68-.12 2.29 .17 2.98 .17C3.59 .17 4.1-.04 4.53-.45C5.22-1.11 5.67-2.49 5.67-3.93ZM4.53-3.91C4.53-1.42 4-.14 2.98-.14S1.43-1.42 1.43-3.89C1.43-6.42 1.97-7.74 2.99-7.74C3.99-7.74 4.53-6.4 4.53-3.91Z'/&gt;
&lt;path id='eq2-g8-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='eq2-g8-50' d='M5.66-1.63L5.5-1.69C5.06-1.01 4.91-.91 4.37-.91H1.52L3.53-3C4.59-4.11 5.05-5.01 5.05-5.94C5.05-7.13 4.09-8.05 2.85-8.05C2.19-8.05 1.57-7.79 1.13-7.31C.75-6.91 .57-6.53 .37-5.68L.62-5.62C1.1-6.79 1.52-7.17 2.35-7.17C3.35-7.17 4.03-6.49 4.03-5.49C4.03-4.56 3.48-3.45 2.48-2.39L.36-.14V0H5L5.66-1.63Z'/&gt;
&lt;path id='eq2-g4-17' d='M5.69-1.25L5.54-1.39C4.9-.58 4.75-.45 4.56-.45C4.45-.45 4.37-.55 4.37-.67C4.37-.8 4.56-1.54 4.76-2.16C5.12-3.28 5.34-4.16 5.34-4.45C5.34-4.93 5.01-5.25 4.55-5.25C3.78-5.25 2.93-4.48 1.82-2.74L3.25-8.08L3.19-8.13C2.5-7.98 2.04-7.9 1.32-7.81V-7.63C1.38-7.63 1.45-7.63 1.52-7.63C1.82-7.63 2.12-7.61 2.12-7.35C2.12-7.07 1.93-6.46 1.83-6.1L.23 0H1.12C1.6-1.83 1.75-2.24 2.23-2.97C2.85-3.91 3.66-4.65 4.07-4.65C4.25-4.65 4.41-4.5 4.41-4.35C4.41-4.3 4.37-4.14 4.32-3.95L3.67-1.49C3.51-.91 3.43-.54 3.43-.39C3.43-.08 3.63 .11 3.95 .11C4.56 .11 4.97-.21 5.69-1.25Z'/&gt;
&lt;path id='eq2-g4-53' d='M5.05-7.3C5.05-7.74 4.61-8.08 4.03-8.08C3.45-8.08 3-7.83 2.57-7.3C2.2-6.82 1.95-6.26 1.6-5.1H.5L.42-4.72H1.49L.44 .29C.17 1.57-.21 2.22-.71 2.22C-.86 2.22-.95 2.13-.95 2.02C-.95 1.98-.94 1.95-.91 1.89C-.86 1.82-.85 1.77-.85 1.7C-.85 1.45-1.06 1.25-1.31 1.25S-1.75 1.46-1.75 1.74C-1.75 2.16-1.35 2.47-.81 2.47C.25 2.47 1.07 1.3 1.57-.91L2.43-4.72H3.73L3.8-5.1H2.51C2.86-6.98 3.3-7.81 3.97-7.81C4.13-7.81 4.23-7.75 4.23-7.66C4.23-7.66 4.22-7.6 4.18-7.55C4.12-7.46 4.11-7.4 4.11-7.3C4.11-7.02 4.29-6.82 4.55-6.82C4.82-6.82 5.05-7.04 5.05-7.3Z'/&gt;
&lt;path id='eq2-g4-56' d='M2.8-1.23L2.64-1.36C2.13-.68 1.87-.43 1.67-.43C1.57-.43 1.48-.51 1.48-.61C1.48-.8 1.6-1.16 1.67-1.39L2.72-5.23L2.68-5.25C1.48-5.03 1.24-4.99 .77-4.95V-4.76C1.42-4.75 1.52-4.72 1.52-4.48C1.52-4.38 1.49-4.18 1.42-3.95L.85-1.85C.66-1.13 .58-.79 .58-.55C.58-.11 .77 .13 1.13 .13C1.69 .13 2.12-.23 2.8-1.23ZM3.29-7C3.29-7.35 3.01-7.66 2.69-7.66S2.13-7.4 2.13-7.03C2.13-6.65 2.36-6.4 2.7-6.4C3.01-6.4 3.29-6.67 3.29-7Z'/&gt;
&lt;path id='eq2-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='eq2-g4-150' d='M2.32-.07C2.32-1.06 1.63-1.21 1.36-1.21C1.06-1.21 .67-1.04 .67-.52C.67-.05 1.1 .07 1.41 .07C1.49 .07 1.55 .06 1.58 .05C1.63 .04 1.67 .02 1.69 .02C1.77 .02 1.86 .08 1.86 .19C1.86 .42 1.67 .95 .88 1.45L.99 1.68C1.35 1.56 2.32 .77 2.32-.07Z'/&gt;
&lt;path id='eq2-g1-0' d='M6.84-2.73V-3.39H.74V-2.73H6.84Z'/&gt;
&lt;path id='eq2-g1-1' d='M2.16-3.05C2.16-3.42 1.85-3.73 1.49-3.73S.83-3.43 .83-3.05C.83-2.6 1.24-2.41 1.49-2.41S2.16-2.61 2.16-3.05Z'/&gt;
&lt;path id='eq2-g1-184' d='M2.97-3.41H.36V-2.62H2.97V0H3.75V-2.62H6.36V-3.41H3.75V-6.03H2.97V-3.41Z'/&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;path id='eq2-g1-187' d='M3.56 2H2.54C2.14 2 1.95 1.8 1.95 1.38V-7.6C1.95-7.97 2.11-8.12 2.49-8.12H3.56V-8.42H1.05V2.3H3.56V2Z'/&gt;
&lt;path id='eq2-g1-188' d='M.4 2V2.3H2.92V-8.42H.4V-8.12H1.43C1.82-8.12 2.01-7.92 2.01-7.5V1.48C2.01 1.85 1.85 2 1.48 2H.4Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq2-page1'&gt;
&lt;use x='33.05' y='-2.5' xlink:href='#eq2-g1-185'/&gt;
&lt;use x='39.59' y='-2.48' xlink:href='#eq2-g4-53'/&gt;
&lt;use x='45.74' y='-2.48' xlink:href='#eq2-g1-187'/&gt;
&lt;use x='49.84' y='-2.48' xlink:href='#eq2-g4-56'/&gt;
&lt;use x='53.62' y='-2.48' xlink:href='#eq2-g1-188'/&gt;
&lt;use x='60.97' y='-2.48' xlink:href='#eq2-g1-184'/&gt;
&lt;use x='72.45' y='-2.48' xlink:href='#eq2-g4-53'/&gt;
&lt;use x='78.6' y='-2.48' xlink:href='#eq2-g1-187'/&gt;
&lt;use x='82.7' y='-2.48' xlink:href='#eq2-g4-56'/&gt;
&lt;use x='89.13' y='-2.48' xlink:href='#eq2-g1-184'/&gt;
&lt;use x='99.02' y='-2.48' xlink:href='#eq2-g4-58'/&gt;
&lt;use x='107.94' y='-2.48' xlink:href='#eq2-g1-0'/&gt;
&lt;use x='118.2' y='-2.48' xlink:href='#eq2-g8-49'/&gt;
&lt;use x='124.18' y='-2.48' xlink:href='#eq2-g1-188'/&gt;
&lt;use x='128.88' y='-2.5' xlink:href='#eq2-g1-186'/&gt;
&lt;use x='135.88' y='-2.48' xlink:href='#eq2-g4-17'/&gt;
&lt;use x='142.81' y='-2.48' xlink:href='#eq2-g1-187'/&gt;
&lt;use x='147.15' y='-2.48' xlink:href='#eq2-g8-48'/&gt;
&lt;use x='153.13' y='-2.48' xlink:href='#eq2-g1-188'/&gt;
&lt;use x='160.49' y='-2.48' xlink:href='#eq2-g1-184'/&gt;
&lt;use x='170.48' y='-2.5' xlink:href='#eq2-g1-185'/&gt;
&lt;use x='177.02' y='-2.48' xlink:href='#eq2-g4-53'/&gt;
&lt;use x='183.17' y='-2.48' xlink:href='#eq2-g1-187'/&gt;
&lt;use x='187.27' y='-2.48' xlink:href='#eq2-g4-56'/&gt;
&lt;use x='193.7' y='-2.48' xlink:href='#eq2-g1-184'/&gt;
&lt;use x='203.1' y='-2.48' xlink:href='#eq2-g8-49'/&gt;
&lt;use x='209.08' y='-2.48' xlink:href='#eq2-g1-188'/&gt;
&lt;use x='216.44' y='-2.48' xlink:href='#eq2-g1-184'/&gt;
&lt;use x='227.92' y='-2.48' xlink:href='#eq2-g4-53'/&gt;
&lt;use x='234.06' y='-2.48' xlink:href='#eq2-g1-187'/&gt;
&lt;use x='238.16' y='-2.48' xlink:href='#eq2-g4-56'/&gt;
&lt;use x='244.6' y='-2.48' xlink:href='#eq2-g1-184'/&gt;
&lt;use x='254.48' y='-2.48' xlink:href='#eq2-g4-58'/&gt;
&lt;use x='263.41' y='-2.48' xlink:href='#eq2-g1-0'/&gt;
&lt;use x='273.67' y='-2.48' xlink:href='#eq2-g8-50'/&gt;
&lt;use x='279.64' y='-2.48' xlink:href='#eq2-g1-188'/&gt;
&lt;use x='284.34' y='-2.5' xlink:href='#eq2-g1-186'/&gt;
&lt;use x='291.34' y='-2.48' xlink:href='#eq2-g4-17'/&gt;
&lt;use x='298.28' y='-2.48' xlink:href='#eq2-g1-187'/&gt;
&lt;use x='302.62' y='-2.48' xlink:href='#eq2-g8-49'/&gt;
&lt;use x='308.6' y='-2.48' xlink:href='#eq2-g1-188'/&gt;
&lt;use x='315.95' y='-2.48' xlink:href='#eq2-g1-184'/&gt;
&lt;use x='325.47' y='-2.48' xlink:href='#eq2-g1-1'/&gt;
&lt;use x='330.69' y='-2.48' xlink:href='#eq2-g1-1'/&gt;
&lt;use x='335.91' y='-2.48' xlink:href='#eq2-g1-1'/&gt;
&lt;use x='352.97' y='-2.48' xlink:href='#eq2-g4-150'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/div&gt;
&lt;p&gt;saving us &lt;span class="math"&gt;&lt;svg style="width: 2.640em; height: 1.072em; vertical-align: -0.230em; " viewBox=".72 -8.42 26.4 10.72"&gt;
&lt;title&gt;
\(\lfloor k/2 \rfloor\)
&lt;/title&gt;
&lt;defs&gt;
&lt;path id='eq16-g8-50' d='M5.66-1.63L5.5-1.69C5.06-1.01 4.91-.91 4.37-.91H1.52L3.53-3C4.59-4.11 5.05-5.01 5.05-5.94C5.05-7.13 4.09-8.05 2.85-8.05C2.19-8.05 1.57-7.79 1.13-7.31C.75-6.91 .57-6.53 .37-5.68L.62-5.62C1.1-6.79 1.52-7.17 2.35-7.17C3.35-7.17 4.03-6.49 4.03-5.49C4.03-4.56 3.48-3.45 2.48-2.39L.36-.14V0H5L5.66-1.63Z'/&gt;
&lt;path id='eq16-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='eq16-g1-98' d='M4.01 2.29V1.57H1.76V-8.42H1.05V2.29H4.01Z'/&gt;
&lt;path id='eq16-g1-99' d='M3.37 2.26V-8.42H2.66V1.55H.4V2.26H3.37Z'/&gt;
&lt;path id='eq16-g1-157' d='M.74 2.3H1.36L4.74-8.42H4.12L.74 2.3Z'/&gt;
&lt;/defs&gt;
&lt;g id='eq16-page1'&gt;
&lt;use x='.72' y='0' xlink:href='#eq16-g1-98'/&gt;
&lt;use x='6' y='0' xlink:href='#eq16-g4-58'/&gt;
&lt;use x='12.27' y='0' xlink:href='#eq16-g1-157'/&gt;
&lt;use x='17.77' y='0' xlink:href='#eq16-g8-50'/&gt;
&lt;use x='23.74' y='0' xlink:href='#eq16-g1-99'/&gt;
&lt;/g&gt;
&lt;/svg&gt;&lt;/span&gt; multiplications. If OpenCV is serious about speed, this seems an obvious optimization
to make. DIPlib has a dedicated convolution also for 1D antisymmetric kernels, such as the first and third derivatives
of the Gaussian.&lt;/p&gt;
&lt;p&gt;Likewise, OpenCV&amp;rsquo;s dilation, the most basic morphological operator, is the naive algorithm, although implemented
very efficiently. The naive algorithm computes the maximum over the pixels under the kernel, shifts the kernel,
computes the maximum over the new set of pixels, etc. It does not attempt to use the result for the previous pixel
to make the current pixel&amp;rsquo;s computation lighter. OpenCV does recognize that the square kernel is separable, and so has
an implementation that is linear in the length of the side of the square. A circular kernel is not separable,
which means that the naive algorithm is quadratic in the circle&amp;rsquo;s diameter (it scales with the area).&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an experiment:&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;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="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;cv2&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;timeit&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;plt&lt;/span&gt;

&lt;span class="n"&gt;rng&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;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_rng&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;integers&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="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;astype&lt;/span&gt;&lt;span class="p"&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;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;sizes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&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="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;kernel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getStructuringElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MORPH_RECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ksize&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="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="n"&gt;t1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dilate&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;kernel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iterations&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;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
    &lt;span class="n"&gt;t2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;lambda&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;Dilation&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;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SE&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="s2"&gt;&amp;quot;rectangular&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
    &lt;span class="n"&gt;kernel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getStructuringElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MORPH_ELLIPSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ksize&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="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="n"&gt;t3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dilate&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;kernel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iterations&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;number&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="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="n"&gt;t4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;lambda&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;Dilation&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;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SE&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="s2"&gt;&amp;quot;elliptic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="n"&gt;number&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="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;times&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;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;times&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="s2"&gt;&amp;quot;.:r&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Square, OpenCV&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;times&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="s2"&gt;&amp;quot;.:b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Square, DIPlib&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;times&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="s2"&gt;&amp;quot;.-r&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Circle, OpenCV&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;times&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="s2"&gt;&amp;quot;.-b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Circle, DIPlib&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yscale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;log&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kernel diameter (pixels)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;time for a single dilation (s)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;legend&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&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="Timing comparison for dilations with OpenCV and DIPlib" src="/images/opencv_morphology.png"&gt;&lt;/p&gt;
&lt;p&gt;For smaller kernels, OpenCV is much faster than DIPlib. They really put effort into the quality of the implementation.
But it is obvious from the graph that DIPlib implements the better algorithms. For both kernel shapes, DIPlib
scales better than OpenCV: the square kernel is independent of the size, and the circular kernel scales with the
diameter (not the area). &lt;a href="/archives/885"&gt;I have written before about optimizing the wrong algorithm&lt;/a&gt;,
MATLAB has a similar issue.&lt;/p&gt;
&lt;p&gt;By the way, the reason that OpenCV can be so much faster than DIPlib for small kernels is because OpenCV&amp;rsquo;s
implementation is explicitly 2D, and requires pixels within an image line to be consecutive in memory. This
allows the implementation to optimize memory access patterns. 
DIPlib prefers more generic algorithms. DIPlib&amp;rsquo;s implementation works for an arbitrary number of dimensions,
with an arbitrary memory layout. There are some optimizations that are just not possible without those additional
constraints.&lt;/p&gt;</content><category term="analyses"></category><category term="OpenCV"></category><category term="interpolation"></category><category term="measure"></category><category term="quantification"></category><category term="performance"></category><category term="precision"></category><category term="numerical accuracy"></category></entry><entry><title>DIPlib 3.4.0 released</title><link href="https://www.crisluengo.net/archives/1139" rel="alternate"></link><published>2022-12-09T00:00:00-07:00</published><updated>2022-12-09T00:00:00-07:00</updated><author><name>Cris Luengo</name></author><id>tag:www.crisluengo.net,2022-12-09:/archives/1139</id><summary type="html">&lt;p&gt;Yesterday we released DIPlib version 3.4.0. The &lt;a href="https://diplib.org/changelogs/diplib_3.4.0.html"&gt;change log&lt;/a&gt;
is quite extensive. The improved median filter I discussed in &lt;a href="/archives/1138" title="Median filtering"&gt;my previous blog post&lt;/a&gt;
is in this release, as well as a much faster built-in FFT implementation (used when not linking against FFTW),
a bunch of new functionality including …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Yesterday we released DIPlib version 3.4.0. The &lt;a href="https://diplib.org/changelogs/diplib_3.4.0.html"&gt;change log&lt;/a&gt;
is quite extensive. The improved median filter I discussed in &lt;a href="/archives/1138" title="Median filtering"&gt;my previous blog post&lt;/a&gt;
is in this release, as well as a much faster built-in FFT implementation (used when not linking against FFTW),
a bunch of new functionality including various iterative
&lt;a href="https://github.com/DIPlib/diplib/blob/master/examples/python/deconvolution.ipynb"&gt;deconvolution algorithms&lt;/a&gt;,
and some bug fixes.&lt;/p&gt;
&lt;h2&gt;New function forms in Python bindings&lt;/h2&gt;
&lt;p&gt;DIPib has always had &lt;a href="https://diplib.org/diplib-docs/design.html#design_function_signatures"&gt;two signatures for each function&lt;/a&gt;,
one where the output image is passed as an input argument to be modified, one that returns the output image.
For example, the &lt;code&gt;dip::Gauss&lt;/code&gt; function can be called in these two ways:&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;dip&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="w"&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;dip&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Gauss&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="n"&gt;out&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;3&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;0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;FIR&amp;quot;&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;Image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;out&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;dip&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Gauss&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="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="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="s"&gt;&amp;quot;FIR&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;(I&amp;rsquo;m purposefully specifying more parameters here than necessary, bear with me.)&lt;/p&gt;
&lt;p&gt;When I first created the Python bindings, PyDIP, I only created bindings for the second form, with the output
image as return value. It is the more user-friendly form, and I might have been subconsciously imitating the
MATLAB syntax of DIPimage. In this latest release, we&amp;rsquo;re adding bindings for the other form as well.&lt;/p&gt;
&lt;p&gt;In Python it is not possible to have multiple signatures for one function. We use Pybind11 to create the
Python bindings for DIPlib, which does allow multiple overloaded functions to be called from Python. It has
an overload resolution algorithm built-in, where it attempts to convert input arguments to the types needed
by the first overload, if it fails it tries the second overload, etc. But Python is not as strongly typed as C++,
so it can be difficult to avoid certain type conversions from happening, making the two overloads difficult
to distinguish for some functions. To avoid ambiguity, we made the output image argument a keyword-only argument.
All arguments that come after must be keyword-only as well, as per Python syntax.
So, whenever there is an &lt;code&gt;out=xxx&lt;/code&gt; argument specified, we mean the one function form, and whenever that argument
is not specified, we mean the other function form. This has the added benefit of making the output image argument
clearly recognizable.&lt;/p&gt;
&lt;p&gt;For example, where previously you could call &lt;code&gt;dip.Gauss&lt;/code&gt; as&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="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;Gauss&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;3&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="s2"&gt;&amp;quot;FIR&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;or&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="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;Gauss&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;sigmas&lt;/span&gt;&lt;span class="o"&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;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FIR&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;you can now also call it as&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="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;Image&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;Gauss&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;out&lt;/span&gt;&lt;span class="o"&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;sigmas&lt;/span&gt;&lt;span class="o"&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;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FIR&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;(I mean to say &lt;code&gt;sigmas&lt;/code&gt; and &lt;code&gt;method&lt;/code&gt; must be given as keyword arguments in this new form).&lt;/p&gt;
&lt;p&gt;Note, in that last call, that we didn&amp;rsquo;t need to allocate space for &lt;code&gt;out&lt;/code&gt;, we didn&amp;rsquo;t need to worry about how large
the output image would be, or what data type it would have. The DIPlib function will reallocate the output image
to match its needs. &lt;/p&gt;
&lt;h2&gt;Why bother?&lt;/h2&gt;
&lt;p&gt;The reason that I wanted to add this alternate, less user-friendly form of each function, is because of the
possibilities it opens up. The obvious advantage is that it allows re-using already allocated images, reducing
memory requirements, and possibly speed up some computations by working in-place. 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;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;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&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;works faster than&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="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;Abs&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;But the more important advantage is being able to determine the data type of the output. For this to work we
need &lt;a href="https://diplib.org/diplib-docs/dip-Image.html#protect"&gt;the &amp;ldquo;protect&amp;rdquo; flag&lt;/a&gt; (this works the same as in
the DIPlib C++ library of course). The &amp;ldquo;protect&amp;rdquo; flag protects an image from being reallocated. That is, if an
image is protected, its data segment cannot be freed or changed. This makes the image&amp;rsquo;s sizes and data type
fixed. So one could create, for example, complex-valued image, and use that as the output:&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Similar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SCOMPLEX&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Protect&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;Gauss&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;out&lt;/span&gt;&lt;span class="o"&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;sigmas&lt;/span&gt;&lt;span class="o"&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;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FIR&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;The image being protected, the &lt;code&gt;dip.Gauss&lt;/code&gt; function must use it as-is for its output, and so will produce,
in this case, a complex-valued result even if it would otherwise have produced a real-valued floating-point
result. Note that if the protected image were to have the wrong sizes or number of tensor elements, an
exception would be raised.&lt;/p&gt;
&lt;p&gt;To make this process simpler (i.e. letting the filter function do the allocation), DIPlib has another trick:
it is possible to protect a raw image (an image without a data segment). For such an image it is still
possible to change its sizes and number of tensor elements, and then allocate its data segment. But all
DIPlib functions will see the &amp;ldquo;protect&amp;rdquo; flag and not change the data type.
This allows the following code to work as expected:&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="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;Image&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetDataType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SCOMPLEX&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Protect&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;Gauss&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;out&lt;/span&gt;&lt;span class="o"&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;sigmas&lt;/span&gt;&lt;span class="o"&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;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FIR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;In-place processing&lt;/h2&gt;
&lt;p&gt;The following code might not work in place:&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;dip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Gauss&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;out&lt;/span&gt;&lt;span class="o"&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;sigmas&lt;/span&gt;&lt;span class="o"&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;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FIR&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;If &lt;code&gt;img&lt;/code&gt; is of an integer type, the output will be adjusted to be a floating-point type, and so will still create
a new output data segment. To force a function to work in place, protect 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;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Protect&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;Gauss&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;out&lt;/span&gt;&lt;span class="o"&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;sigmas&lt;/span&gt;&lt;span class="o"&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;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FIR&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;Some functions cannot work in place, they will always create an intermediate image to do the computation, maybe
by copying the input image so that it can be used as a separate output image. Some of the functions that cannot
work in place will raise an exception if the input and output images are the same. There will always be things
that could be improved!&lt;/p&gt;
&lt;p&gt;Let us know what you think of this new functionality!&lt;/p&gt;</content><category term="announcements"></category><category term="DIPlib"></category><category term="PyDIP"></category></entry></feed>