<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aditya Singh</title>
    <description>The latest articles on DEV Community by Aditya Singh (@adityahunt).</description>
    <link>https://dev.to/adityahunt</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2805758%2Fa82608b2-2b1f-4a7b-8fc8-bd9da458846d.jpg</url>
      <title>DEV Community: Aditya Singh</title>
      <link>https://dev.to/adityahunt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adityahunt"/>
    <language>en</language>
    <item>
      <title>🌊 From SAR Data to Actionable Maps: Building an Open-Source Flood Detection Pipeline with Python</title>
      <dc:creator>Aditya Singh</dc:creator>
      <pubDate>Sat, 01 Aug 2026 07:20:38 +0000</pubDate>
      <link>https://dev.to/adityahunt/from-sar-data-to-actionable-maps-building-an-open-source-flood-detection-pipeline-with-python-2idf</link>
      <guid>https://dev.to/adityahunt/from-sar-data-to-actionable-maps-building-an-open-source-flood-detection-pipeline-with-python-2idf</guid>
      <description>&lt;p&gt;Optical satellites are useless during floods because, well, it’s cloudy. &lt;strong&gt;Sentinel-1 SAR&lt;/strong&gt; sees through rain and night, but its data is noisy, complex, and full of "permanent water" that isn't actually flooding.&lt;/p&gt;

&lt;p&gt;I built an end-to-end pipeline to turn raw SNAP GeoTIFFs into clean, actionable flood maps. Here’s how I solved the three biggest headaches in SAR processing.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The "Padding" Trap
&lt;/h3&gt;

&lt;p&gt;SNAP exports often include zero-value borders. If you don’t crop these, your histogram gets skewed by millions of &lt;code&gt;0&lt;/code&gt;s, breaking automatic thresholding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; Treat &lt;code&gt;0.0&lt;/code&gt; as &lt;code&gt;NaN&lt;/code&gt;, convert to dB, and crop strictly to the valid data footprint before any analysis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Crop to valid data only
&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isnan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dB_full&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isnan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dB_full&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;dB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dB_full&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Isolating &lt;em&gt;New&lt;/em&gt; Floodwater
&lt;/h3&gt;

&lt;p&gt;A simple &lt;code&gt;-17 dB&lt;/code&gt; threshold detects &lt;em&gt;all&lt;/em&gt; water. To find actual flood damage, you must subtract permanent rivers and lakes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; I used &lt;strong&gt;OSMnx&lt;/strong&gt; to fetch permanent water bodies from OpenStreetMap, rasterized them to match the SAR grid, and subtracted them from the detected mask.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Fetch permanent water from OSM
&lt;/span&gt;&lt;span class="n"&gt;osm_water&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;features_from_bbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bbox&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bbox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;natural&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;water&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;# Subtract from detected mask
&lt;/span&gt;&lt;span class="n"&gt;flood_only&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mask_clean&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="nf"&gt;binary_dilation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;permanent_water&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. The QGIS "Black Background" Bug
&lt;/h3&gt;

&lt;p&gt;Exporting transparent GeoTIFFs is a nightmare. QGIS often renders "transparent" pixels as solid black if you rely on standard Alpha channels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; Instead of RGBA, I use a single-band raster with a dedicated &lt;code&gt;NoData&lt;/code&gt; value (255). QGIS treats &lt;code&gt;NoData&lt;/code&gt; as transparent automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 1 = flooded, 255 = NoData (Transparent in QGIS)
&lt;/span&gt;&lt;span class="n"&gt;overlay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final_flood_raster&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Result
&lt;/h3&gt;

&lt;p&gt;The pipeline outputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Vector Data:&lt;/strong&gt; GeoJSON/Shapefiles with accurate area calculations (in km²).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Interactive Maps:&lt;/strong&gt; A Folium HTML map with Satellite/Street toggles.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;QGIS-Ready Rasters:&lt;/strong&gt; Clean overlays that work immediately upon import.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🚀 Try It Out
&lt;/h3&gt;

&lt;p&gt;The full notebook is open-source. It handles everything from speckle cleanup to final visualization.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;[&lt;a href="https://github.com/Adityas221b/QGIS-Flood" rel="noopener noreferrer"&gt;https://github.com/Adityas221b/QGIS-Flood&lt;/a&gt;]&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Python #GeoAI #RemoteSensing #OpenSource #ClimateTech
&lt;/h1&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>opensource</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
