<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="https://konrad.earth/feed_style.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <tabi:metadata xmlns:tabi="https://github.com/welpo/tabi">
        <tabi:base_url>https:&#x2F;&#x2F;konrad.earth</tabi:base_url>
        <tabi:separator>
            •
        </tabi:separator>
        <tabi:about_feeds>This is a web feed, also known as an Atom feed. Subscribe by copying the URL from the address bar into your newsreader. Visit About Feeds to learn more and get started. It&#x27;s free.</tabi:about_feeds>
        <tabi:visit_the_site>Visit website</tabi:visit_the_site>
        <tabi:recent_posts>Recent posts</tabi:recent_posts>
        <tabi:last_updated_on>Updated on $DATE</tabi:last_updated_on>
        <tabi:default_theme></tabi:default_theme>
        <tabi:post_listing_date>date</tabi:post_listing_date>
        <tabi:current_section>konrad.earth</tabi:current_section>
    </tabi:metadata><title>konrad.earth</title>
        <subtitle>Konrad Heidler</subtitle>
    <link href="https://konrad.earth/atom.xml" rel="self" type="application/atom+xml"/>
    <link href="https://konrad.earth" rel="alternate" type="text/html"/>
    <generator uri="https://www.getzola.org/">Zola</generator><updated>2025-05-26T00:00:00+00:00</updated><id>https://konrad.earth/atom.xml</id><entry xml:lang="en">
        <title>Japan Shader</title>
        <published>2025-05-26T00:00:00+00:00</published>
        <updated>2025-05-26T00:00:00+00:00</updated>
        <author>
            <name>Konrad Heidler</name>
        </author>
        <link rel="alternate" href="https://konrad.earth/blog/japan-shader/" type="text/html"/>
        <id>https://konrad.earth/blog/japan-shader/</id>
        
            <content type="html">&lt;figure &gt;
    
    &lt;img src=photo.jpg &#x2F;&gt;
    &lt;figcaption&gt;
      
      &lt;p&gt;
         Mountains fading out toward the horizon. This is what we’re looking to re-create in a shader. Photo taken from Oyunohara View Point, Wakayama Prefecture 
        
        
        
      &lt;&#x2F;p&gt;
    &lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;The mountains in traditional japanese art prints, ridge behind ridge, each one paler than the last until they finally fade into the sky, always struck me as a stylistic choice.
A way of flattening distance onto the canvas.
Then I walked the Kumano Kodo, on the Kii Peninsula south of Osaka, and found out that these paintings are much closer to reality than I thought they were.
Near ridges dark green. The next one blue-grey. Four or five more behind that, each fainter, the last barely separable from the sky.
Dense folded ranges, a lot of humidity, and atmospheric scattering does the rest.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.openpv.de&#x2F;&quot;&gt;Having worked with elevation models and ray marching before&lt;&#x2F;a&gt;, I figured it would be an interesting challenge to try and recreate this prorammatically.
Not a static image, but an interactive applet where you can pick a point on the map and have the mountain ranges laid out before you, in the traditional Japanese art style.
So before we dive into how this is done, enjoy some dynamically rendered Japanese mountains, drawn live by your GPU:&lt;&#x2F;p&gt;
&lt;div class=&quot;applet&quot;&gt;
  &lt;iframe src=https:&amp;#x2F;&amp;#x2F;maps.heidler.info&amp;#x2F;japan-shader allow=&quot;fullscreen&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
  
    &lt;button
      type=&quot;button&quot;
      class=&quot;fullscreen&quot;
      aria-label=&quot;Open applet fullscreen&quot;
      title=&quot;Fullscreen&quot;
    &gt;
      &lt;svg viewBox=&quot;0 0 24 24&quot; aria-hidden=&quot;true&quot;&gt;
        &lt;path
          d=&quot;M5 9V5h4M15 5h4v4M19 15v4h-4M9 19H5v-4&quot;
          fill=&quot;none&quot;
          stroke=&quot;currentColor&quot;
          stroke-width=&quot;2&quot;
          stroke-linecap=&quot;round&quot;
        &#x2F;&gt;
      &lt;&#x2F;svg&gt;
    &lt;&#x2F;button&gt;
  
&lt;&#x2F;div&gt;
&lt;h1 id=&quot;the-shader&quot;&gt;The shader&lt;&#x2F;h1&gt;
&lt;p&gt;The core of this app is the WebGL shader.
For each pixel on the screen, it marches along the corresponding ray in 3d space and checks whether the ray intersects with the terrain at any point.
The ray marching loop is quite simple:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;glsl&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;for&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; t &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;&amp;lt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 15.&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;e&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;4&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; t &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;*=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 1.05&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;  vec3 check &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; x0 &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; t &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; dx&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;  alpha &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;*=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0.992&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;  float&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; h &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; height&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;check&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;xy&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;h &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; check&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;z&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; ||&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; h &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 5000.&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    break&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At each step it samples the digital elevation model (DEM) texture.
If the sampled terrain height is higher than the current ray height, the ray has hit something.
If it does not hit anything within about 150 km, we stop marching and treat this pixel as sky.&lt;&#x2F;p&gt;
&lt;p&gt;Now there is no actual lighting simulation or anything done in the shader.
Instead, the &lt;code&gt;alpha&lt;&#x2F;code&gt; variable starts at &lt;code&gt;1.0&lt;&#x2F;code&gt; and decays as the ray flies through space.
The later the ray hits the terrain, the less &lt;code&gt;alpha&lt;&#x2F;code&gt; is left.
For near terrain, we colour the pixel dark green, while farther terrain shifts toward blue, and long unobstructed rays fade toward a pale grey sky:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;glsl&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;if&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;alpha &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0.5&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;  fragColor &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; mix&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;sky&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; blue&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; alpha &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 2.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; else&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;  fragColor &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; mix&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;blue&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; green&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; alpha &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 2.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That is why the output feels more like a painting than a shaded relief map.
The visible shapes are controlled by real topography, but the color ramp is chosen to emphasize distance, haze, and silhouettes.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;getting-elevation-data&quot;&gt;Getting Elevation Data&lt;&#x2F;h1&gt;
&lt;p&gt;Copernicus provides &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;dataspace.copernicus.eu&#x2F;explore-data&#x2F;data-collections&#x2F;copernicus-contributing-missions&#x2F;collections-description&#x2F;COP-DEM&quot;&gt;global elevation models&lt;&#x2F;a&gt; at multiple resolutions.
As the browser will have to load all of Japan into RAM, the processed DEM will have to be quite low-res, so I used the coarse 90m product as a good starting point.
After downloading 71 DEM tiles from the Copernicus S3 bucket, this data now needs to be reprojected from a lat&#x2F;lon grid (EPSG:4326) to something more suited for building something resembling 3d space.
Considering our rough radius requirement of 150km, we can get away with using Mercator (EPSG:3857) here, ignoring the fact that Earth is a sphere.
Also, using Mercator makes the calculations between points on the map and the pixel space of the DEM much easier.
The reprojection happens in 2 steps:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Building a VRT from the elevation tiles&lt;&#x2F;li&gt;
&lt;li&gt;Warping the VRT to Web Mercator and subsetting to the bounds of Japan&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;gdalbuildvrt&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; dem.vrt dem_tiles&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;*&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;gdal_translate&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  -projwin&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 14411296.0 5015741.0 15844896.0 3582141.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  -tr&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 700 700&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  -co COMPRESS=DEFLATE&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  dem.vrt dem.tif&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Bounds and resolution are chosen in such a way that the resulting DEM texture will be 2048×2048 pixels in size. Finally, PIL converts the TIF image into a 16-bit grayscale PNG, making it easy to serve and load into WebGL.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; rasterio&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; rio&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; PIL&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; Image&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-control z-flow z-python&quot;&gt;with&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; rio&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;open&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;dem.tif&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-control z-flow z-python&quot;&gt; as&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; raster&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;    # Scale by 10.0 to keep more vertical accuracy&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    data&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; raster&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;read&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 10.0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;img&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; Image&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;fromarray&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;data&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;astype&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;np&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;uint16&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;img&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;save&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;dem.png&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is the practical trick that makes the whole applet lightweight. All the geospatial work happens offline. The web app only needs this one 2048 by 2048 elevation texture and a WebGL fragment shader.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;tradeoffs&quot;&gt;Tradeoffs&lt;&#x2F;h1&gt;
&lt;p&gt;There are plenty of inaccuracies. Web Mercator distorts scale, the elevation texture is coarse, the ray marching uses fixed exponential steps, the camera sits just above the DEM surface, and the atmosphere is just a hand-tuned color fade. There is no lighting model, no shadows, no clouds, and no curvature correction.&lt;&#x2F;p&gt;
&lt;p&gt;But for this purpose those are acceptable compromises. The goal is not to answer “what exactly would I see from this point?” The goal is to make a fast, explorable sketch of how Japan’s terrain stacks up visually: ridge after ridge, each one a little paler than the last.&lt;&#x2F;p&gt;
</content>
        </entry><entry xml:lang="en">
        <title>PixelDINO: Semi-Supervised Semantic Segmentation for Detecting Permafrost Disturbances</title>
        <published>2024-06-03T00:00:00+00:00</published>
        <updated>2024-06-03T00:00:00+00:00</updated>
        <author>
            <name>Konrad Heidler</name>
        </author>
        <link rel="alternate" href="https://konrad.earth/blog/pixeldino/" type="text/html"/>
        <id>https://konrad.earth/blog/pixeldino/</id>
        
            <content type="html">&lt;p&gt;In sync with the changing climate, permafrost is undergoing rapid transformations.
As temperatures rise, the frozen ground starts to thaw, which has various consequences.
Not only does permafrost thawing pose risks to local infrastructure such as roads and buildings,
but it also tightly connected to the global climate system by potentially releasing stored carbon into the atmosphere.&lt;&#x2F;p&gt;
&lt;figure class=&quot;w70&quot;&gt;
    
    &lt;img src=permafrost_globe.png#center &#x2F;&gt;
    &lt;figcaption&gt;
      
      &lt;p&gt;
         Distribution of permafrost in the northern hemisphere. (Visualisation based on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;nsidc.org&#x2F;data&#x2F;ggd318&#x2F;versions&#x2F;2&quot;&gt;data from NSICD&lt;&#x2F;a&gt;) 
        
        
        
      &lt;&#x2F;p&gt;
    &lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;Covering more than 10% of the Earth’s land surface, permafrost areas are often remote and sparsely populated, making them difficult to monitor through traditional means. In-situ measurements are limited to specific locations and times, usually when expeditions visit these sites or when local sensors collect data.
To overcome these limitations, remote sensing is a more efficient alternative for monitoring permafrost.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;permafrost-remote-sensing&quot;&gt;Permafrost Remote Sensing&lt;&#x2F;h2&gt;
&lt;figure &gt;
    
    &lt;img src=bykovsky.jpg &#x2F;&gt;
    &lt;figcaption&gt;
      
      &lt;p&gt;
         Coastal retrogressive thaw slump on the Bykovsky Peninsula in northern Siberia. © &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.awi.de&#x2F;ueber-uns&#x2F;organisation&#x2F;mitarbeiter&#x2F;detailseite&#x2F;ingmar-nitze.html&quot;&gt;Ingmar Nitze (AWI)&lt;&#x2F;a&gt; 
        
        
        
      &lt;&#x2F;p&gt;
    &lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;With satellite imagery, we can easily get observations of the entire Arctic.
While permafrost itself is mostly subsurface, we can monitor surface indicators closely linked to permafrost health or degradation.
One such example are retrogressive thaw slumps (RTS), slow landslides resulting from the thawing of ice-rich permafrost.
Despite their small size and scattered distribution, RTS can be detected in satellite images due to their distinct shape and spectral signature.&lt;&#x2F;p&gt;
&lt;p&gt;But here’s the catch:
While deep learning algorithms show promise in identifying RTS from satellite images, they need huge amounts of labeled training data.
In fact, only a very small fraction of the Arctic has been labelled for RTS:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a name=&quot;footprints&quot; id=&quot;footprints&quot;&gt;&lt;&#x2F;a&gt;
&lt;div class=&quot;applet&quot;&gt;
  &lt;iframe src=https:&amp;#x2F;&amp;#x2F;maps.heidler.info&amp;#x2F;rts-overview&amp;#x2F; allow=&quot;fullscreen&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
  
    &lt;button
      type=&quot;button&quot;
      class=&quot;fullscreen&quot;
      aria-label=&quot;Open applet fullscreen&quot;
      title=&quot;Fullscreen&quot;
    &gt;
      &lt;svg viewBox=&quot;0 0 24 24&quot; aria-hidden=&quot;true&quot;&gt;
        &lt;path
          d=&quot;M5 9V5h4M15 5h4v4M19 15v4h-4M9 19H5v-4&quot;
          fill=&quot;none&quot;
          stroke=&quot;currentColor&quot;
          stroke-width=&quot;2&quot;
          stroke-linecap=&quot;round&quot;
        &#x2F;&gt;
      &lt;&#x2F;svg&gt;
    &lt;&#x2F;button&gt;
  
&lt;&#x2F;div&gt;
&lt;&#x2F;p&gt;
&lt;p&gt;Acquiring labelled data is no easy feat, as permafrost experts need to manually look through large satellite imagery archives and label examples pixel-by-pixel.
Clearly, it is impossible to cover large fractions of the Arctic in this way.
So ideally, we are looking for ways to get our models to generalize to new locations without the need for extensive labeled data.
Therefore, we are exploring a new way of enhancing this generalisation ability without additional labels in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;2401.09271&quot;&gt;our recent study&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-is-dino&quot;&gt;What is DINO?&lt;&#x2F;h2&gt;
&lt;p&gt;In an ideal setup, we can not only use the existing labelled data, but also teach the model to extract knowledge from unlabelled imagery of previously unseen regions.
This hybrid setup is known as &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.ibm.com&#x2F;topics&#x2F;semi-supervised-learning&quot;&gt;&lt;em&gt;semi-supervised learning&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;ai.meta.com&#x2F;blog&#x2F;dino-paws-computer-vision-with-self-supervised-transformers-and-10x-more-efficient-training&#x2F;&quot;&gt;DINO&lt;&#x2F;a&gt;
is a method for training AI models without any labelled examples.
Instead of relying on human-labeled data, it lets the computer figure out its own way to recognize objects in pictures and classify them.
Intuitively, it gives the network the following rules:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Assign a class label to each training image&lt;&#x2F;li&gt;
&lt;li&gt;Make sure that the class labels remain the same when the image is transformed&lt;&#x2F;li&gt;
&lt;li&gt;Make use of all available labels (given a predefined number of them)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;To do this, the DINO learning process introduces two key players: the &lt;em&gt;student&lt;&#x2F;em&gt; and the &lt;em&gt;teacher&lt;&#x2F;em&gt;.
They work together to learn from images through a process called &lt;em&gt;self-distillation&lt;&#x2F;em&gt;, making sure to obey the rules stated above.
The teacher starts by guessing what’s in an image, then the student tries to match those guesses while also learning from the image itself.
It’s like a teacher guiding a student, but in this case, both of them are AI models learning together.&lt;&#x2F;p&gt;
&lt;p&gt;The transformations introduced by the second rule are called &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;viso.ai&#x2F;computer-vision&#x2F;image-data-augmentation-for-computer-vision&#x2F;&quot;&gt;Data Augmentations&lt;&#x2F;a&gt;.
By randomly applying operations to the input images, we can change the layout (flips, rotations, etc.) or adjust the image colours (brightness, contrast, …).
During training, student and teacher will both see differently augmented versions of the same image.
The student is then trained to match the teacher’s label.&lt;&#x2F;p&gt;
&lt;figure &gt;
    
    &lt;video autoplay loop muted playsinline src=augmentation.webm&gt; &lt;&#x2F;video&gt;
    &lt;figcaption&gt;
      
      &lt;p&gt;
         Visualization of spatial and colourspace augmentations on a Sentinel-2 satellite image from &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.google.com&#x2F;maps?ll=72.8585351,-119.521537&amp;amp;hl=en&amp;amp;t=h&amp;amp;z=11&quot;&gt;Banks Island, Canada&lt;&#x2F;a&gt;. 
        
        
        
      &lt;&#x2F;p&gt;
    &lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;That leaves us with the last rule, making the models to use of all of the available classes.
For this, DINO introduces two operations, called &lt;em&gt;centering&lt;&#x2F;em&gt; and &lt;em&gt;temperature scaling&lt;&#x2F;em&gt;.
So how do these work?
When we give it an image, the teacher doesn’t simply predict a single label, but actually gives us a &lt;em&gt;distribution&lt;&#x2F;em&gt; over the class labels.
For the centering step, we reduce the weight of frequently used classes, and boost the classes that are less often used.
This is done by keeping track of past teacher outputs.
For the temperature scaling step, the teacher outputs are adjusted in a way that emphasizes the differences present in the prediction – high-weight classes are given even higher weight, and low-weight classes are tuned down:&lt;&#x2F;p&gt;
&lt;figure &gt;
    
    &lt;video autoplay loop muted playsinline src=teacher.webm&gt; &lt;&#x2F;video&gt;
    &lt;figcaption&gt;
      
      &lt;p&gt;
         Centering and scaling of the teacher output, encouraging use of all classes while deciding for a single class per image. 
        
        
        
      &lt;&#x2F;p&gt;
    &lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;from-dino-to-pixeldino&quot;&gt;From DINO to PixelDINO&lt;&#x2F;h2&gt;
&lt;p&gt;In the regular DINO scheme, only a single label is assigned to the entire image.
But for mapping tasks in remote sensing, we need a class label for each individual location in the image instead.
This is what we do with our PixelDINO framework.
Instead of classifying whole images, it assigns labels to each pixel in the picture.&lt;&#x2F;p&gt;
&lt;p&gt;A fundamental assumption of DINO training is that augmentations don’t change the class of the image.
When working on the pixel level, this is no longer true!
When mirroring or rotating an image, the objects within the image change their locations.
So not only do we need to augment the imagery for the student, but we also need to transform the teacher labels alongside with it.
For this, we take inspiration from another semi-supervised training method, called &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;2208.00400&quot;&gt;FixMatchSeg&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Instead of creating two random augmentations of an image, FixMatchSeg builds on a chain of augmentations.
A first set, called &lt;em&gt;weak augmentations&lt;&#x2F;em&gt; is applied before passing an image to the teacher.
After getting teacher labels for this version of the image, the weakly augmented image is then augmented together with the teacher labels using a second set of augmentations, called &lt;em&gt;strong augmentations&lt;&#x2F;em&gt;.
The student then trains to match the teacher’s output on the strongly augmented version of the image.&lt;&#x2F;p&gt;
&lt;figure &gt;
    
    &lt;img src=pixeldino.png &#x2F;&gt;
    &lt;figcaption&gt;
      
      &lt;p&gt;
         Overview of the PixelDINO training pipeline used to train a model on unlabelled images. 
        
        
        
      &lt;&#x2F;p&gt;
    &lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;One final question in this setup is how to learn the teacher’s weights.
For this, we adapt the simple, yet effective strategy used by DINO: the teacher follows the student with an &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Exponential_smoothing&quot;&gt;exponential moving average&lt;&#x2F;a&gt;.
In this way, the teacher is not static, and is updated with newly distilled knowledge.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;from-self-supervised-to-semi-supervised&quot;&gt;From self-supervised to semi-supervised&lt;&#x2F;h2&gt;
&lt;p&gt;The next step is combining this self-supervised training method with regular supervised training.
After all, we do have imagery with existing ground truth annotations, as we saw &lt;a href=&quot;https:&#x2F;&#x2F;konrad.earth&#x2F;blog&#x2F;pixeldino&#x2F;#footprints&quot;&gt;above&lt;&#x2F;a&gt;.
Due to the fact that PixelDINO already works with pseudoclasses, this is very easy!
All we need to do is align one of the pseudoclasses with the RTS class that we have given in the training data.&lt;&#x2F;p&gt;
&lt;p&gt;All in all, we arrive at the following training procedure for a single batch (in Pytorch-Pseudocode):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-python&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; train_step&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt;img&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt; mask&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt; unlabelled&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # Supervised Training Step&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;  pred&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt; student&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;img&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;  loss_supervised&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt; cross_entropy&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;pred&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt; mask&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # Get pseudo-classes from teacher&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;  view_1&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt; augment_weak&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;unlabelled&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;  mask_1&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt; teacher&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;mask_1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;  mask_1&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;mask_1&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; center&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; temp&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;  batch_center&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; center&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;mean&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt;dim&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;  mask_1&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt; softmax&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;mask_1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # Strongly augment image and label together&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;  view_2&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; mask_2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt; augment_strong&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;view_1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt; mask_1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;  pred_2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt; student&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;view_2&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;  loss_dino&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt; cross_entropy&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;pred_2&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt; mask_2&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;  loss&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; loss_supervised&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; beta&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;loss_dino&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;  loss&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;backward&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;()&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # Back-propagate losses&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;  update&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;student&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # Adam weight update&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;  ema_update&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;teacher&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt; student&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # Teacher EMA&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;  ema_update&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;center&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt; batch_center&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # Center EMA&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;results&quot;&gt;Results&lt;&#x2F;h2&gt;
&lt;p&gt;To evaluate how well our method works, we trained multiple models with different configurations to see how well they classify permafrost disturbances.
To make the training process as fair as possible, we counted the number of training steps each model went through instead of using epochs, since our labeled data was much smaller than the unlabeled data.
We set aside two regions for testing the models: Herschel Island because it’s an island isolated from the mainland and Lena because it is in a different land cover zone.
This helps with seeing how well the models can handle areas they’ve never seen before.
We then compared the performance of these models with different configurations to see which training methods worked best.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Model&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;Herschel&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;Lena&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Baseline&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;19.8 ± 1.7&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;28.8 ±  3.0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Baseline+Aug&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;22.9 ± 3.0&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;25.8 ± 10.2&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;FixMatchSeg&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;23.4 ± 0.8&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;32.4 ±  3.2&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Adversarial&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;26.6 ± 3.9&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;25.1 ± 15.1&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;PixelDINO&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;30.2 ± 2.7&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;39.5 ±  6.5&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Indeed, PixelDINO outperforms not only the supervised base methods, but also the two other semi-supervised segmentation methods we tested: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;2208.00400&quot;&gt;FixMatchSeg&lt;&#x2F;a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;1802.07934&quot;&gt;Adversarial Semi-Segmentation&lt;&#x2F;a&gt;.
In practice, this improved training method leads to less false positives and more faithful reconstruction of RTS shapes:&lt;&#x2F;p&gt;
&lt;div class=&quot;applet&quot;&gt;
  &lt;iframe src=https:&amp;#x2F;&amp;#x2F;konrad.earth&amp;#x2F;blog&amp;#x2F;pixeldino&amp;#x2F;lena_interactive.svg?h=cb845ac93dc691bf5361 allow=&quot;fullscreen&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
  
&lt;&#x2F;div&gt;
&lt;p&gt;The developed PixelDINO method should prove useful not only for permafrost monitoring, but also for other usecases in remote sensing, where spatial variability poses a challenge.
We hope that this work can inspire follow-up research for other applications.&lt;&#x2F;p&gt;
</content>
        </entry><entry xml:lang="en">
        <title>COBRA: A Deep Active Contour Model for Delineating Glacier Calving Fronts</title>
        <published>2023-07-31T00:00:00+00:00</published>
        <updated>2023-07-31T00:00:00+00:00</updated>
        <author>
            <name>Konrad Heidler</name>
        </author>
        <link rel="alternate" href="https://konrad.earth/blog/deepsnake/" type="text/html"/>
        <id>https://konrad.earth/blog/deepsnake/</id>
        
            <content type="html">&lt;figure &gt;
    
    &lt;img src=helheim_overlay.jpg &#x2F;&gt;
    &lt;figcaption&gt;
      
      &lt;p&gt;
        
        
        
        
      &lt;&#x2F;p&gt;
    &lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;Existing approaches for calving front detection generally work by first performing
a pixel-wise segmentation or edge detection,
and then extract the actual calving front in a post-processing step.
Our goal in this study is to build a model that only needs a single step,
and directly outputs the calving front as a polyline.&lt;&#x2F;p&gt;
&lt;figure &gt;
    
    &lt;img src=architecture.png &#x2F;&gt;
    &lt;figcaption&gt;
      
      &lt;p&gt;
        
        
        
        
      &lt;&#x2F;p&gt;
    &lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;Following the idea of explicit contour prediction,
we have developed a new method called “Charting Outlines by Recurrent Adaptation” (COBRA).
It works by combining the idea of Active Contour models with deep learning.
First, a 2D CNN backbone derives feature maps from the input imagery.
Then, a 1D CNN (Snake Head) iteratively deforms an initial contour until to match the true contour.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;results&quot;&gt;Results&lt;&#x2F;h1&gt;
&lt;p&gt;These animations show how COBRA iteratively
predicts glacier calving fronts:&lt;&#x2F;p&gt;
&lt;div class=&quot;visual_row&quot;&gt;
  &lt;img src=&quot;.&#x2F;anim&#x2F;6.svg&quot;&gt;&lt;&#x2F;img&gt;
  &lt;img src=&quot;.&#x2F;anim&#x2F;8.svg&quot;&gt;&lt;&#x2F;img&gt;
  &lt;img src=&quot;.&#x2F;anim&#x2F;11.svg&quot;&gt;&lt;&#x2F;img&gt;
  &lt;img src=&quot;.&#x2F;anim&#x2F;18.svg&quot;&gt;&lt;&#x2F;img&gt;
  &lt;img src=&quot;.&#x2F;anim&#x2F;25.svg&quot;&gt;&lt;&#x2F;img&gt;
  &lt;img src=&quot;.&#x2F;anim&#x2F;32.svg&quot;&gt;&lt;&#x2F;img&gt;
&lt;&#x2F;div&gt;
&lt;h1 id=&quot;paper&quot;&gt;Paper&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;2307.03461&quot;&gt;Preprint available on arxiv&lt;&#x2F;a&gt;, published article available on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;ieeexplore.ieee.org&#x2F;document&#x2F;10195954&quot;&gt;IEEE Explore&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;code&quot;&gt;Code&lt;&#x2F;h1&gt;
&lt;p&gt;If you would like to
have a closer look at the implementation details,
work with our method,
or reproduce our results,
you can find all of our code &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;khdlr&#x2F;COBRA&quot;&gt;on Github&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;inference-map&quot;&gt;Inference Map&lt;&#x2F;h1&gt;
&lt;div class=&quot;applet&quot;&gt;
  &lt;iframe src=https:&amp;#x2F;&amp;#x2F;maps.heidler.info&amp;#x2F;cobra allow=&quot;fullscreen&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
  
    &lt;button
      type=&quot;button&quot;
      class=&quot;fullscreen&quot;
      aria-label=&quot;Open applet fullscreen&quot;
      title=&quot;Fullscreen&quot;
    &gt;
      &lt;svg viewBox=&quot;0 0 24 24&quot; aria-hidden=&quot;true&quot;&gt;
        &lt;path
          d=&quot;M5 9V5h4M15 5h4v4M19 15v4h-4M9 19H5v-4&quot;
          fill=&quot;none&quot;
          stroke=&quot;currentColor&quot;
          stroke-width=&quot;2&quot;
          stroke-linecap=&quot;round&quot;
        &#x2F;&gt;
      &lt;&#x2F;svg&gt;
    &lt;&#x2F;button&gt;
  
&lt;&#x2F;div&gt;
&lt;h1 id=&quot;video&quot;&gt;Video&lt;&#x2F;h1&gt;
&lt;div class=&quot;embed video-player&quot; style=&quot;text-align:center;&quot;&gt;
  &lt;iframe
    class=&quot;youtube-player&quot;
    type=&quot;text&#x2F;html&quot;
    width=&quot;640&quot;
    height=&quot;385&quot;
    src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;D66o6BVfZuk&quot;
    allowfullscreen
    frameborder=&quot;0&quot;
  &gt;
  &lt;&#x2F;iframe&gt;
&lt;&#x2F;div&gt;
</content>
        </entry><entry xml:lang="en">
        <title>Seeing the Bigger Picture: Enabling Large Context Windows in Neural Networks by Combining Multiple Zoom Levels</title>
        <published>2021-10-12T00:00:00+00:00</published>
        <updated>2021-10-12T00:00:00+00:00</updated>
        <author>
            <name>Konrad Heidler</name>
        </author>
        <link rel="alternate" href="https://konrad.earth/blog/zoom-nn/" type="text/html"/>
        <id>https://konrad.earth/blog/zoom-nn/</id>
        
            <content type="html">&lt;div class=&quot;embed video-player&quot; style=&quot;text-align:center;&quot;&gt;
  &lt;iframe
    class=&quot;youtube-player&quot;
    type=&quot;text&#x2F;html&quot;
    width=&quot;640&quot;
    height=&quot;385&quot;
    src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;aXyxxMvjIQ0&quot;
    allowfullscreen
    frameborder=&quot;0&quot;
  &gt;
  &lt;&#x2F;iframe&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Read the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;ieeexplore.ieee.org&#x2F;abstract&#x2F;document&#x2F;9554434&quot;&gt;full paper on IEEE Xplore&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        </entry><entry xml:lang="en">
        <title>Self-supervised Audiovisual Representation Learning for Remote Sensing Data</title>
        <published>2021-08-02T00:00:00+00:00</published>
        <updated>2021-08-02T00:00:00+00:00</updated>
        <author>
            <name>Konrad Heidler</name>
        </author>
        <link rel="alternate" href="https://konrad.earth/blog/sounding-earth/" type="text/html"/>
        <id>https://konrad.earth/blog/sounding-earth/</id>
        
            <content type="html">&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;konrad.earth&#x2F;blog&#x2F;sounding-earth&#x2F;.&#x2F;graphical_abstract.png&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Read the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.sciencedirect.com&#x2F;science&#x2F;article&#x2F;pii&#x2F;S1569843222003181&quot;&gt;full paper at Science Direct&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        </entry><entry xml:lang="en">
        <title>HED-UNet: Combined segmentation and edge detection for monitoring the Antarctic coastline</title>
        <published>2021-03-23T00:00:00+00:00</published>
        <updated>2021-03-23T00:00:00+00:00</updated>
        <author>
            <name>Konrad Heidler</name>
        </author>
        <link rel="alternate" href="https://konrad.earth/blog/hed-unet/" type="text/html"/>
        <id>https://konrad.earth/blog/hed-unet/</id>
        
            <content type="html">&lt;div class=&quot;embed video-player&quot; style=&quot;text-align:center;&quot;&gt;
  &lt;iframe
    class=&quot;youtube-player&quot;
    type=&quot;text&#x2F;html&quot;
    width=&quot;640&quot;
    height=&quot;385&quot;
    src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;NRAFh_gFU9U&quot;
    allowfullscreen
    frameborder=&quot;0&quot;
  &gt;
  &lt;&#x2F;iframe&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Read the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;ieeexplore.ieee.org&#x2F;abstract&#x2F;document&#x2F;9383809&quot;&gt;full paper on IEEE Xplore&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        </entry><entry xml:lang="en">
        <title>Metaballs</title>
        <published>2020-05-16T09:00:00+01:00</published>
        <updated>2020-05-16T09:00:00+01:00</updated>
        <author>
            <name>Konrad Heidler</name>
        </author>
        <link rel="alternate" href="https://konrad.earth/blog/metaballs/" type="text/html"/>
        <id>https://konrad.earth/blog/metaballs/</id>
        
            <content type="html">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Metaballs&quot;&gt;Metaballs&lt;&#x2F;a&gt;
are a fun way of creating blobby looking shapes.
The idea stems from a method for rendering molecules,
where each atom contributes to the overall electron density&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-1-1&quot;&gt;&lt;a href=&quot;#fn-1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In the easy case of hydrogen, this contribution is a radially symmetric shape centered around the
atom’s center \(c\):&lt;&#x2F;p&gt;
&lt;p&gt;$$
f(v; c) = a \exp(- \|v - c\|_2^2)
$$&lt;&#x2F;p&gt;
&lt;p&gt;To render a collection of such atoms, one then takes the sum over these contributions,
and renders a level-set:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\left\{\,v \,\middle|\, \sum_{i=1}^n a_i\exp(- \|v - c_i\|_2^2) = L\,\right\}
$$&lt;&#x2F;p&gt;
&lt;p&gt;Here’s a visualization in two dimensions with moving centers:&lt;&#x2F;p&gt;
&lt;div class=&quot;applet&quot;&gt;
  &lt;iframe src=metaballs.html allow=&quot;fullscreen&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
  
    &lt;button
      type=&quot;button&quot;
      class=&quot;fullscreen&quot;
      aria-label=&quot;Open applet fullscreen&quot;
      title=&quot;Fullscreen&quot;
    &gt;
      &lt;svg viewBox=&quot;0 0 24 24&quot; aria-hidden=&quot;true&quot;&gt;
        &lt;path
          d=&quot;M5 9V5h4M15 5h4v4M19 15v4h-4M9 19H5v-4&quot;
          fill=&quot;none&quot;
          stroke=&quot;currentColor&quot;
          stroke-width=&quot;2&quot;
          stroke-linecap=&quot;round&quot;
        &#x2F;&gt;
      &lt;&#x2F;svg&gt;
    &lt;&#x2F;button&gt;
  
&lt;&#x2F;div&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;dl.acm.org&#x2F;doi&#x2F;10.1145&#x2F;357306.357310&quot;&gt;Blinn, J. (1982). A Generalization of Algebraic Surface Drawing. ACM Trans. Graph., 1, 235-256.&lt;&#x2F;a&gt; &lt;a href=&quot;#fr-1-1&quot;&gt;↩&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;section&gt;
</content>
        <summary type="html">Moving blobs</summary>
        </entry><entry xml:lang="en">
        <title>Evolution</title>
        <published>2019-12-29T00:00:00+00:00</published>
        <updated>2019-12-29T00:00:00+00:00</updated>
        <author>
            <name>Konrad Heidler</name>
        </author>
        <link rel="alternate" href="https://konrad.earth/blog/evo/" type="text/html"/>
        <id>https://konrad.earth/blog/evo/</id>
        
            <content type="html">&lt;p&gt;Not much to do in this one… Simply watch the virtual creatures evolve to walk as fast as possible.&lt;&#x2F;p&gt;
&lt;div class=&quot;applet&quot;&gt;
  &lt;iframe src=evolution.html allow=&quot;fullscreen&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
  
    &lt;button
      type=&quot;button&quot;
      class=&quot;fullscreen&quot;
      aria-label=&quot;Open applet fullscreen&quot;
      title=&quot;Fullscreen&quot;
    &gt;
      &lt;svg viewBox=&quot;0 0 24 24&quot; aria-hidden=&quot;true&quot;&gt;
        &lt;path
          d=&quot;M5 9V5h4M15 5h4v4M19 15v4h-4M9 19H5v-4&quot;
          fill=&quot;none&quot;
          stroke=&quot;currentColor&quot;
          stroke-width=&quot;2&quot;
          stroke-linecap=&quot;round&quot;
        &#x2F;&gt;
      &lt;&#x2F;svg&gt;
    &lt;&#x2F;button&gt;
  
&lt;&#x2F;div&gt;
</content>
        <summary type="html">Learning to walk</summary>
        </entry><entry xml:lang="en">
        <title>Remote Sensing for Assessing Drought Insurance Claims in Central Europe</title>
        <published>2019-11-14T00:00:00+00:00</published>
        <updated>2019-11-14T00:00:00+00:00</updated>
        <author>
            <name>Konrad Heidler</name>
        </author>
        <link rel="alternate" href="https://konrad.earth/blog/draughts/" type="text/html"/>
        <id>https://konrad.earth/blog/draughts/</id>
        
            <content type="html">&lt;p&gt;Read the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;ieeexplore.ieee.org&#x2F;document&#x2F;8898926&quot;&gt;full paper on IEEE Xplore&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        </entry><entry xml:lang="en">
        <title>Soap Bubbles</title>
        <published>2019-06-06T09:00:00+01:00</published>
        <updated>2019-06-06T09:00:00+01:00</updated>
        <author>
            <name>Konrad Heidler</name>
        </author>
        <link rel="alternate" href="https://konrad.earth/blog/soap-bubbles/" type="text/html"/>
        <id>https://konrad.earth/blog/soap-bubbles/</id>
        
            <content type="html">&lt;p&gt;Have you ever wondered about the rainbows on CDs, gasoline puddles or soap bubbles?
All of these have the same cause: The &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Thin-film_interference&quot;&gt;interference of light on thin surfaces&lt;&#x2F;a&gt;.
Today, we’ll try to render something that looks like a soap bubble.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;some-physics&quot;&gt;Some Physics&lt;&#x2F;h2&gt;
&lt;p&gt;When a ray of light hits the surface of the soap bubble,
it is either instantly reflected, or it enters the soap film and is refracted.
When it is refracted, it can then be reflected off the other end of the soap,
and then leave it again at a slightly different spot.
There are countless other possiblities for the ray to bounce around,
but these are the two that we will focus on here:&lt;&#x2F;p&gt;
&lt;img class=&quot;img-light&quot; src=&quot;paths.svg&quot; loading=&quot;lazy&quot;&gt;
&lt;img class=&quot;img-dark&quot; src=&quot;paths-dark.svg&quot; loading=&quot;lazy&quot;&gt;
&lt;p&gt;Because the width of the soap layer is comparable to the wavelength of visible light,
the resulting color of the light rays will change considerably depending on the
incidence angle.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;finished-visualization&quot;&gt;Finished visualization&lt;&#x2F;h2&gt;
&lt;p&gt;Putting it all together,
we get this nice little interactive visualization.&lt;&#x2F;p&gt;
&lt;div class=&quot;applet&quot;&gt;
  &lt;iframe src=bubbles.html allow=&quot;fullscreen&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
  
    &lt;button
      type=&quot;button&quot;
      class=&quot;fullscreen&quot;
      aria-label=&quot;Open applet fullscreen&quot;
      title=&quot;Fullscreen&quot;
    &gt;
      &lt;svg viewBox=&quot;0 0 24 24&quot; aria-hidden=&quot;true&quot;&gt;
        &lt;path
          d=&quot;M5 9V5h4M15 5h4v4M19 15v4h-4M9 19H5v-4&quot;
          fill=&quot;none&quot;
          stroke=&quot;currentColor&quot;
          stroke-width=&quot;2&quot;
          stroke-linecap=&quot;round&quot;
        &#x2F;&gt;
      &lt;&#x2F;svg&gt;
    &lt;&#x2F;button&gt;
  
&lt;&#x2F;div&gt;
&lt;!-- If you&#x27;re interested in the source code, --&gt;
&lt;!-- # you can find it &lt;a download=&quot;bubbles.html&quot; href=&quot;bubbles.html&quot;&gt;here&lt;&#x2F;a&gt;. --&gt;
</content>
        </entry><entry xml:lang="en">
        <title>Particle Life</title>
        <published>2019-01-31T09:00:00+01:00</published>
        <updated>2019-01-31T09:00:00+01:00</updated>
        <author>
            <name>Konrad Heidler</name>
        </author>
        <link rel="alternate" href="https://konrad.earth/blog/particlelife/" type="text/html"/>
        <id>https://konrad.earth/blog/particlelife/</id>
        
            <content type="html">&lt;p&gt;Particle Simulations are great.
Life Simulations are fun.
Meet Particle Life – the intersection of these two worlds.&lt;&#x2F;p&gt;
&lt;p&gt;Inspired by Biologist &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;evolution.berkeley.edu&#x2F;evolibrary&#x2F;article&#x2F;history_24&quot;&gt;Lynn Margulis’&lt;&#x2F;a&gt;
theory of endosymbiosis, Jeffrey Ventrella invented his &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;www.ventrella.com&#x2F;Clusters&#x2F;&quot;&gt;Clusters&lt;&#x2F;a&gt;.
The main idea is simulating microorganisms that have pretty basic interaction patterns.
Some species are drawn towards certain others, while some will be repelled by others.
Implementing a particle simulation with these rules leads to very interesting patterns.&lt;&#x2F;p&gt;
&lt;p&gt;Compared to physics-based particle simulations, this approach explicitly violates the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Momentum#Conservation&quot;&gt;Conservation of Momentum&lt;&#x2F;a&gt;.
This allows for self-propelling structures, clusters that look like biological cells, and much more.&lt;&#x2F;p&gt;
&lt;p&gt;Porting this simulation to WebGL code was pretty straight-forward using
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;cindyjs.org&quot;&gt;CindyJS&lt;&#x2F;a&gt;.
Play around with the simulation here:&lt;&#x2F;p&gt;
&lt;div class=&quot;applet&quot;&gt;
  &lt;iframe src=particlelife.html allow=&quot;fullscreen&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
  
    &lt;button
      type=&quot;button&quot;
      class=&quot;fullscreen&quot;
      aria-label=&quot;Open applet fullscreen&quot;
      title=&quot;Fullscreen&quot;
    &gt;
      &lt;svg viewBox=&quot;0 0 24 24&quot; aria-hidden=&quot;true&quot;&gt;
        &lt;path
          d=&quot;M5 9V5h4M15 5h4v4M19 15v4h-4M9 19H5v-4&quot;
          fill=&quot;none&quot;
          stroke=&quot;currentColor&quot;
          stroke-width=&quot;2&quot;
          stroke-linecap=&quot;round&quot;
        &#x2F;&gt;
      &lt;&#x2F;svg&gt;
    &lt;&#x2F;button&gt;
  
&lt;&#x2F;div&gt;
</content>
        </entry><entry xml:lang="en">
        <title>Reversing Hill Shading in Digital Elevation Models</title>
        <published>2019-01-03T09:00:00+01:00</published>
        <updated>2019-01-03T09:00:00+01:00</updated>
        <author>
            <name>Konrad Heidler</name>
        </author>
        <link rel="alternate" href="https://konrad.earth/blog/hillshade/" type="text/html"/>
        <id>https://konrad.earth/blog/hillshade/</id>
        
            <content type="html">&lt;p&gt;A while back on the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;gis&quot;&gt;Geographic Information Systems Subreddit&lt;&#x2F;a&gt; I stumbled upon &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;gis&#x2F;comments&#x2F;9o6pni&#x2F;convert_a_rgb_rendered_geotiff_back_into_a_dem&#x2F;&quot;&gt;this question&lt;&#x2F;a&gt;.
A user wished to get height information from this elevation map they had found somewhere:&lt;&#x2F;p&gt;
&lt;img class=&quot;dimmable-image&quot; src=&quot;.&amp;#x2F;hillshade_rotated.png&quot; loading=&quot;lazy&quot;&#x2F;&gt;
&lt;p&gt;Unforunately it has &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Terrain_cartography#Shaded_relief&quot;&gt;hill-shading&lt;&#x2F;a&gt; applied on top of the color bar. While this shading technique makes it easier to understand the terrain structure at a glance, it also makes it harder to extract the raw height data from the map. So first we’ll have to understand how hill-shading works in order to find a way to get around it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;hill-shading&quot;&gt;Hill-shading&lt;&#x2F;h2&gt;
&lt;p&gt;The point of hill-shading is make a height map appear 3D by shading slopes according to imaginary sunlight that hits the plane at some angle.&lt;&#x2F;p&gt;
&lt;img class=&quot;img-light&quot; src=&quot;shading.svg&quot; loading=&quot;lazy&quot;&gt;
&lt;img class=&quot;img-dark&quot; src=&quot;shading-dark.svg&quot; loading=&quot;lazy&quot;&gt;
&lt;p&gt;When creating a regular elevation map, one takes the raw elevation values and
maps them to \((r, g, b)\) values according to a specified color scale.
To achieve hill-shading, one additionally specifies a global light vector \(\vec l\)
and a surface normal vector \(\vec n\) for each point. The shading factor can then be expressed as the scalar product \(s = \langle -\vec l, \vec n\rangle\).&lt;&#x2F;p&gt;
&lt;p&gt;The important point here is that the shading factor is applied uniformly to all three color channels. This means that hillshading only affects the brightness of a pixel, but not its hue. So if the color scale plays nicely with the hue, we will be able to extract the original height data.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;getting-our-hands-dirty&quot;&gt;Getting our hands dirty&lt;&#x2F;h2&gt;
&lt;p&gt;Time to whip out python and get going!
Reading the image is pretty straight forward.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; numpy&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; np&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; matplotlib&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;pyplot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; plt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; colorsys&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;img&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; plt&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;imread&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;hillshade_rotated.png&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We then take a look at the image and find a line that slices through the color bar.
From this bar we’ll deduce the transform needed to convert hue to elevation&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;plt&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;figure&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt;figsize&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;10&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;plt&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;grid&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language z-python&quot;&gt;False&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;plt&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;imshow&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;img&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;plt&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;plot&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;([&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;97&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 97&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;], [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;95&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 755&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;])&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;img class=&quot;dimmable-image&quot; src=&quot;find_bar.png&quot; loading=&quot;lazy&quot;&#x2F;&gt;
&lt;p&gt;We can now take the data from the color bar.
To be sure, we plot the color bar data to verify that we actually extracted what we wanted.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;colorbar&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-indexed-name z-python&quot;&gt; img&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;755&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;95&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 97&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;98&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;, :]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;plt&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;grid&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language z-python&quot;&gt;False&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;plt&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;imshow&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;np&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;kron&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;colorbar&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt; np&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;ones&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;((&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 60&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;))).&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;transpose&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;))&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;figure &gt;
    
    &lt;img src=colorbar.png &#x2F;&gt;
    &lt;figcaption&gt;
      
      &lt;p&gt;
         The extracted color bar 
        
        
        
      &lt;&#x2F;p&gt;
    &lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;Upon extracting the hue channel from the color bar data, we see that it is in fact monotonic and close to injective!
This means we can reconstruct the original elevation data from just this channel.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-python&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; convert&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt;image&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    fun&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; np&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;vectorize&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;colorsys&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;rgb_to_hsv&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-control z-flow z-python&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; np&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;stack&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;fun&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-indexed-name z-python&quot;&gt;image&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[:,:,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;],&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-indexed-name z-python&quot;&gt; image&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[:,:,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;],&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-indexed-name z-python&quot;&gt; image&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[:,:,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;HUE&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; SATURATION&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; VALUE&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt; convert&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;colorbar&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;HIGH&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; = -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;40&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;LOW&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; = -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;78.08&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-python&quot;&gt;def&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; correct&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    H&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; np&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;sort&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;HUE&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;reshape&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-control z-flow z-python&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; HIGH&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;HIGH&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; LOW&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; H&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;searchsorted&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;x&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-builtin z-python&quot;&gt; len&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;H&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;fig&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;, (&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;ax1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; ax2&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; plt&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;subplots&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt; figsize&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;10&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 5&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;ax1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;plot&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;HUE&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;ax1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;set_title&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;Hue Progression&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;ax2&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;plot&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;correct&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;HUE&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;ax2&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;set_title&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;Corrected Elevation Progression&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;img class=&quot;invertible-image&quot; src=&quot;correction.png&quot; loading=&quot;lazy&quot;&#x2F;&gt;
&lt;p&gt;The left plot shows how the hue progresses over the color bar.
The right plot shows how the color bar looks when corrected using &lt;code&gt;np.searchsorted&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;p&gt;Now, we can calculate the hue channel for the entire image and reconstruct the elevation values
using the correction function we just wrote.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;h&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; s&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; v&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt; convert&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;img&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;corrected&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt; correct&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;h&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;plt&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;figure&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt;figsize&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;10&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;plt&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;grid&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language z-python&quot;&gt;False&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;plt&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;imshow&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;corrected&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt; cmap&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;gray&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;plt&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;imsave&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;elevation_rotated.png&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt; corrected&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; ,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt; cmap&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;gray&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;img class=&quot;dimmable-image&quot; src=&quot;naive.png&quot; loading=&quot;lazy&quot;&#x2F;&gt;
&lt;p&gt;As a quick check, we’ll plot the output:&lt;&#x2F;p&gt;
&lt;p&gt;There’s a lot of ugly artifacts going on!
Why is that?
As you may have guessed, JPEG compression is to blame here.
Pixels close to the border of the heightmap that &lt;em&gt;should&lt;&#x2F;em&gt; be white get assigned some
color that slightly differs from white.
Because we only look at the hue channel, this is enough to distort these pixels completely.&lt;&#x2F;p&gt;
&lt;p&gt;This is where the saturation channel comes in handy.
These almost-white pixels have close to no saturation,
so it is enough to cut off the pixels at some saturation value&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-indexed-name z-python&quot;&gt;corrected&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;s&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0.1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language z-python&quot;&gt; None&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;plt&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;figure&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt;figsize&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;10&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;plt&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;grid&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language z-python&quot;&gt;False&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;plt&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;imshow&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;corrected&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt; cmap&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;gray&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;plt&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;imsave&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;elevation.png&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt; corrected&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; ,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt; cmap&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;gray&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;img class=&quot;dimmable-image&quot; src=&quot;elevation.png&quot; loading=&quot;lazy&quot;&#x2F;&gt;
&lt;p&gt;This looks much better!&lt;&#x2F;p&gt;
&lt;p&gt;And because stuff is always more fun in 3D,
we will end this post with a quick interactive surface plot of the extracted data in plotly:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; plotly&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;graph_objs&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; go&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;from&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; plotly&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;offline&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; import&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; plot&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; init_notebook_mode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;init_notebook_mode&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;Z&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-indexed-name z-python&quot;&gt; corrected&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[:,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 240&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;610&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-indexed-name z-python&quot;&gt;Z&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;Z&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;==&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;surf&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; go&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;Surface&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt;z&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;Z&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;layout&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; go&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;Layout&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt;    title&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;Heightmap&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt;    autosize&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language z-python&quot;&gt;True&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt;    width&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;640&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt;    height&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;640&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;fig&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; go&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;Figure&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt;data&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;surf&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;],&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt; layout&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;layout&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;plot&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;fig&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt; filename&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;hillshade_elevation&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;div class=&quot;applet&quot;&gt;
  &lt;iframe src=hillshade_elevation.html allow=&quot;fullscreen&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
  
    &lt;button
      type=&quot;button&quot;
      class=&quot;fullscreen&quot;
      aria-label=&quot;Open applet fullscreen&quot;
      title=&quot;Fullscreen&quot;
    &gt;
      &lt;svg viewBox=&quot;0 0 24 24&quot; aria-hidden=&quot;true&quot;&gt;
        &lt;path
          d=&quot;M5 9V5h4M15 5h4v4M19 15v4h-4M9 19H5v-4&quot;
          fill=&quot;none&quot;
          stroke=&quot;currentColor&quot;
          stroke-width=&quot;2&quot;
          stroke-linecap=&quot;round&quot;
        &#x2F;&gt;
      &lt;&#x2F;svg&gt;
    &lt;&#x2F;button&gt;
  
&lt;&#x2F;div&gt;
&lt;p&gt;Find the Jupyter Notebook containing all the source code from this post &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;cloud-oak&#x2F;HillshadeInverseTransform&#x2F;blob&#x2F;master&#x2F;Hillshade.ipynb&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        </entry><entry xml:lang="en">
        <title>Pachelballs</title>
        <published>2018-12-25T09:00:00+01:00</published>
        <updated>2018-12-25T09:00:00+01:00</updated>
        <author>
            <name>Konrad Heidler</name>
        </author>
        <link rel="alternate" href="https://konrad.earth/blog/pachelballs/" type="text/html"/>
        <id>https://konrad.earth/blog/pachelballs/</id>
        
            <content type="html">&lt;p&gt;When I was a kid, I really enjoyed this game called &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Electroplankton&quot;&gt;Electroplankton&lt;&#x2F;a&gt;.
Especially the game mode called &lt;em&gt;Hanenbow&lt;&#x2F;em&gt;,
where you launched small tadpoles onto leaves.
On impact the leaves would emit glockenspiel sounds, and create a nice sounding melody.
Here’s what it looked like:&lt;&#x2F;p&gt;
&lt;div class=&quot;embed video-player&quot; style=&quot;text-align:center;&quot;&gt;
  &lt;iframe
    class=&quot;youtube-player&quot;
    type=&quot;text&#x2F;html&quot;
    width=&quot;640&quot;
    height=&quot;385&quot;
    src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;it7wvZmgYYA&quot;
    allowfullscreen
    frameborder=&quot;0&quot;
  &gt;
  &lt;&#x2F;iframe&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;I wondered, how hard could it be to implement something similar?
If you just want to see the animation, click &lt;a href=&quot;https:&#x2F;&#x2F;konrad.earth&#x2F;blog&#x2F;pachelballs&#x2F;#finished-animation&quot;&gt;here&lt;&#x2F;a&gt;.
If you’re interested in the background, read on!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;concept&quot;&gt;Concept&lt;&#x2F;h2&gt;
&lt;figure &gt;
    
    &lt;img src=concept.svg &#x2F;&gt;
    &lt;figcaption&gt;
      
      &lt;p&gt;
         The basic idea: Cannon, Balls, Bouncers 
        
        
        
      &lt;&#x2F;p&gt;
    &lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;First we have a cannon that shoots out balls at a certain frequency.
These balls then fly through a field of bouncers, that act like small trampolines for the balls.
Upon hitting a bouncer, a ball will emit a certain tone.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;physics&quot;&gt;Physics&lt;&#x2F;h2&gt;
&lt;p&gt;We’re going to be using the wonderful &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;cindyjs.org&quot;&gt;CindyJS Framework&lt;&#x2F;a&gt;,
that does a lot of the heavy lifting behind the scenes.&lt;&#x2F;p&gt;
&lt;p&gt;Ball flying physics is easy.
A ball with position \(x\) and velocity \(v\) will be updated in two steps:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Updating the speed: \(v \gets v + dt \cdot g\), with a time step \(dt\) and the gravity vector \(g\)&lt;&#x2F;li&gt;
&lt;li&gt;Updating the position: \(x \gets x + dt \cdot v\)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The collisions with the bouncers are where it gets tricky.
First of all, how do we detect a collision?&lt;&#x2F;p&gt;
&lt;figure &gt;
    
    &lt;img src=collision.svg &#x2F;&gt;
    &lt;figcaption&gt;
      
      &lt;p&gt;
         This is what we want to detect: \(x\) and \(\bar x\) are on opposite sides of the bouncer. 
        
        
        
      &lt;&#x2F;p&gt;
    &lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;Say a ball is updated from position \(x\) to position \(\bar x\).
We want to check if it collides with a bouncer during that update.
A bouncer is essentially a line between two points \(b_1\) and \(b_2\).&lt;&#x2F;p&gt;
&lt;p&gt;One can see that we have a collision if and only if these two conditions are fulfilled:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;\(x\) and \(\bar x\) lie on opposing sides of the line \(b_1b_2\).&lt;&#x2F;li&gt;
&lt;li&gt;\(b_1\) and \(b_2\) lie on opposing sides of the line \(x\bar x\).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;There is a neat way of calculating which side of a line a point is on using determinants.
The determinant&lt;&#x2F;p&gt;
&lt;p&gt;$$\det\begin{pmatrix}x_A&amp;amp;x_B&amp;amp;x_C\\ y_A&amp;amp;y_B&amp;amp;y_C\\ 1&amp;amp;1&amp;amp;1\end{pmatrix}$$&lt;&#x2F;p&gt;
&lt;p&gt;will have a positive sign if \(C\) lies left of the line \(AB\), and a negative one if it lies on the right:&lt;&#x2F;p&gt;
&lt;div class=&quot;applet&quot;&gt;
  &lt;iframe src=determinantorientation.html allow=&quot;fullscreen&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
  
    &lt;button
      type=&quot;button&quot;
      class=&quot;fullscreen&quot;
      aria-label=&quot;Open applet fullscreen&quot;
      title=&quot;Fullscreen&quot;
    &gt;
      &lt;svg viewBox=&quot;0 0 24 24&quot; aria-hidden=&quot;true&quot;&gt;
        &lt;path
          d=&quot;M5 9V5h4M15 5h4v4M19 15v4h-4M9 19H5v-4&quot;
          fill=&quot;none&quot;
          stroke=&quot;currentColor&quot;
          stroke-width=&quot;2&quot;
          stroke-linecap=&quot;round&quot;
        &#x2F;&gt;
      &lt;&#x2F;svg&gt;
    &lt;&#x2F;button&gt;
  
&lt;&#x2F;div&gt;
&lt;p&gt;So a collision is equivalent to these two expressions being true at the same time:
$${\det(b_1,b_2,x) \cdot \det(b_1,b_2,\bar x) \leq 0}$$
$${\det(x, \bar x, b_1) \cdot \det(x, \bar x, b_2) \leq 0}$$&lt;&#x2F;p&gt;
&lt;p&gt;Upon collision, we want to play a sound and reflect the ball.
This is done by setting the velocity
\(v \gets v - 2\langle v,n \rangle n\),
where \(n\) is the bouncer’s normal.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;music&quot;&gt;Music&lt;&#x2F;h2&gt;
&lt;p&gt;If we were to just emit random tones,
the result would be disharmony.
We must therefore pick the tones we play from some harmonic distribution.
The easiest way to do this is to limit the tones to a certain chord.
We can then change the chords over time, to get a nice little tune.&lt;&#x2F;p&gt;
&lt;p&gt;As you have probably guessed from the title,
we’ll be using the chord progression from
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Pachelbel%27s_Canon&quot;&gt;Pachelbel’s Canon&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;img class=&quot;invertible-image&quot; src=&quot;https:&amp;#x2F;&amp;#x2F;konrad.earth&amp;#x2F;blog&amp;#x2F;pachelballs&amp;#x2F;pachelbel.svg?h=16d783a7566f55eb256f&quot; loading=&quot;lazy&quot; width=&quot;1184&quot; height=&quot;241&quot; &#x2F;&gt;
&lt;p&gt;CindyJS has a MIDI Interface, that just enumerates musical notes starting with &lt;code&gt;C0&lt;&#x2F;code&gt;.
So our chord progression can be transcribed into&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite&quot;&gt;chords&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;63&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 68&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 72&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; &#x2F;&#x2F; Ab&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;63&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 67&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 70&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; &#x2F;&#x2F; Eb&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;60&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 65&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 68&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; &#x2F;&#x2F; Fm&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;60&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 63&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 67&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; &#x2F;&#x2F; Cm&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;56&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 61&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 65&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; &#x2F;&#x2F; Db&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;56&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 60&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 63&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; &#x2F;&#x2F; Ab&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;56&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 61&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 65&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; &#x2F;&#x2F; Db&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;    [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;58&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 63&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 67&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  &#x2F;&#x2F; Eb&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Each one of the bouncers will play a tone based on this chord table,
where the row will cycle through time.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;finished-animation&quot;&gt;Finished animation&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a name=&quot;finished&quot;&gt;&lt;&#x2F;a&gt;
Putting it all together,
we get this nice little interactive animation.&lt;&#x2F;p&gt;
&lt;div class=&quot;applet&quot;&gt;
  &lt;iframe src=pachelballs.html allow=&quot;fullscreen&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
  
    &lt;button
      type=&quot;button&quot;
      class=&quot;fullscreen&quot;
      aria-label=&quot;Open applet fullscreen&quot;
      title=&quot;Fullscreen&quot;
    &gt;
      &lt;svg viewBox=&quot;0 0 24 24&quot; aria-hidden=&quot;true&quot;&gt;
        &lt;path
          d=&quot;M5 9V5h4M15 5h4v4M19 15v4h-4M9 19H5v-4&quot;
          fill=&quot;none&quot;
          stroke=&quot;currentColor&quot;
          stroke-width=&quot;2&quot;
          stroke-linecap=&quot;round&quot;
        &#x2F;&gt;
      &lt;&#x2F;svg&gt;
    &lt;&#x2F;button&gt;
  
&lt;&#x2F;div&gt;
&lt;p&gt;If you’re interested in the source code,
you can find it &lt;a download=&quot;pachelballs.html&quot; href=&quot;&#x2F;static&#x2F;pachelballs.html&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        </entry>
</feed>
