<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.2">Jekyll</generator><link href="https://pycache.de/feed.xml" rel="self" type="application/atom+xml" /><link href="https://pycache.de/" rel="alternate" type="text/html" /><updated>2025-11-03T14:06:03-06:00</updated><id>https://pycache.de/feed.xml</id><title type="html">pycache.de</title><subtitle>A weblog with Python, Linux, and physics.</subtitle><entry><title type="html">KeePassXC, ssh-agent, and KDE</title><link href="https://pycache.de/keepassxc_ssh-agent_kde/" rel="alternate" type="text/html" title="KeePassXC, ssh-agent, and KDE" /><published>2025-02-02T00:00:00-06:00</published><updated>2025-02-02T00:00:00-06:00</updated><id>https://pycache.de/keepassxc_ssh-agent_kde</id><content type="html" xml:base="https://pycache.de/keepassxc_ssh-agent_kde/"><![CDATA[<p><a href="https://github.com/keepassxreboot/keepassxc">KeePassXC</a> is a password manager.
I make heavy use of its browser integration in Firefox. And I also store
my private SSH keys (e.g. for git or deployment) in a .kdbx database.</p>

<p>KeePassXC has an SSH Agent integration. It communicates with the SSH Agent
via a socket that is created by the SSH Agent on startup. One great
feature of KeePassXC is the loading and unloading of SSH keys to and from
the SSH Agent, either manually or upon unlocking and locking of a .kdbx
database.</p>

<p>This setup worked well until I recently. After a system upgrade, KeePassXC
could not anymore remove keys from the SSH Agent.</p>

<h3 id="understand-the-problem">Understand the problem</h3>

<p>KeePassXC connects to the socket advertised in the environment variable
<code class="language-plaintext highlighter-rouge">SSH_AUTH_SOCK</code>. When looking at that variable, I noticed that the socket
(<code class="language-plaintext highlighter-rouge">/run/user/1000/gnupg/S.gpg-agent.ssh</code>) was created by the GPG-Agent. 
The <code class="language-plaintext highlighter-rouge">gpg-agent</code> service is a drop-in replacement for the <code class="language-plaintext highlighter-rouge">ssh-agent</code> service and it
<a href="https://github.com/keepassxreboot/keepassxc/blob/2.7.9/docs/topics/SSHAgent.adoc?plain=1#L29"><strong>does not support unloading SSH keys</strong></a>.</p>

<p>There we have it. KeePassXC was loading all of its keys to <code class="language-plaintext highlighter-rouge">gpg-agent</code>
which refused to forget them.</p>

<h3 id="remove-keys-from-pgp-agent">Remove keys from PGP-Agent</h3>

<p>PGP-Agent stores private keys in the <code class="language-plaintext highlighter-rouge">.gnupg/private-keys-v1.d/</code> directory.
These files are named <code class="language-plaintext highlighter-rouge">keygrip.key</code>. You can find the <em>keygrip</em> of the keys
you are using with GnuPG (for signing and encryption) with</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>paul@pycache:~<span class="nv">$ </span>gpg  <span class="nt">--list-secret-keys</span> <span class="nt">--with-keygrip</span>
/home/paul/.gnupg/pubring.kbx
<span class="nt">-----------------------------</span>
sec   rsa4096 2019-09-15 <span class="o">[</span>SC] <span class="o">[</span>expires: 2035-09-13]
      WG0OVWA1M0GPQDAWNPMTW67MEKD3NZAPK72SZ
      Keygrip <span class="o">=</span> 5SUUGGFI8U8SWEPBSPZ3NY82JFTE0NYV1PV3N
uid           <span class="o">[</span>ultimate] Paul Müller &lt;dev@pycache.de&gt;
ssb   rsa4096 2024-08-10 <span class="o">[</span>S] <span class="o">[</span>expires: 2034-08-08]
      Keygrip <span class="o">=</span> 8DGONGBBF7YH5K9NGNLVB1VQ7PWBHE637EDJC
</code></pre></div></div>

<p>Remove those keys that (are not listed here and) you want to use
with <code class="language-plaintext highlighter-rouge">ssh-agent</code>, managed by KeePassXC, from that private key directory.</p>

<h3 id="set-up-vanilla-ssh-agent">Set up vanilla <code class="language-plaintext highlighter-rouge">ssh-agent</code></h3>

<p>To replace <code class="language-plaintext highlighter-rouge">gpg-agent</code> with the vanilla <code class="language-plaintext highlighter-rouge">ssh-agent</code> (which supports unloading
keys), first make sure that you have <code class="language-plaintext highlighter-rouge">ssh-agent</code> installed. On Debian,
<code class="language-plaintext highlighter-rouge">ssh-agent</code> is included in the <code class="language-plaintext highlighter-rouge">openssh-client</code> package. You can test this
by starting a new instance of <code class="language-plaintext highlighter-rouge">ssh-agent</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>paul@pycache:~<span class="nv">$ </span><span class="nb">eval</span> <span class="sb">`</span>ssh-agent <span class="nt">-s</span><span class="sb">`</span>
Agent pid 64141
</code></pre></div></div>

<p>Why the <code class="language-plaintext highlighter-rouge">eval</code>? Because <code class="language-plaintext highlighter-rouge">ssh-agent</code> prints a script to <code class="language-plaintext highlighter-rouge">stdout</code> that
sets all necessary environment variables when executed. If you ran
just <code class="language-plaintext highlighter-rouge">ssh-agent -s</code>, you would get the following output:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SSH_AUTH_SOCK=/tmp/ssh-LiIlmPqRAPpA/agent.64140; export SSH_AUTH_SOCK;
SSH_AGENT_PID=64141; export SSH_AGENT_PID;
echo Agent pid 64141;
</code></pre></div></div>

<p>Now set the <code class="language-plaintext highlighter-rouge">SSH_AUTH_SOCK override</code> option in the KeePassXC settings to the
environment variable that you have in your current shell:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>paul@pycache:~<span class="nv">$ </span>print <span class="nv">$SSH_AUTH_SOCK</span>
/tmp/ssh-LiIlmPqRAPpA/agent.64140
</code></pre></div></div>

<p>Now you can use <code class="language-plaintext highlighter-rouge">ssh-add -l</code> in the same shell to list all private keys loaded.
Loading and unloading them in KeePassXC should now work here.</p>

<h3 id="setting-up-autostart-of-ssh-agent-for-kde-plasma">Setting up autostart of ssh-agent for KDE Plasma</h3>

<p>This is a slim approach to what <a href="https://gnulinux.ch/ssh-agent-unter-kde-plasma-automatisch-starten">Lioh Möller from gnulinux.ch</a>
(<a href="https://web.archive.org/web/20230326144223/https://gnulinux.ch/ssh-agent-unter-kde-plasma-automatisch-starten">archive.org</a>)
proposes. We don’t need to ask for passwords, since KeePassXC handles everything.</p>

<p>Simply create these files, log out and back in, and everything should work
fine with the default settings in KeePassXc.</p>

<p>Start the agent via <code class="language-plaintext highlighter-rouge">~/.config/plasma-workspace/env/ssh-agent-startup.sh</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/sh
[ -n "$SSH_AGENT_PID" ] || eval "$(ssh-agent -s)"
</code></pre></div></div>

<p>Stop the agent via <code class="language-plaintext highlighter-rouge">~/.config/plasma-workspace/shutdown/ssh-agent-shutdown.sh</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/sh
[ -z "$SSH_AGENT_PID" ] || eval "$(ssh-agent -k)"
</code></pre></div></div>

<p>And make sure those files are executable (<code class="language-plaintext highlighter-rouge">chmod +x path_to_script.sh</code>).</p>]]></content><author><name></name></author><category term="ssh" /><category term="private" /><category term="key" /><category term="management" /><summary type="html"><![CDATA[KeePassXC is a password manager. I make heavy use of its browser integration in Firefox. And I also store my private SSH keys (e.g. for git or deployment) in a .kdbx database.]]></summary></entry><entry><title type="html">Fourier transform reference</title><link href="https://pycache.de/fourier/" rel="alternate" type="text/html" title="Fourier transform reference" /><published>2019-01-10T00:00:00-06:00</published><updated>2019-01-10T00:00:00-06:00</updated><id>https://pycache.de/fourier</id><content type="html" xml:base="https://pycache.de/fourier/"><![CDATA[<p><a href="https://nbn-resolving.org/urn:nbn:de:bsz:14-qucosa2-879537">André Scholich</a>,
notwithstanding having mastered it, once jokingly said that the
<a href="https://en.wikipedia.org/wiki/Fourier_transform">Fourier transform</a>
must have come from hell. This collection of notes covers most of my
encounters with the Fourier transform - in the context of programming.</p>

<p>Contents:</p>
<ul id="markdown-toc">
  <li><a href="#basic-numpy-functionalities" id="markdown-toc-basic-numpy-functionalities">Basic NumPy functionalities</a></li>
  <li><a href="#time-shifting-the-first-pitfall" id="markdown-toc-time-shifting-the-first-pitfall">Time shifting: the first pitfall</a></li>
  <li><a href="#frequency-shifting" id="markdown-toc-frequency-shifting">Frequency shifting</a></li>
  <li><a href="#time-scaling" id="markdown-toc-time-scaling">Time scaling</a></li>
  <li><a href="#image-translation" id="markdown-toc-image-translation">Image translation</a></li>
  <li><a href="#image-modulation-holograms" id="markdown-toc-image-modulation-holograms">Image modulation: Holograms</a></li>
  <li><a href="#scaling-images" id="markdown-toc-scaling-images">Scaling images</a></li>
  <li><a href="#watermarks" id="markdown-toc-watermarks">Watermarks</a></li>
</ul>

<h3 id="basic-numpy-functionalities">Basic NumPy functionalities</h3>
<p><a href="https://www.numpy.org/">Numpy</a> is <em>the</em> basic library for scientific
programming in Python and it has its own implementation of the
<a href="https://en.wikipedia.org/wiki/Fast_Fourier_transform">fast Fourier transform (FFT) algorithm</a>.
A summary of all Fourier-related functions is given
in the <a href="https://docs.scipy.org/doc/numpy/reference/routines.fft.html">NumPy docs</a>.
Let me highlight the most essential functions here:</p>

<ul>
  <li><a href="https://docs.scipy.org/doc/numpy/reference/generated/numpy.fft.fft.html#numpy.fft.fft"><code class="language-plaintext highlighter-rouge">np.fft.fft</code></a>:
<em>Compute the one-dimensional discrete Fourier Transform.</em></li>
  <li><a href="https://docs.scipy.org/doc/numpy/reference/generated/numpy.fft.fftfreq.html#numpy.fft.fftfreq"><code class="language-plaintext highlighter-rouge">np.fft.fftfreq</code></a>:
<em>Return the Discrete Fourier Transform sample frequencies.</em>
This function is used to obtain the frequencies corresponding to the output
of <code class="language-plaintext highlighter-rouge">np.fft.fft</code> for data visualization and postprocessing purposes.</li>
  <li><a href="https://docs.scipy.org/doc/numpy/reference/generated/numpy.fft.fftshift.html#numpy.fft.fftshift"><code class="language-plaintext highlighter-rouge">np.fft.fftshift</code></a>:
<em>Shift the zero-frequency component to the center of the spectrum.</em>
By default, the zero-frequency component is the first element of the array
returned by <code class="language-plaintext highlighter-rouge">np.fft.fft</code> and negative frequencies are located in the
second half of the array. For data visualization, we need to have the
zero-frequency component at the center of the array. This function handles
odd- and even- length arrays correctly and should be used instead of manual
solutions.</li>
</ul>

<h3 id="time-shifting-the-first-pitfall">Time shifting: the first pitfall</h3>
<p>Let us attempt to perform the Fourier transform
of a Gaussian signal</p>

\[g(t) = \frac{1}{\sigma \sqrt{2 \pi}} e^{-\frac{1}{2} \left( \frac{t-\tau}{\sigma} \right)^2}.\]

<p>The Fourier transform of a Gaussian signal is also Gaussian, which makes it
easy to check the result.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">matplotlib.pylab</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">import</span> <span class="n">numpy</span> <span class="k">as</span> <span class="n">np</span>

<span class="c1"># Gaussian signal
# (parameters are chosen such that both signal and FT plot nicely)
</span><span class="n">N</span> <span class="o">=</span> <span class="mi">100</span>
<span class="n">time</span><span class="p">,</span> <span class="n">dt</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">linspace</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="n">N</span><span class="p">,</span> <span class="n">endpoint</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">retstep</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="n">sigma</span> <span class="o">=</span> <span class="p">.</span><span class="mi">25</span>
<span class="n">tau</span> <span class="o">=</span> <span class="mi">5</span>
<span class="n">sig</span> <span class="o">=</span> <span class="mi">1</span><span class="o">/</span><span class="p">(</span><span class="n">sigma</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="nf">sqrt</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">np</span><span class="p">.</span><span class="n">pi</span><span class="p">))</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="nf">exp</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="o">/</span><span class="mi">2</span> <span class="o">*</span> <span class="p">((</span><span class="n">time</span><span class="o">-</span><span class="n">tau</span><span class="p">)</span> <span class="o">/</span> <span class="n">sigma</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span><span class="p">)</span>

<span class="n">freq</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fftfreq</span><span class="p">(</span><span class="n">N</span><span class="p">,</span> <span class="n">dt</span><span class="p">)</span>
<span class="n">ft_sig</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fft</span><span class="p">(</span><span class="n">sig</span><span class="p">)</span>

<span class="n">fig</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="mi">3</span><span class="p">))</span>

<span class="n">ax1</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplot</span><span class="p">(</span><span class="mi">121</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">"signal"</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">time</span><span class="p">,</span> <span class="n">sig</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="nf">set_xlabel</span><span class="p">(</span><span class="s">"time $t$ [s]"</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="nf">set_ylabel</span><span class="p">(</span><span class="s">"amplitude $g$ [a.u.]"</span><span class="p">)</span>

<span class="n">ax2</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplot</span><span class="p">(</span><span class="mi">122</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">"Fourier transform"</span><span class="p">)</span>
<span class="n">ax2</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fftshift</span><span class="p">(</span><span class="n">freq</span><span class="p">),</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fftshift</span><span class="p">(</span><span class="n">ft_sig</span><span class="p">.</span><span class="n">real</span><span class="p">))</span>
<span class="n">ax2</span><span class="p">.</span><span class="nf">set_xlabel</span><span class="p">(</span><span class="s">"frequency $f$ [Hz]"</span><span class="p">)</span>
<span class="n">ax2</span><span class="p">.</span><span class="nf">set_ylabel</span><span class="p">(</span><span class="s">"amplitude $G$ [a.u.]"</span><span class="p">)</span>

<span class="n">plt</span><span class="p">.</span><span class="nf">tight_layout</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">savefig</span><span class="p">(</span><span class="s">"shift_pitfall.png"</span><span class="p">,</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">120</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">close</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="/assets/fourier/shift_pitfall.png" alt="the first pitfall" /></p>

<p>What happened? The Fourier transformed signal rapidly changes signs while
one could only make out a Gaussian envelope. To understand
what went wrong, we need to take a closer look at what <code class="language-plaintext highlighter-rouge">numpy.fft.fft</code>
actually does.</p>

<p>First, let us consider the continuous Fourier transform $G(f)$ of a signal $g(t)$,</p>

\[G(f) = \int_{-\infty}^{\infty} g(t) \cdot e^{-2\pi i ft}\,dt.\]

<p>In order to discretize this equation, we replace the integral by a sum of $N$
points, forcing us to reduce the integration interval from $(-\infty, \infty)$ to $(0, N)$.
Furthermore, we choose the substitutions $f \rightarrow k, k \in \Bbb N$ and $t \rightarrow n/N, n \in \Bbb N$,
which leads to the normalization $dt \rightarrow 1$. The discrete signals
are now described as $G_k = G(f_k)$ and $g_n = g(t_n)$.
The <a href="https://en.wikipedia.org/wiki/Discrete_Fourier_transform">discrete Fourier transform</a> 
can thus be written as</p>

\[G_k = \sum_{n=0}^{N-1} g_n \cdot e^{-\frac {2\pi i}{N} k n}.\]

<p>Note how the definition of $t=0$ has become $n=0$, which brings us back to
the original problem. The origin of $g_k$ is located at $k=0$ (not at
the center of the array $k = N/2$). Thus, in order to get the
Fourier transform of our Gaussian signal right, we would have to shift $g_k$ such
that its maximum is located at $k=0$ (the first element of the array).
We could achieve this by means of <code class="language-plaintext highlighter-rouge">np.fft.fftshift</code>, but that only works
as long as the center of $g$ coincides exactly with $n=N/2$.
A more elegant solution is to directly correct for the temporal shift $\tau$
after the Fourier transform. Let’s consider a shifted function $g(t-\tau)$
in the equation of the continuous Fourier transform:</p>

\[G(f) = \int_{-\infty}^{\infty} g(t-\tau) \cdot e^{-2\pi i ft}\,dt.\]

<p>We would like to get rid of the shift $\tau$ and thus substitute $t \rightarrow t + \tau$.</p>

\[G(f) = \int_{-\infty}^{\infty} g(t) \cdot e^{-2\pi i f(t+\tau)}\,dt.\]

<p>This step only affects the Fourier kernel and results in the additional term
$\exp (- 2 \pi i f \tau)$ which can be pulled out of the integral. This
oscillatory term is a simple
<a href="https://en.wikipedia.org/wiki/Fourier_transform#Translation_/_time_shifting">time shift</a>
and explains the artifacts in the figure above. We can correct
for this time shift by multiplying $G_k$ with $\exp (+ 2 \pi i f \tau)$:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># correct for time shift `tau`
</span><span class="n">ft_cor</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fft</span><span class="p">(</span><span class="n">sig</span><span class="p">)</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="nf">exp</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">np</span><span class="p">.</span><span class="n">pi</span><span class="o">*</span><span class="mf">1j</span><span class="o">*</span><span class="n">freq</span><span class="o">*</span><span class="n">tau</span><span class="p">)</span>

<span class="n">fig</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="mi">3</span><span class="p">))</span>

<span class="n">ax1</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplot</span><span class="p">(</span><span class="mi">121</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">"signal"</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">time</span><span class="p">,</span> <span class="n">sig</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="nf">set_xlabel</span><span class="p">(</span><span class="s">"time $t$ [s]"</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="nf">set_ylabel</span><span class="p">(</span><span class="s">"amplitude $g$ [a.u.]"</span><span class="p">)</span>

<span class="n">ax2</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplot</span><span class="p">(</span><span class="mi">122</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">"time-shift corrected Fourier transform"</span><span class="p">)</span>
<span class="n">ax2</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fftshift</span><span class="p">(</span><span class="n">freq</span><span class="p">),</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fftshift</span><span class="p">(</span><span class="n">ft_cor</span><span class="p">.</span><span class="n">real</span><span class="p">))</span>
<span class="n">ax2</span><span class="p">.</span><span class="nf">set_xlabel</span><span class="p">(</span><span class="s">"frequency $f$ [Hz]"</span><span class="p">)</span>
<span class="n">ax2</span><span class="p">.</span><span class="nf">set_ylabel</span><span class="p">(</span><span class="s">"amplitude $G$ [a.u.]"</span><span class="p">)</span>

<span class="n">plt</span><span class="p">.</span><span class="nf">tight_layout</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">savefig</span><span class="p">(</span><span class="s">"shift_corrected.png"</span><span class="p">,</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">120</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">close</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="/assets/fourier/shift_corrected.png" alt="time shift correction" /></p>

<p>Note that this correction works for <em>any</em> real-valued $\tau$ (as long as
the support of $g(t)$ is within the interval $0\,\text{s} &lt; t &lt; 10\,\text{s}$).</p>

<h3 id="frequency-shifting">Frequency shifting</h3>
<p>In some cases, it can be useful to manipulate a signal such that it
shows up at a predefined frequency in Fourier space. A frequency shift
can be described with</p>

\[G(f-f_0) = \int_{-\infty}^{\infty} g(t) \cdot e^{-2\pi i (f-f_0)t}\,dt.\]

<p>In other words, the signal $g(t)$ must be multiplied by the complex
exponential $\exp(-2\pi i f_0t)$ to shift its Fourier transform by $f_0$.
Here is an example for a shift by 2.2 Hz.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># multiply input with complex exponential
</span><span class="n">sig_shift</span> <span class="o">=</span> <span class="n">sig</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="nf">exp</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">np</span><span class="p">.</span><span class="n">pi</span><span class="o">*</span><span class="mf">1j</span><span class="o">*</span><span class="p">(</span><span class="n">time</span><span class="o">-</span><span class="n">tau</span><span class="p">)</span><span class="o">*</span><span class="mf">2.2</span><span class="p">)</span>
<span class="n">ft_shift</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fft</span><span class="p">(</span><span class="n">sig_shift</span><span class="p">)</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="nf">exp</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">np</span><span class="p">.</span><span class="n">pi</span><span class="o">*</span><span class="mf">1j</span><span class="o">*</span><span class="n">freq</span><span class="o">*</span><span class="n">tau</span><span class="p">)</span>

<span class="n">fig</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="mi">3</span><span class="p">))</span>

<span class="n">ax1</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplot</span><span class="p">(</span><span class="mi">121</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">"signal × complex exponential"</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">time</span><span class="p">,</span> <span class="n">sig_shift</span><span class="p">.</span><span class="n">real</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="nf">set_xlabel</span><span class="p">(</span><span class="s">"time $t$ [s]"</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="nf">set_ylabel</span><span class="p">(</span><span class="s">"amplitude $g$ [a.u.]"</span><span class="p">)</span>

<span class="n">ax2</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplot</span><span class="p">(</span><span class="mi">122</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">"frequency-shifted Fourier transform"</span><span class="p">)</span>
<span class="n">ax2</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fftshift</span><span class="p">(</span><span class="n">freq</span><span class="p">),</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fftshift</span><span class="p">(</span><span class="n">ft_shift</span><span class="p">.</span><span class="n">real</span><span class="p">))</span>
<span class="n">ax2</span><span class="p">.</span><span class="nf">set_xlabel</span><span class="p">(</span><span class="s">"frequency $f$ [Hz]"</span><span class="p">)</span>
<span class="n">ax2</span><span class="p">.</span><span class="nf">set_ylabel</span><span class="p">(</span><span class="s">"amplitude $G$ [a.u.]"</span><span class="p">)</span>

<span class="n">plt</span><span class="p">.</span><span class="nf">tight_layout</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">savefig</span><span class="p">(</span><span class="s">"shift_frequency.png"</span><span class="p">,</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">120</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">close</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="/assets/fourier/shift_frequency.png" alt="frequency shift" /></p>

<p>Note again that $\tau$ must be included to correctly shift the
frequency, hence the term <code class="language-plaintext highlighter-rouge">time-tau</code> in the complex exponential.</p>

<h3 id="time-scaling">Time scaling</h3>
<p>The time scaling property of the Fourier transform states that a change
of the sampling frequency in the input signal is equivalent to a scaled
signal in Fourier space.</p>

\[\frac{1}{a} G(f/a) = \int_{-\infty}^{\infty} g(at) \cdot e^{-2\pi i f t}\,dt\]

<p>In this example, the time axis is scaled by a factor of two, which leads
to a Fourier signal that is scaled by a factor of two and narrowed by a
factor of one half.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># scale by a factor of 2
</span><span class="n">freq_sc</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fftfreq</span><span class="p">(</span><span class="n">N</span><span class="p">,</span> <span class="n">dt</span><span class="o">/</span><span class="mi">2</span><span class="p">)</span>

<span class="n">time_sc</span> <span class="o">=</span> <span class="n">time</span> <span class="o">/</span> <span class="mi">2</span>
<span class="n">tau_sc</span> <span class="o">=</span> <span class="n">tau</span> <span class="o">/</span> <span class="mi">2</span>
<span class="n">sig_sc</span> <span class="o">=</span> <span class="mi">1</span><span class="o">/</span><span class="p">(</span><span class="n">sigma</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="nf">sqrt</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">np</span><span class="p">.</span><span class="n">pi</span><span class="p">))</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="nf">exp</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="o">/</span><span class="mi">2</span> <span class="o">*</span>
                                               <span class="p">((</span><span class="n">time_sc</span><span class="o">-</span><span class="n">tau_sc</span><span class="p">)</span> <span class="o">/</span> <span class="n">sigma</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span><span class="p">)</span>
<span class="n">ft_sc</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fft</span><span class="p">(</span><span class="n">sig_sc</span><span class="p">)</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="nf">exp</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">np</span><span class="p">.</span><span class="n">pi</span><span class="o">*</span><span class="mf">1j</span><span class="o">*</span><span class="n">freq_sc</span><span class="o">*</span><span class="n">tau_sc</span><span class="p">)</span>
<span class="n">ft_sc</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fftshift</span><span class="p">(</span><span class="n">ft_sc</span><span class="p">)</span>

<span class="n">freq</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fftshift</span><span class="p">(</span><span class="n">freq</span><span class="p">)</span>
<span class="n">freq_sc</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fftshift</span><span class="p">(</span><span class="n">freq_sc</span><span class="p">)</span>

<span class="n">fig</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="mi">3</span><span class="p">))</span>

<span class="n">ax1</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplot</span><span class="p">(</span><span class="mi">121</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">"time-scaled signal"</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">time</span><span class="p">,</span> <span class="n">sig_sc</span><span class="p">.</span><span class="n">real</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="nf">set_xlabel</span><span class="p">(</span><span class="s">"time $t$ [s]"</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="nf">set_ylabel</span><span class="p">(</span><span class="s">"amplitude $g$ [a.u.]"</span><span class="p">)</span>

<span class="n">ax2</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplot</span><span class="p">(</span><span class="mi">122</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">"scaled Fourier transform"</span><span class="p">)</span>
<span class="n">ax2</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">freq</span><span class="p">,</span> <span class="n">ft_sc</span><span class="p">.</span><span class="n">real</span><span class="p">)</span>
<span class="n">ax2</span><span class="p">.</span><span class="nf">set_xlabel</span><span class="p">(</span><span class="s">"frequency $f$ [Hz]"</span><span class="p">)</span>
<span class="n">ax2</span><span class="p">.</span><span class="nf">set_ylabel</span><span class="p">(</span><span class="s">"amplitude $G$ [a.u.]"</span><span class="p">)</span>

<span class="n">plt</span><span class="p">.</span><span class="nf">tight_layout</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">savefig</span><span class="p">(</span><span class="s">"scale_time.png"</span><span class="p">,</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">120</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">close</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="/assets/fourier/scale_time.png" alt="time scaling" /></p>

<h3 id="image-translation">Image translation</h3>
<p>Many applications of the Fourier transform involve image analysis.
It is possible to perform the trivial task of image translation with the
Fourier transform. If the image is translated by a non-integer amount of
pixels, then the interpolation takes place with the Fourier kernel
(sine and cosine functions).
For this example, we use a <a href="/assets/fourier/moon_small.png">downscaled image</a>
of the lunar eclipse, recorded on
<a href="https://en.wikipedia.org/wiki/July_2018_lunar_eclipse">July 27th 2018</a>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">matplotlib.image</span> <span class="k">as</span> <span class="n">mpimg</span>

<span class="n">moon</span> <span class="o">=</span> <span class="n">mpimg</span><span class="p">.</span><span class="nf">imread</span><span class="p">(</span><span class="s">"moon_small.png"</span><span class="p">)</span>
<span class="n">fy</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fftfreq</span><span class="p">(</span><span class="n">moon</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">]).</span><span class="nf">reshape</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">fx</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fftfreq</span><span class="p">(</span><span class="n">moon</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">1</span><span class="p">]).</span><span class="nf">reshape</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="n">ft_moon</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fft2</span><span class="p">(</span><span class="n">moon</span><span class="p">)</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="nf">exp</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">np</span><span class="p">.</span><span class="n">pi</span><span class="o">*</span><span class="mf">1j</span><span class="o">*</span><span class="p">(</span><span class="n">fx</span><span class="o">*</span><span class="mf">10.5</span> <span class="o">+</span> <span class="n">fy</span><span class="o">*</span><span class="mi">10</span><span class="p">))</span>
<span class="n">moon_tr</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">ifft2</span><span class="p">(</span><span class="n">ft_moon</span><span class="p">)</span>

<span class="n">fig</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="mf">3.6</span><span class="p">))</span>

<span class="n">ax1</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplot</span><span class="p">(</span><span class="mi">121</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">"moon"</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="nf">imshow</span><span class="p">(</span><span class="n">moon</span><span class="p">,</span> <span class="n">cmap</span><span class="o">=</span><span class="s">"gray"</span><span class="p">,</span> <span class="n">interpolation</span><span class="o">=</span><span class="s">"none"</span><span class="p">)</span>

<span class="n">ax2</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplot</span><span class="p">(</span><span class="mi">122</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">"translated moon"</span><span class="p">)</span>
<span class="n">ax2</span><span class="p">.</span><span class="nf">imshow</span><span class="p">(</span><span class="n">moon_tr</span><span class="p">.</span><span class="n">real</span><span class="p">,</span> <span class="n">cmap</span><span class="o">=</span><span class="s">"gray"</span><span class="p">,</span> <span class="n">interpolation</span><span class="o">=</span><span class="s">"none"</span><span class="p">)</span>

<span class="n">plt</span><span class="p">.</span><span class="nf">tight_layout</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">savefig</span><span class="p">(</span><span class="s">"moon_translated.png"</span><span class="p">,</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">120</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">close</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="/assets/fourier/moon_translated.png" alt="time scaling" /></p>

<p>The image is translated by 10 pixels along the y-axis and by 10.5 pixels along
the x-axis. The resulting interpolation along the x-axis leads to horizontal
ringing artifacts.</p>

<p>Performing image translation with the Fourier transform might be fast,
but for higher accuracy, other interpolation methods (e.g. splines)
might be better suited, especially when sharp boundaries (dark-bright) are
present.</p>

<h3 id="image-modulation-holograms">Image modulation: Holograms</h3>
<p>The Fourier transform can be used for the analysis of digital holograms.
In the life sciences,
<a href="https://en.wikipedia.org/wiki/Digital_holographic_microscopy">digital holographic imaging</a>
is used to quantify
the refractive index of cells. To achieve that, a laser beam is split into
two beams, one passes through the sample and the other serves as a reference.
When these two beams are brought back together at a slightly tilted angle,
they generate an interference pattern, periodic stripes that can be
recorded with a regular camera, that is modulated by the phase
delay introduced by the varying refractive index of the sample.</p>

<p>The <a href="/assets/fourier/cell_hologram.png">example hologram</a>
shows an HL60 cell - the intensity data clearly reveals a cell,
but we are after the phase data. The modulation of the phase data becomes
visible when tracing the interference pattern (dark stripes) through the
cell: they appear to be deformed at the cell boundary. This modulation
can be extracted with Fourier analysis. The interference pattern
can be described as a cosine function, whose Fourier transform are two
delta functions, the so-called sidebands. Isolating one of those sidebands
(see arrow in the image below) and performing an inverse Fourier transform,
reveals the part of the light that passed through the cell and the phase
delay can be computed.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">cell</span> <span class="o">=</span> <span class="n">mpimg</span><span class="p">.</span><span class="nf">imread</span><span class="p">(</span><span class="s">"cell_hologram.png"</span><span class="p">)</span>
<span class="n">ft_cell</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fft2</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
<span class="n">ft_cell_copy</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">copy</span><span class="p">(</span><span class="n">ft_cell</span><span class="p">)</span>
<span class="c1"># suppress central band
</span><span class="n">ft_cell</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="p">:]</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">ft_cell</span><span class="p">[:,</span> <span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
<span class="c1"># determine sideband position
</span><span class="n">xmax</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">argmax</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="nf">max</span><span class="p">(</span><span class="n">ft_cell</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">))</span>
<span class="n">ymax</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">argmax</span><span class="p">(</span><span class="n">ft_cell</span><span class="p">[</span><span class="n">xmax</span><span class="p">])</span>

<span class="c1"># move sideband to zero frequency
</span><span class="n">ft_cell_rolled</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">roll</span><span class="p">(</span><span class="n">ft_cell</span><span class="p">,</span> <span class="p">(</span><span class="o">-</span><span class="n">xmax</span><span class="p">,</span> <span class="o">-</span><span class="n">ymax</span><span class="p">),</span> <span class="n">axis</span><span class="o">=</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
<span class="c1"># apply sideband filter
</span><span class="n">ft_cell_rolled</span><span class="p">[</span><span class="mi">20</span><span class="p">:</span><span class="o">-</span><span class="mi">20</span><span class="p">,</span> <span class="p">:]</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">ft_cell_rolled</span><span class="p">[:,</span> <span class="mi">20</span><span class="p">:</span><span class="o">-</span><span class="mi">20</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
<span class="c1"># invert to get sideband modulation
</span><span class="n">modulation</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">ifft2</span><span class="p">(</span><span class="n">ft_cell_rolled</span><span class="p">)</span>
<span class="c1"># compute phase
</span><span class="n">phase</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">angle</span><span class="p">(</span><span class="n">modulation</span><span class="p">)</span>

<span class="n">fig</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="mf">2.8</span><span class="p">))</span>

<span class="n">ax1</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplot</span><span class="p">(</span><span class="mi">131</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">"hologram"</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="nf">imshow</span><span class="p">(</span><span class="n">cell</span><span class="p">.</span><span class="n">real</span><span class="p">,</span> <span class="n">cmap</span><span class="o">=</span><span class="s">"gray"</span><span class="p">,</span> <span class="n">interpolation</span><span class="o">=</span><span class="s">"bilinear"</span><span class="p">)</span>

<span class="n">ax2</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplot</span><span class="p">(</span><span class="mi">132</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">"Fourier transform"</span><span class="p">)</span>
<span class="n">ax2</span><span class="p">.</span><span class="nf">imshow</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fftshift</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="mi">1</span> <span class="o">+</span> <span class="n">np</span><span class="p">.</span><span class="nf">abs</span><span class="p">(</span><span class="n">ft_cell_copy</span><span class="p">))),</span>
           <span class="n">interpolation</span><span class="o">=</span><span class="s">"none"</span><span class="p">)</span>
<span class="n">ax2</span><span class="p">.</span><span class="nf">arrow</span><span class="p">(</span><span class="mi">37</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">32</span><span class="p">,</span> <span class="mi">11</span><span class="p">,</span> <span class="n">head_length</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">head_width</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">fc</span><span class="o">=</span><span class="s">'w'</span><span class="p">,</span> <span class="n">ec</span><span class="o">=</span><span class="s">'w'</span><span class="p">)</span>
<span class="n">ax3</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplot</span><span class="p">(</span><span class="mi">133</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">"wrapped phase"</span><span class="p">)</span>
<span class="n">ax3</span><span class="p">.</span><span class="nf">imshow</span><span class="p">(</span><span class="n">phase</span><span class="p">,</span> <span class="n">cmap</span><span class="o">=</span><span class="s">"coolwarm"</span><span class="p">,</span> <span class="n">interpolation</span><span class="o">=</span><span class="s">"none"</span><span class="p">)</span>

<span class="k">for</span> <span class="n">ax</span> <span class="ow">in</span> <span class="p">[</span><span class="n">ax1</span><span class="p">,</span> <span class="n">ax2</span><span class="p">,</span> <span class="n">ax3</span><span class="p">]:</span>
    <span class="n">ax</span><span class="p">.</span><span class="nf">set_xticks</span><span class="p">([])</span>
    <span class="n">ax</span><span class="p">.</span><span class="nf">set_yticks</span><span class="p">([])</span>

<span class="n">plt</span><span class="p">.</span><span class="nf">tight_layout</span><span class="p">(</span><span class="n">w_pad</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">pad</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">h_pad</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">savefig</span><span class="p">(</span><span class="s">"cell_modulation.png"</span><span class="p">,</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">120</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">close</span><span class="p">()</span>
</code></pre></div></div>
<p><img src="/assets/fourier/cell_modulation.png" alt="time scaling" /></p>

<p>Note that the phase is wrapped in the interval $(0, 2\pi)$, i.e. there are
$2\pi$ phase jumps (from red to blue) that have to be “unwrapped” for further analysis.</p>

<h3 id="scaling-images">Scaling images</h3>
<p>The Fourier transform can also be used to up- or downscale images. In the above example,
the inverse Fourier transform was performed for a much larger frequency space
than necessary, because we actually cropped the sideband to 40 by 40 pixels.
If we only take the inverse Fourier transform of the cropped sideband, we
get an idea of the actual image resolution. In short, upscaling with the
Fourier transform means that the image is interpolated with cosine
functions. On the other hand, downscaling with the Fourier transform means
that high-frequency contributions are omitted.
The illustration below additionally makes use of a
<a href="https://scikit-image.org/docs/dev/api/skimage.restoration.html#skimage.restoration.unwrap_phase">phase unwrapping algorithm</a>
that is part of the <a href="https://scikit-image.org/">scikit-image</a> library.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">skimage.restoration</span> <span class="kn">import</span> <span class="n">unwrap_phase</span>

<span class="n">ft_cell_low</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">zeros</span><span class="p">((</span><span class="mi">40</span><span class="p">,</span> <span class="mi">40</span><span class="p">),</span> <span class="n">dtype</span><span class="o">=</span><span class="nb">complex</span><span class="p">)</span>
<span class="n">ft_cell_low</span><span class="p">.</span><span class="n">flat</span><span class="p">[:]</span> <span class="o">=</span> <span class="n">ft_cell_rolled</span><span class="p">[</span><span class="n">ft_cell_rolled</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">]</span>
<span class="n">modulation_low</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">ifft2</span><span class="p">(</span><span class="n">ft_cell_low</span><span class="p">)</span>
<span class="c1"># compute phase
</span><span class="n">phase_low</span> <span class="o">=</span> <span class="nf">unwrap_phase</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="nf">angle</span><span class="p">(</span><span class="n">modulation_low</span><span class="p">))</span>
<span class="n">phase</span> <span class="o">=</span> <span class="nf">unwrap_phase</span><span class="p">(</span><span class="n">phase</span><span class="p">)</span>

<span class="n">fig</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="mf">3.6</span><span class="p">))</span>

<span class="n">ax1</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplot</span><span class="p">(</span><span class="mi">121</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">"unwrapped phase"</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="nf">imshow</span><span class="p">(</span><span class="n">phase</span><span class="p">,</span> <span class="n">cmap</span><span class="o">=</span><span class="s">"coolwarm"</span><span class="p">,</span> <span class="n">interpolation</span><span class="o">=</span><span class="s">"none"</span><span class="p">)</span>

<span class="n">ax2</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplot</span><span class="p">(</span><span class="mi">122</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">"unwrapped phase (actual resolution)"</span><span class="p">)</span>
<span class="n">ax2</span><span class="p">.</span><span class="nf">imshow</span><span class="p">(</span><span class="n">phase_low</span><span class="p">,</span> <span class="n">cmap</span><span class="o">=</span><span class="s">"coolwarm"</span><span class="p">,</span> <span class="n">interpolation</span><span class="o">=</span><span class="s">"none"</span><span class="p">)</span>

<span class="n">plt</span><span class="p">.</span><span class="nf">tight_layout</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">savefig</span><span class="p">(</span><span class="s">"cell_downsampled.png"</span><span class="p">,</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">120</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">close</span><span class="p">()</span>
</code></pre></div></div>
<p><img src="/assets/fourier/cell_downsampled.png" alt="time scaling" /></p>

<h3 id="watermarks">Watermarks</h3>
<p>A watermark is a modification of an image, often used to prevent (or track) the usage
of an image by others. Watermarks are usually just image overlays, but they
can also be hidden in Fourier space. Note that the modification in Fourier
space results in distortions, present everywhere in the image,
whose intensity depends on the number of
frequencies used and the corresponding amplitudes.
In this example, an <a href="/assets/fourier/moon.png">image of the lunar eclipse</a>
is watermarked with a <a href="/assets/fourier/smile.png">smiley</a>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">moon</span> <span class="o">=</span> <span class="n">mpimg</span><span class="p">.</span><span class="nf">imread</span><span class="p">(</span><span class="s">"moon.png"</span><span class="p">)</span>
<span class="n">fy</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fftfreq</span><span class="p">(</span><span class="n">moon</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">]).</span><span class="nf">reshape</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">fx</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fftfreq</span><span class="p">(</span><span class="n">moon</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">1</span><span class="p">]).</span><span class="nf">reshape</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="n">ft_moon</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fft2</span><span class="p">(</span><span class="n">moon</span><span class="p">)</span>
<span class="n">smile</span> <span class="o">=</span> <span class="n">mpimg</span><span class="p">.</span><span class="nf">imread</span><span class="p">(</span><span class="s">"smile.png"</span><span class="p">)</span>
<span class="n">smile_pad</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">zeros_like</span><span class="p">(</span><span class="n">moon</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="nb">complex</span><span class="p">)</span>
<span class="n">smile_pad</span><span class="p">[</span><span class="o">-</span><span class="p">(</span><span class="mi">20</span><span class="o">+</span><span class="n">smile</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">]):</span><span class="o">-</span><span class="mi">20</span><span class="p">,</span> <span class="o">-</span><span class="p">(</span><span class="mi">60</span><span class="o">+</span><span class="n">smile</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">1</span><span class="p">]):</span><span class="o">-</span><span class="mi">60</span><span class="p">]</span> <span class="o">=</span> <span class="n">smile</span>
<span class="n">moon_mark</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">ifft2</span><span class="p">(</span><span class="n">ft_moon</span> <span class="o">+</span> <span class="mi">10</span><span class="o">*</span><span class="n">smile_pad</span><span class="p">)</span>
<span class="n">ft_mark</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fft2</span><span class="p">(</span><span class="n">moon_mark</span><span class="p">.</span><span class="n">real</span><span class="p">)</span>

<span class="n">fig</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="mf">2.8</span><span class="p">))</span>

<span class="n">ax1</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplot</span><span class="p">(</span><span class="mi">131</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">"moon"</span><span class="p">)</span>
<span class="n">ax1</span><span class="p">.</span><span class="nf">imshow</span><span class="p">(</span><span class="n">moon</span><span class="p">,</span> <span class="n">cmap</span><span class="o">=</span><span class="s">"gray"</span><span class="p">,</span> <span class="n">interpolation</span><span class="o">=</span><span class="s">"none"</span><span class="p">)</span>

<span class="n">ax2</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplot</span><span class="p">(</span><span class="mi">132</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">"watermarked moon"</span><span class="p">)</span>
<span class="n">ax2</span><span class="p">.</span><span class="nf">imshow</span><span class="p">(</span><span class="n">moon_mark</span><span class="p">.</span><span class="n">real</span><span class="p">,</span> <span class="n">cmap</span><span class="o">=</span><span class="s">"gray"</span><span class="p">,</span> <span class="n">interpolation</span><span class="o">=</span><span class="s">"none"</span><span class="p">)</span>

<span class="n">ax3</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplot</span><span class="p">(</span><span class="mi">133</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s">"Fourier transform"</span><span class="p">)</span>
<span class="n">ax3</span><span class="p">.</span><span class="nf">imshow</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="mi">1</span> <span class="o">+</span> <span class="n">np</span><span class="p">.</span><span class="nf">abs</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">fft</span><span class="p">.</span><span class="nf">fftshift</span><span class="p">(</span><span class="n">ft_mark</span><span class="p">))),</span> <span class="n">interpolation</span><span class="o">=</span><span class="s">"none"</span><span class="p">)</span>
<span class="n">ax3</span><span class="p">.</span><span class="nf">arrow</span><span class="p">(</span><span class="mi">55</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="o">-</span><span class="mi">15</span><span class="p">,</span> <span class="mi">15</span><span class="p">,</span> <span class="n">head_length</span><span class="o">=</span><span class="mi">8</span><span class="p">,</span> <span class="n">head_width</span><span class="o">=</span><span class="mi">8</span><span class="p">,</span> <span class="n">fc</span><span class="o">=</span><span class="s">'w'</span><span class="p">,</span> <span class="n">ec</span><span class="o">=</span><span class="s">'w'</span><span class="p">)</span>

<span class="k">for</span> <span class="n">ax</span> <span class="ow">in</span> <span class="p">[</span><span class="n">ax1</span><span class="p">,</span> <span class="n">ax2</span><span class="p">,</span> <span class="n">ax3</span><span class="p">]:</span>
    <span class="n">ax</span><span class="p">.</span><span class="nf">set_xticks</span><span class="p">([])</span>
    <span class="n">ax</span><span class="p">.</span><span class="nf">set_yticks</span><span class="p">([])</span>

<span class="n">plt</span><span class="p">.</span><span class="nf">tight_layout</span><span class="p">(</span><span class="n">w_pad</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">pad</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">h_pad</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">savefig</span><span class="p">(</span><span class="s">"moon_watermark.png"</span><span class="p">,</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">120</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">close</span><span class="p">()</span>
</code></pre></div></div>
<p><img src="/assets/fourier/moon_watermark.png" alt="time scaling" /></p>

<p>Note that watermarks may also cover only a few pixels in Fourier space which
are then not as easily spotted as in the example above. Also note that depending on the
used frequencies of the watermark, scaling down the image might or might not remove the watermark.</p>]]></content><author><name></name></author><category term="fourier" /><category term="transform" /><summary type="html"><![CDATA[André Scholich, notwithstanding having mastered it, once jokingly said that the Fourier transform must have come from hell. This collection of notes covers most of my encounters with the Fourier transform - in the context of programming.]]></summary></entry><entry><title type="html">Casting poems into hexadecimal</title><link href="https://pycache.de/hex_lyrics/" rel="alternate" type="text/html" title="Casting poems into hexadecimal" /><published>2018-08-26T00:00:00-05:00</published><updated>2018-08-26T00:00:00-05:00</updated><id>https://pycache.de/hex_lyrics</id><content type="html" xml:base="https://pycache.de/hex_lyrics/"><![CDATA[<p>The <a href="https://en.wikipedia.org/wiki/Hexadecimal">hex numerical system</a>
is indispensable in modern computer art and science. It is used to
define colors, allows forensic data scientists to explore and visualize
binary files, helps you count up to 272 with the digits of your hands’
fingers, and yields highly descriptive representations of unicode characters that
<a href="https://duckduckgo.com/?q=UnicodeDecodeError%3A+%27ascii%27+codec+can%27t+decode+byte&amp;t=canonical&amp;ia=qa">cannot be decoded with ASCII</a>. 
All the more should it be a priority to establish it in the artistic métiers;
in other words: as a new poetic form.</p>

<p>As it is with <a href="https://en.wikipedia.org/wiki/Elevenie">elevenies</a> and
<a href="https://en.wikipedia.org/wiki/Haiku">haiku</a>, the hexadecimal poem must,
in contrast to <a href="https://daniel.haxx.se/hexpoetry/goodies.html">previous attempts</a>,
obey a certain set of rules:</p>

<ol>
  <li>It must consist of 16 lines with 16 ASCII characters each.
If non-standard ASCII characters are used, an extended
character set may be employed, e.g.
<a href="https://www.ascii-codes.com/cp850.html#extended_character_set">Latin-1</a>.</li>
  <li>It must be formatted as common hex editors would:
Each line consists of three parts that are separated by four spaces.
The first part enumerates the number of ASCII characters of the poem
at the end of the line with a hexadecimal number as an eight-character
string (leading zeros). The second part of the line consists of the
hexadecimal representation of the poem’s ASCII characters, separated
by single spaces. The last part of the line consists of the decoded,
human-readable characters.</li>
  <li>The title (if applicable) is the first line of the poem.</li>
</ol>

<p>Here are two examples. Number one will help you get started with Python.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>00000010    72 69 66 65 20 6d 61 6c 70 72 61 63 74 69 63 65    rife malpractice
00000020    6e 65 77 63 6f 6d 65 72 20 73 6e 61 6b 69 73 74    newcomer snakist
00000030    64 65 6c 69 63 61 74 65 20 68 65 69 67 68 74 73    delicate heights
00000040    73 65 65 6d 69 6e 67 6c 79 20 62 6c 69 67 68 74    seemingly blight
00000050    6e 6f 74 65 73 20 77 65 6e 74 20 61 6d 69 73 73    notes went amiss
00000060    63 61 74 20 6d 61 64 65 20 6d 65 20 74 68 69 73    cat made me this
00000070    62 69 72 64 73 20 74 68 65 72 65 20 72 65 73 74    birds there rest
00000080    63 61 73 65 73 20 77 6f 6e 27 74 20 6d 65 73 73    cases won't mess
00000090    6d 75 74 61 62 6c 65 20 73 74 61 6e 64 61 72 64    mutable standard
000000A0    6d 79 73 74 69 63 61 6c 20 63 6f 6e 64 75 63 74    mystical conduct
000000B0    73 63 6f 70 65 20 61 6c 74 65 72 61 74 69 6f 6e    scope alteration
000000C0    77 6f 72 6c 64 20 64 6f 6d 69 6e 61 74 69 6f 6e    world domination
000000D0    6e 6f 62 6c 65 20 63 68 61 6e 67 65 6c 69 6e 67    noble changeling
000000E0    69 6e 68 65 72 69 74 65 64 20 70 69 70 70 69 6e    inherited pippin
000000F0    66 61 74 68 6f 6d 6c 65 73 73 20 70 72 69 6e 74    fathomless print
00000100    66 6f 72 20 58 65 72 6f 78 20 61 20 68 69 6e 74    for Xerox a hint
</code></pre></div></div>

<p>The second poem is a translation of
<a href="http://memory-alpha.wikia.com/wiki/Ode_to_Spot">Ode to Spot</a> to hexadecimal.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>00000010    68 65 78 61 64 65 63 69 6d 61 6c 20 73 70 6f 74    hexadecimal spot
00000020    65 63 63 65 20 66 65 6c 69 73 20 63 61 74 75 73    ecce felis catus
00000030    63 61 72 6e 69 76 6f 72 65 20 73 74 61 74 75 73    carnivore status
00000040    65 73 74 68 65 73 69 61 20 66 6f 63 75 73 65 64    esthesia focused
00000050    6e 61 74 75 72 65 20 6d 6f 6d 65 6e 74 6f 75 73    nature momentous
00000060    69 6e 66 72 61 73 6f 75 6e 64 20 77 6f 72 64 73    infrasound words
00000070    6a 6f 79 6f 75 73 20 64 69 73 63 6f 75 72 73 65    joyous discourse
00000080    61 6c 74 72 75 69 73 74 69 63 20 73 68 69 6e 65    altruistic shine
00000090    6e 65 65 64 20 73 74 72 6f 6b 65 20 6d 69 6e 65    need stroke mine
000000A0    63 6f 6e 73 74 69 74 75 65 6e 74 20 74 61 69 6c    constituent tail
000000B0    62 61 6c 61 6e 63 65 20 70 72 65 76 61 69 6c 73    balance prevails
000000C0    66 65 65 6c 69 6e 67 20 63 6f 6e 76 65 79 65 64    feeling conveyed
000000D0    67 6f 72 67 65 6f 75 73 20 64 69 73 70 6c 61 79    gorgeous display
000000E0    70 65 72 63 65 70 74 75 61 6c 20 61 72 72 61 79    perceptual array
000000F0    63 61 6e 27 74 20 63 6f 6d 70 72 65 68 65 6e 64    can't comprehend
00000100    73 75 63 68 20 74 72 75 65 20 66 72 69 65 6e 64    such true friend
</code></pre></div></div>

<p>You may download these poems as binaries
<a href="/assets/hex_lyrics/rife_malpractice.bin">here</a> and
<a href="/assets/hex_lyrics/hexadecimal_spot.bin">here</a>.
If you would like to get started yourself, you could use the
<a href="/assets/hex_lyrics/poem_to_hex.py">example script that I used</a>.</p>]]></content><author><name></name></author><category term="art" /><category term="hex" /><summary type="html"><![CDATA[The hex numerical system is indispensable in modern computer art and science. It is used to define colors, allows forensic data scientists to explore and visualize binary files, helps you count up to 272 with the digits of your hands’ fingers, and yields highly descriptive representations of unicode characters that cannot be decoded with ASCII. All the more should it be a priority to establish it in the artistic métiers; in other words: as a new poetic form.]]></summary></entry><entry><title type="html">An artistic tomographic image filter</title><link href="https://pycache.de/tm_art_filter/" rel="alternate" type="text/html" title="An artistic tomographic image filter" /><published>2018-04-22T00:00:00-05:00</published><updated>2018-04-22T00:00:00-05:00</updated><id>https://pycache.de/tm_art_filter</id><content type="html" xml:base="https://pycache.de/tm_art_filter/"><![CDATA[<p><a href="https://en.wikipedia.org/wiki/CT_scan">Computerized tomography (CT)</a>
and related techniques such as
<a href="https://en.wikipedia.org/wiki/Positron_emission_tomography">PET</a> or
<a href="https://en.wikipedia.org/wiki/Magnetic_resonance_imaging">MRI</a> are
very common tools in medical imaging.
They allow to resolve the 3D structure of living tissues.
Tomography itself is divided into two processing steps.
First, projection images are recorded for several angles, the result of
which is called a sinogram.
Second, this sinogram is used to reconstruct a 3D
representation of the original object.
In the most simple case, the object (or
detector) rotation is performed only about one axis, which means that the 3D
reconstruction from 2D images can be broken down to several 2D slice
reconstructions from 1D line scans.
For CT, the sinogram consists of x-ray absorption images:
Bone tissue absorbs x-ray radiation and thus bones appear white on
the developed photographic x-ray film.
A PET image visualizes the radiation of radioactive tracers linked
to biological molecules that accumulate in the targeted tissue.
In MRI, image contrast is computed from the
time-dependent magnetic response of tissues to
strong dynamic magnetic fields.
The main problem to solve in tomographic imaging is the
reconstruction step, i.e. the ill-posed <a href="https://en.wikipedia.org/wiki/Radon_transform#Radon_inversion_formula">inversion of the
Radon transform</a>.
While nowadays there exist iterative approaches that take into account
prior knowledge about the imaged sample, the most beautiful reconstruction
artifacts can be achieved with the classical backpropagation algorithm.
In general, the quality of the 3D reconstruction in CT-like imaging
depends on the number of recorded images (the more the better) and on
the angular coverage (below 180° only partial coverage can be achieved).
The artistic tomographic image filter enforces low reconstruction quality
by addressing such aspects.</p>

<h3 id="filter-operation">Filter operation</h3>
<p>The basic mode of operation of the artistic tomographic filter is to simulate
the sinogram acquisition process and to reconstruct the original image
from the simulated sinogram.
Here is an example that illustrates the steps from
original image to sinogram image to reconstructed image with 256 angles
(deliberately matching the image dimensions 256x256 px for visualization purposes):</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center"><img src="/assets/tm_art_filter/disks.png" alt="" /></th>
      <th style="text-align: center"><img src="/assets/tm_art_filter/disks_sino.png" alt="" /></th>
      <th style="text-align: center"><img src="/assets/tm_art_filter/disks_tf_nang-256.png" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">original</td>
      <td style="text-align: center">sinogram</td>
      <td style="text-align: center">reconstruction</td>
    </tr>
  </tbody>
</table>

<p>As mentioned above, the actual artistic potential of the filter
will only be released, if the number of angles in the sinogram used
for the reconstruction is reduced:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center"><img src="/assets/tm_art_filter/disks_tf_nang-5.png" alt="" /></th>
      <th style="text-align: center"><img src="/assets/tm_art_filter/disks_tf_nang-10.png" alt="" /></th>
      <th style="text-align: center"><img src="/assets/tm_art_filter/disks_tf_nang-20.png" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">5 angles</td>
      <td style="text-align: center">10 angles</td>
      <td style="text-align: center">20 angles</td>
    </tr>
  </tbody>
</table>

<p>The resulting artifacts consist of streaks through the image that, when
many of them intersect, reproduce the original image content.
The filter can be modified in several different ways, some of which are
covered further below.</p>

<h3 id="python-setup">Python setup</h3>
<p>The artistic tomographic filter uses the backprojection
algorithm I implemented in the Python library
<a href="https://github.com/RI-imaging/radontea">radontea</a>.
Radontea also comes with an implementation of the Radon transform which
is used to compute the sinogram from the input image. For loading and
writing images, <a href="https://imageio.github.io/">imageio</a> is used.
For Python 3, imageio and radontea can be installed from the Python package index via
<code class="language-plaintext highlighter-rouge">pip install imageio radontea</code>.</p>

<p>To use the filter, download <a href="/assets/tm_art_filter/tomographic_filter.py">tomographic_filter.py</a>
and execute it like so:</p>

<p><code class="language-plaintext highlighter-rouge">python tomographic_filter.py input_image.jpg</code></p>

<p>This will produce a new image <code class="language-plaintext highlighter-rouge">input_image_tf.jpg</code>, generated with
the standard filtering parameters.</p>

<h3 id="tweak-and-tune">Tweak and tune</h3>
<p>The <em>tomographic_filter.py</em> script comes with a convenient
command line interface that allows to tune several parameters,
from Radon transform to backprojection, affecting the
artistic character of the filter.</p>

<p>By default, the filter uses 37 equally spaced angles with a full angular
coverage (from 0° to 180°).</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center"><img src="/assets/tm_art_filter/leaf_lr.jpg" alt="" /></th>
      <th style="text-align: center"><img src="/assets/tm_art_filter/leaf_lr_tf.jpg" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">original <a href="/assets/tm_art_filter/leaf.jpg">[highres]</a></td>
      <td style="text-align: center">filter with defaults <a href="/assets/tm_art_filter/leaf_tf.jpg">[highres]</a></td>
    </tr>
  </tbody>
</table>

<p>The angular coverage can be modified with the arguments <code class="language-plaintext highlighter-rouge">--cov-min</code> (default 0) and <code class="language-plaintext highlighter-rouge">--cov-max</code> (default 180),
leading to so-called <em>missing-angle artifacts</em>.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center"><img src="/assets/tm_art_filter/puncher_black_lr.jpg" alt="" /></th>
      <th style="text-align: center"><img src="/assets/tm_art_filter/puncher_black_lr_tf.jpg" alt="" /></th>
      <th style="text-align: center"><img src="/assets/tm_art_filter/puncher_black_lr_tf_ival-0-120.0.jpg" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">original <a href="/assets/tm_art_filter/puncher_black.jpg">[highres]</a></td>
      <td style="text-align: center">defaults <a href="/assets/tm_art_filter/puncher_black_tf.jpg">[highres]</a></td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">--cov-max 120</code> <a href="/assets/tm_art_filter/puncher_black_tf_ival-0-120.0.jpg">[highres]</a></td>
    </tr>
  </tbody>
</table>

<p>To achieve a more chaotic behavior, the angles can be distributed randomly using the
<code class="language-plaintext highlighter-rouge">--randomness</code> argument. To improve the recognizability of the object, <code class="language-plaintext highlighter-rouge">--weight-angles</code> can
be combined with randomly distributed angles.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center"><img src="/assets/tm_art_filter/puncher_brown_lr.jpg" alt="" /></th>
      <th style="text-align: center"><img src="/assets/tm_art_filter/puncher_brown_lr_tf_rand-47.jpg" alt="" /></th>
      <th style="text-align: center"><img src="/assets/tm_art_filter/puncher_brown_lr_tf_weight-True_rand-47.jpg" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">original <a href="/assets/tm_art_filter/puncher_brown.jpg">[highres]</a></td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">--randomness 47</code> <a href="/assets/tm_art_filter/puncher_brown_tf_rand-47.jpg">[highres]</a></td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">--randomness 47</code> and <code class="language-plaintext highlighter-rouge">--weight-angles</code> <a href="/assets/tm_art_filter/puncher_brown_tf_weight-True_rand-47.jpg">[highres]</a></td>
    </tr>
  </tbody>
</table>

<p>Random distribution of angles can also be combined with partial angular coverage.
The choice of angles affects the angular positions of the streaks,
sometimes resulting in exaggerated edge contrast.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center"><img src="/assets/tm_art_filter/puncher_white_lr.jpg" alt="" /></th>
      <th style="text-align: center"><img src="/assets/tm_art_filter/puncher_white_lr_tf_ival-30.0-160.0_rand-8472.jpg" alt="" /></th>
      <th style="text-align: center"><img src="/assets/tm_art_filter/puncher_white_lr_tf_ival-70.0-180_rand-8472.jpg" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">original <a href="/assets/tm_art_filter/puncher_white.jpg">[highres]</a></td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">--randomness 8472</code>, <code class="language-plaintext highlighter-rouge">--cov-min 20</code>, and <code class="language-plaintext highlighter-rouge">--cov-max 160</code> <a href="/assets/tm_art_filter/puncher_white_tf_ival-30.0-160.0_rand-8472.jpg">[highres]</a></td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">--randomness 8472</code> and <code class="language-plaintext highlighter-rouge">--cov-min 70</code> <a href="/assets/tm_art_filter/puncher_white_tf_ival-70.0-180_rand-8472.jpg">[highres]</a></td>
    </tr>
  </tbody>
</table>

<p>For bright images (black images would have to be inverted first), it might also
be worthwhile to disable the color normalization prior to computing the sinogram.
This produces a ring-shaped artifact around the image.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center"><img src="/assets/tm_art_filter/disks_tf_nang-20_norm-False.png" alt="" /></th>
      <th style="text-align: center"><img src="/assets/tm_art_filter/leaf_lr_tf_norm-False.jpg" alt="" /></th>
      <th style="text-align: center"><img src="/assets/tm_art_filter/puncher_white_lr_tf_weight-True_rand-42_norm-False.jpg" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">--no-normalize</code> and <code class="language-plaintext highlighter-rouge">--num-angles 20</code></td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">--no-normalize</code> <a href="/assets/tm_art_filter/leaf_tf_norm-False.jpg">[highres]</a></td>
      <td style="text-align: center"><code class="language-plaintext highlighter-rouge">--no-normalize</code>, <code class="language-plaintext highlighter-rouge">--randomness 42</code> and <code class="language-plaintext highlighter-rouge">--weight-angles</code> <a href="/assets/tm_art_filter/puncher_white_tf_weight-True_rand-42_norm-False.jpg">[highres]</a></td>
    </tr>
  </tbody>
</table>

<h3 id="video-generation">Video generation</h3>
<p>The <code class="language-plaintext highlighter-rouge">--offset</code> option allows to change the offset of the angles used.
The following script imports <em>tomographic_filter.py</em> as a module
and generates a series of 180 filtered images at a 1°-spacing.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">tomographic_filter</span>

<span class="n">path_in</span> <span class="o">=</span> <span class="s">"crow.jpg"</span>

<span class="k">for</span> <span class="n">ii</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">180</span><span class="p">):</span>
    <span class="n">path_out</span> <span class="o">=</span> <span class="s">"crow_tf_{:03d}.png"</span><span class="p">.</span><span class="nf">format</span><span class="p">(</span><span class="n">ii</span><span class="p">)</span>
    <span class="n">tomographic_filter</span><span class="p">.</span><span class="nf">tomographic_filter</span><span class="p">(</span><span class="n">path_in</span><span class="o">=</span><span class="n">path_in</span><span class="p">,</span>
                                          <span class="n">path_out</span><span class="o">=</span><span class="n">path_out</span><span class="p">,</span>
                                          <span class="n">angle_offset</span><span class="o">=</span><span class="n">ii</span><span class="p">,</span>
                                          <span class="n">randomness</span><span class="o">=</span><span class="mi">42</span><span class="p">,</span>
                                          <span class="n">num_angles</span><span class="o">=</span><span class="mi">47</span><span class="p">,</span>
                                          <span class="n">weight_angles</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
</code></pre></div></div>

<p>The resulting png files can be converted to a
browser-compatible video using avconv/ffmpeg:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>avconv -r 15 -i crow_tf_%03d.png -c:v vp8 -b:v 10M crow.webm
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th style="text-align: center"><img src="/assets/tm_art_filter/crow.jpg" alt="" /></th>
      <th style="text-align: center"><video width="100%" controls="" loop="" preload="metadata" src="/assets/tm_art_filter/crow.webm" type="video/webm"></video></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">original</td>
      <td style="text-align: center">offset series video</td>
    </tr>
  </tbody>
</table>

<p>Angular offsets are also a nice way of visualizing missing-angle artifacts.
Recognizability of the original image often depends on the angular range
covered by the sinogram.
In the next example, the keyword arguments
<code class="language-plaintext highlighter-rouge">ival_coverage=(0,100)</code> and <code class="language-plaintext highlighter-rouge">normalize=False</code> were used.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center"><img src="/assets/tm_art_filter/duck.jpg" alt="" /></th>
      <th style="text-align: center"><video width="100%" controls="" loop="" preload="metadata" src="/assets/tm_art_filter/duck.webm" type="video/webm"></video></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">original</td>
      <td style="text-align: center">offset series video</td>
    </tr>
  </tbody>
</table>

<h3 id="play">Play!</h3>
<p>The examples shown here do not exhaust the full capability of the tomographic filter.
Besides the various combinations of the filter parameters given,
<a href="http://radontea.readthedocs.io/en/stable/code_reference.html#radontea.backproject">other parameters</a>
could be used, such as <code class="language-plaintext highlighter-rouge">padding</code> or <code class="language-plaintext highlighter-rouge">filtering</code>. In addition, a different reconstruction
algorithm, such as Fourier domain mapping could be used. A qualitative
comparison of the available algorithms can be found at the
<a href="http://radontea.readthedocs.io/en/stable/examples.html#comparison-of-parallel-beam-reconstruction-methods">radontea docs</a>.</p>

<h3 id="tldr">TL;DR</h3>
<p>Install Python 3, <code class="language-plaintext highlighter-rouge">pip install imageio radontea</code>, download 
<a href="/assets/tm_art_filter/tomographic_filter.py">tomographic_filter.py</a>,
and execute it, passing an image path as an argument.</p>

<p><code class="language-plaintext highlighter-rouge">python tomographic_filter.py /path/to/image.jpg</code></p>

<p>For a list of possible command-line arguments, use <code class="language-plaintext highlighter-rouge">--help</code>.</p>

<p><code class="language-plaintext highlighter-rouge">python tomographic_filter.py --help</code>.</p>]]></content><author><name></name></author><category term="art" /><category term="tomography" /><summary type="html"><![CDATA[Computerized tomography (CT) and related techniques such as PET or MRI are very common tools in medical imaging. They allow to resolve the 3D structure of living tissues. Tomography itself is divided into two processing steps. First, projection images are recorded for several angles, the result of which is called a sinogram. Second, this sinogram is used to reconstruct a 3D representation of the original object. In the most simple case, the object (or detector) rotation is performed only about one axis, which means that the 3D reconstruction from 2D images can be broken down to several 2D slice reconstructions from 1D line scans. For CT, the sinogram consists of x-ray absorption images: Bone tissue absorbs x-ray radiation and thus bones appear white on the developed photographic x-ray film. A PET image visualizes the radiation of radioactive tracers linked to biological molecules that accumulate in the targeted tissue. In MRI, image contrast is computed from the time-dependent magnetic response of tissues to strong dynamic magnetic fields. The main problem to solve in tomographic imaging is the reconstruction step, i.e. the ill-posed inversion of the Radon transform. While nowadays there exist iterative approaches that take into account prior knowledge about the imaged sample, the most beautiful reconstruction artifacts can be achieved with the classical backpropagation algorithm. In general, the quality of the 3D reconstruction in CT-like imaging depends on the number of recorded images (the more the better) and on the angular coverage (below 180° only partial coverage can be achieved). The artistic tomographic image filter enforces low reconstruction quality by addressing such aspects.]]></summary></entry></feed>