Flood inundation mapping and monitoring using SAR data and deep learning

  • 🔬 Data Science
  • 🥠 Deep Learning and pixel classification

Introduction

Flooding is one of the most frequent and costly forms of natural disasters. They often strike without warning and can occur when large volumes of water fall in a short time, causing flash floods. Flood mapping is typically performed using the following methods:

  • Aerial observations
  • Ground surveys

However, when flooding is widespread, these methods become prohibitively expensive and time consuming. Furthermore, aerial observation and optical imagery can often prove difficult, if not impossible, due to obstructive weather conditions. During flooding conditions, clouds can prevent the use of optical satellite imagery for visualization and analysis. In these instances, synthetic-aperture radar (SAR) allows us to penetrate through clouds and hazy atmospheric conditions to continuously observe and map flooding.

In 2019, severe flooding occurred in the Midwest of the United States. Also known as the Great Flood of 2019, 14 million people were affected across multiple states. In this analysis, we will perform flood mapping and infrastructural inundation mapping of the St. Peters region of Missouri, which was one of the affected areas during the flood.

Necessary imports

import os
from datetime import datetime
from pathlib import Path

from arcgis.gis import GIS
from arcgis.learn import prepare_data, UnetClassifier
from arcgis.raster import Raster, convert_raster_to_feature
from arcgis.features.manage_data import overlay_layers
from arcgis.features.analysis import dissolve_boundaries

Connect to your GIS

from arcgis import GIS
gis = GIS('home')
gis_ent = GIS(profile='your_enterprise_profile')

Export training data

Here, we convert the Sentinel-1 GRD VH polarization band to a 3 band raster using Export Raster. Under the Render Settings section, once Use Renderer is checked, Force RGB will be enabled.

The resulting raster is generated from the Sentinel-1 GRD VH imagery using traditional histogram thresholding technique. The raster contains two classes, permanent waterbodies and flood water. This raster will be used as a Classified Raster in the Export Training Data Using Deep Learning tool.

input_raster = gis_ent.content.get("ca56c6a8be4b44efad2b5fcec25270b2")
input_raster
flood_input_raster_for_training_data
Imagery Layer by api_data_owner
Last Modified: April 17, 2024
0 comments, 3 views

The feature layer contains two classes: 1 = Permanent Waterbodies and 2 = Flood Water. The feature layer will be used as the Input Feature Class in the Export Training Data For Deep Learning tool.

label_raster = gis_ent.content.get("2d52064d4eb54559b75ff4451cb6d52b")
label_raster
flood_label_2classes
flood label 2classesFeature Layer Collection by api_data_owner
Last Modified: December 19, 2023
0 comments, 5 views

The polygon feature class will be used as Input Mask Polygons in the Export Training Data For Deep Learning tool to delineate the area where image chips will be created.

aoi = gis_ent.content.get("d378a12e00a24815a306965e1917601d")
aoi
flood_aoi_mask
flood aoi maskFeature Layer Collection by api_data_owner
Last Modified: December 19, 2023
0 comments, 4 views

The Export Training Data For Deep Learning tool is used to prepare training data for training a deep learning model. The tool is available in both ArcGIS Pro and ArcGIS Enterprise.

Next, we will utilize Jupyter Notebooks. Documentation on how to install and setup the necessary environment is available here.

Model training

Get training data

We have already exported the data, and it can be directly downloaded using the following steps:

training_data = gis.content.get('c4f58fd8e21743d69c82a93b30c8b873')
training_data
flood_inundation_mapping_using_sar_data_and_deep_learning
Flood inundation mapping using sar data and deep learningImage Collection by api_data_owner
Last Modified: February 09, 2022
0 comments, 165 views
filepath = training_data.download(file_name=training_data.name)
import zipfile
with zipfile.ZipFile(filepath, 'r') as zip_ref:
    zip_ref.extractall(Path(filepath).parent)
data_path = Path(os.path.join(os.path.splitext(filepath)[0]))

Prepare data

The prepare_data function takes a training data path as input and creates a fast.ai databunch with specified parameters, like transformation, batch size, split percentage, etc.

data = prepare_data(data_path, batch_size=4, chip_size=400)

Visualize training data

To get a sense of what the training data looks like, the arcgis.learn.show_batch() method will randomly select a few training chips and visualize them.

data.show_batch(rows=3)
<Figure size 1500x1500 with 9 Axes>

Load model architecture

arcgis.learn provides the UnetClassifier model for per pixel classification that is based on a pretrained convnet, like ResNet, that acts as the backbone. More details about UnetClassifier can be found here.

# Create U-Net Model
unet = UnetClassifier(data, backbone='resnet34')
unet.unfreeze()

Train the model

lr = unet.lr_find()
lr
<Figure size 640x480 with 1 Axes>
0.0001584893192461114

We are using the suggested learning rate above to train the model for 400 epochs.

unet.fit(100, lr=lr)
epochtrain_lossvalid_lossaccuracydicetime
00.0565240.0649840.9760620.60877206:45
10.0622500.0655580.9764530.59685303:28
20.0543210.0658290.9749050.60677903:29
30.0554700.0656570.9751550.61250703:38
40.0579250.0626830.9758910.61254403:41
50.0466970.0664850.9753120.61203003:38
60.0483880.0671630.9740900.60562503:34
70.0570850.0681840.9746430.59944003:37
80.0573200.0633180.9769090.60184603:39
90.0558740.0641390.9764540.61823903:39
100.0599820.0653770.9754550.60464603:34
110.0644320.0628930.9756810.60319503:30
120.0527420.0670080.9754610.59122903:34
130.0606520.0656590.9750320.55358703:39
140.0674670.0781930.9712950.61104203:33
150.0467040.0688640.9741960.56587903:34
160.0561450.0706270.9744190.60266003:31
170.0665290.0818960.9691270.60196603:32
180.0633440.0684900.9741170.60083703:32
190.0725480.0733960.9727210.61900603:29
200.0615600.0749580.9729940.61383503:33
210.0600210.0688720.9744120.61566403:28
220.0601530.0729470.9737990.59287703:29
230.0764320.0864170.9687660.58326703:32
240.0848450.0768410.9730720.58345203:28
250.0642840.0658080.9756770.55743903:29
260.0694260.0699170.9736830.61669703:29
270.0662330.0767770.9702530.61676303:28
280.0615310.0690770.9741100.61558003:28
290.0592840.0731900.9736900.54274903:28
300.0659800.0699730.9767070.60850803:29
310.0703540.0925940.9664640.55136203:30
320.0724700.0934560.9655450.59132603:31
330.0625190.0699110.9751240.54139103:33
340.0649940.0679700.9750510.57545803:32
350.0667550.0733110.9749760.50005103:32
360.0565480.0676920.9765050.54351903:30
370.0733700.0709710.9742820.60736103:33
380.0670370.0755560.9711200.58616503:33
390.0657190.0639340.9761060.52987403:31
400.0586890.0801190.9697490.59262803:32
410.0657500.0701320.9731910.58487703:31
420.0635930.0776020.9734650.56367903:33
430.0744730.0692040.9731950.58942703:33
440.0626450.0642370.9758160.49126903:32
450.0553290.0678260.9763640.60238303:33
460.0601150.0637200.9772770.59655903:33
470.0772250.0682340.9765980.49024303:33
480.0547290.0706610.9736990.58642703:39
490.0592840.0859510.9718150.48608203:37
500.0608190.0638390.9771120.57571903:37
510.0683030.0695090.9736300.61956103:36
520.0558210.0646580.9761850.51561803:37
530.0507700.0646490.9757440.55097203:39
540.0578110.0672570.9752710.50318305:27
550.0648840.0775980.9730600.59600903:35
560.0571580.0691960.9736020.61629903:37
570.0755440.0622870.9778940.59041203:36
580.0599950.0642630.9764440.60460403:38
590.0530800.0658860.9755330.61264003:42
600.0582640.0749470.9765200.57025803:43
610.0536110.0716620.9710400.56013903:44
620.0591990.0644300.9765950.58456303:52
630.0576350.0627440.9769760.58275603:45
640.0601890.0664640.9766540.56223603:46
650.0477890.0620370.9773490.52572003:42
660.0517070.0634020.9779410.59117803:42
670.0542820.0627330.9776780.62180803:40
680.0582560.0647460.9758990.55665103:42
690.0546710.0613200.9774920.52082703:42
700.0583450.0597320.9777760.61090403:40
710.0519240.0604940.9781420.59548803:40
720.0585110.0611700.9775010.59462703:44
730.0540620.0618550.9767400.59813806:15
740.0540140.0617750.9773640.58879903:39
750.0647990.0634690.9770690.61577106:46
760.0483760.0614060.9788540.61249908:50
770.0569190.0619640.9772570.58184909:00
780.0622620.0605830.9772710.59416507:03
790.0519970.0594760.9780030.61845208:35
800.0513490.0597160.9783610.57465905:46
810.0453330.0593510.9788190.59541003:30
820.0558860.0590500.9779310.60574103:28
830.0480800.0598880.9781360.60816003:28
840.0489640.0611300.9775580.58432003:29
850.0493050.0568580.9785190.61877003:31
860.0480820.0608990.9778580.59104703:29
870.0486380.0607070.9786540.60172603:28
880.0424510.0603430.9785690.59793203:27
890.0521490.0605630.9778550.60918303:27
900.0425780.0613860.9780170.60207203:26
910.0490540.0594080.9781260.61085703:29
920.0464740.0623020.9772510.60328203:28
930.0456530.0595030.9784010.61410703:28
940.0425890.0604190.9782980.59661203:28
950.0484640.0595930.9783320.59852203:31
960.0524790.0612300.9782610.60954603:27
970.0477320.0589260.9786230.59409703:27
980.0413450.0604600.9778790.60650803:27
990.0480720.0597170.9782550.61786003:26

We have trained the model for a further 300 epochs to improve model performance. For the sake of time, the cell below is commented out.

# model.fit(300)

Visualize results in validation set

It's a good practice to see results of the model vis-a-vis ground truth. The code below picks random samples and shows us ground truths and model predictions, side by side. This enables us to preview the results of the model within the notebook.

unet.show_results(rows=2, alpha=0.9)
<Figure size 1000x1000 with 4 Axes>

Evaluate model performance

unet.accuracy()
0.17219623923301697

As we have 2 classes (1=permanent waterbodies and 2=flood water) for this segmentation task, we need to perform an accuracy assessment for each class. To achieve this, ArcGIS API for Python provides the per_class_metrics function that calculates a precision, recall, and f1 score for each class.

unet.per_class_metrics()
NoData12
precision0.9779140.0236800.006672
recall0.1691920.9725120.012667
f10.2884750.0462340.008741

Save the model

We will save the model that we trained as a 'Deep Learning Package' ('.dlpk' format). A Deep Learning Package is the standard format used to deploy deep learning models on the ArcGIS platform.

We will use the save() method to save the trained model. By default, it will be saved to the 'models' sub-folder within the training data folder.

unet.save('flood_model', publish=True)
Computing model metrics...
Published DLPK Item Id: 6b6aa1356ee64ef590f618c7d32eef92
PosixPath('/tmp/flood_inundation_mapping_using_sar_data_and_deep_learning/models/flood_model')

Model inferencing

Using ArcGIS Pro, we can use the trained model on a test image/area to classify permanent waterbodies and flood inundated areas in the SAR satellite image.

After training the UnetClassifier model and saving the weights for classifying images, we can use the Classify Pixels Using Deep Learning tool tool available in ArcGIS pro and ArcGIS Enterprise for inferencing.

flood_model = gis.content.get('86d5806943024257a8a15fe17296b19b')
flood_model
flood_model
Deep Learning Package by api_data_owner
Last Modified: February 09, 2022
0 comments, 92 views
raster_for_inferencing = gis.content.get('05a5c9ffd2044d2583ec1ae2a5712d54')
raster_for_inferencing

with arcpy.EnvManager(processorType="GPU"): out_classified_raster = arcpy.ia.ClassifyPixelsUsingDeepLearning("sentinel1_3band_inference_raster", "https://deldev.maps.arcgis.com/sharing/rest/content/items/86d5806943024257a8a15fe17296b19b", "padding 100;batch_size 8;predict_background True;tile_size 400", "PROCESS_AS_MOSAICKED_IMAGE", None); out_classified_raster.save(r"C:\Users\shi10484\Documents\ArcGIS\Projects\flood2\flood2.gdb\inferenced_results")

Results visualization

The classified output raster is generated using ArcGIS Pro. The output raster is published on the portal for visualization.

sar_ras2 = gis.content.get('427cd9a47eb544c59c1e965a56e72550')
inf_ras2 = gis.content.get('a7f2c8f23aa448d28fc14ec99b325ca8')
from arcgis.raster import colormap
inf_cmap2 = colormap(inf_ras2.layers[0], colormap=[[1, 7, 42, 108],[2, 0, 206, 209]])

Create map widgets

Three map widgets are created showing flood inundation in different regions.

map1 = gis.map('St Peters, USA', 11)
map1.basemap='satellite'
map2 = gis.map('St Peters, USA', 11)
map2.add_layer(sar_ras2)
map3 = gis.map()
map3.add_layer(sar_ras2)
map3.add_layer(inf_cmap2)

Set the map layout

from ipywidgets import HBox, VBox, Label, Layout
map1.sync_navigation(map2)
map2.sync_navigation(map3)

Hbox and Vbox were used to set the layout of map widgets.

hbox_layout = Layout()
hbox_layout.justify_content = 'space-around'

hb1,hb2=HBox([Label('True Colour Image'),Label('Sentinel-1 Imagery'),Label('Predictions'),]),\
                HBox([Label('True Colour Image'),Label('Sentinel-1 Imagery'),Label('Predictions')])
hb1.layout,hb2.layout=hbox_layout,hbox_layout

Flood inundation mapping

The resulting predictions are provided as a map for better visualization. The results show the spatial distribution of flood water in the Midwestern US during the 2019 floods. Sentinel-1 VV imagery of May 2019 are used for the analysis. In the map widgets, it can be seen that the trained UNetClassifier model is able to identify permanent waterbodies and flood water, as well as differentiate between the two. The deep blue color represents permanent waterbodies and the cyan color represents flood water.

VBox([hb1,HBox([map1,map2,map3])])

Three map widgets were created. The left widget displays natural color high resolution satellite imagery prior to flooding, the middle widget displays the sentinel-1 imagery during the flood event, and the right map widget displays the predictions of the trained UnetClassifier model. In the maps, St Louis city can be seen where the Illinois river and the Mississippi river converge. The model is able to identify river channels and differentiate from the flood water. The True Color Imagery can be used for visual interpretation for model accuracy.

Estimation of flood inundated area (sq. km)

The pixel size of the raster is required to calculate the area of flood inundated areas. We will use the <raster>.properties.pixelSizeX and <raster>.properties.pixelSizeY functions to find the Pixel size of the raster.

## Cellsize
ras2_cellsize_x = inf_ras2.layers[0].properties.pixelSizeX
ras2_cellsize_y = inf_ras2.layers[0].properties.pixelSizeY
print(ras2_cellsize_x, ras2_cellsize_y)
14.3472971497467 14.3472971497467

To calculate the area of land under flood water, we will use the <raster>.attribute_table() function to find the count of pixels per flood water class.

inf_ras2.layers[0].attribute_table()
{'fields': [{'name': 'Value',
   'type': 'esriFieldTypeInteger',
   'alias': 'Value',
   'sqlType': 'sqlTypeOther',
   'domain': None,
   'defaultValue': None},
  {'name': 'Count',
   'type': 'esriFieldTypeDouble',
   'alias': 'Count',
   'sqlType': 'sqlTypeOther',
   'domain': None,
   'defaultValue': None},
  {'name': 'Class',
   'type': 'esriFieldTypeString',
   'alias': 'Class',
   'sqlType': 'sqlTypeOther',
   'length': 1,
   'domain': None,
   'defaultValue': None}],
 'features': [{'attributes': {'Value': 1, 'Count': 1822630, 'Class': '1'}},
  {'attributes': {'Value': 2, 'Count': 5219135, 'Class': '2'}}]}

This study requires the calculation of the area of land under flood water in terms of square km. The raster uses the projected coordinate system (3857), which has pixels in meters.

## area in square kilometers
area_ras2_flood_water = (5219135*(ras2_cellsize_x*ras2_cellsize_y)/1000000)
area_ras2_flood_water
1074.3325074571271

Infrastructural inundation assessment

The inferenced raster will be used to assess the infrasruture inundated in flood water.

flood_raster = Raster("https://tiledimageservices6.arcgis.com/SMX5BErCXLM7eDtY/arcgis/rest/services/st_louis_flood_water/ImageServer",
                      gis=gis_ent,
                      engine="image_server")

The LULC raster for St. Louis is generated using the Land Cover Classification (Sentinel-2) pretrained model to assess the inundated areas per the LULC class.

lulc_raster = Raster("https://tiledimageservices6.arcgis.com/SMX5BErCXLM7eDtY/arcgis/rest/services/lulc_st_louis/ImageServer",
                        gis=gis_ent,
                        engine="image_server")

The flood raster will be converted to polygons using convert_raster_to_feature. We then use the import_data function to create a new feature layer containing the flood water polygons.

flood_poly = convert_raster_to_feature(flood_raster, 
                                    field='Value', 
                                    output_type='Polygon', 
                                    simplify=False, 
                                    output_name='flood_st_louis_poly'+str(datetime.datetime.now().microsecond), 
                                    gis=gis)

## Create dataframe from feature layer and get water polygons
dfm1 = flood_poly.layers[0].query('gridcode=2').sdf 

## Convert dataframe to feature layer
flood_poly = gis.content.import_data(dfm1, title='flood_water_poly'+str(datetime.datetime.now().microsecond))

Next, the LULC raster will be converted to polygons using convert_raster_to_feature. After getting the LULC polygons, we remove NODATA and Water polygons and use the import_data function to create a new feature layer containing the correct LULC polygons.

lulc_poly = convert_raster_to_feature(lulc_raster, 
                                    field='Value', 
                                    output_type='Polygon', 
                                    simplify=False, 
                                    output_name='lulc_st_louis_poly'+str(datetime.datetime.now().microsecond), 
                                    gis=gis)

## Create dataframe from feature layer and get water polygons
dfm2 = lulc_poly.layers[0].query('gridcode > 0 And gridcode < 5').sdf 

## Convert dataframe to feature layer
lulc_polygon = gis.content.import_data(dfm2, title='lulc_poly'+str(datetime.datetime.now().microsecond))

To get the LULC classes for the flood inundated areas, we will use the overlay_layers function.

inundated_lulc = overlay_layers(lulc_polygon.layers[0], 
                                flood_poly.layers[0], 
                                output_name='inundated_lulc'+str(datetime.datetime.now().microsecond),
                                gis=gis)
{"cost": 72.43}

After getting the LULC classes for the flood inundated areas, we will dissolve the polygons on the basis of gridcode. The output feature layer will have the combined area of each class in square miles units.

lulc_dissolve = dissolve_boundaries(inundated_lulc, 
                               dissolve_fields=['gridcode'], 
                               output_name='dissolved_lulc'+str(datetime.datetime.now().microsecond),
                               gis=gis,
                               multi_part_features=True)
{"cost": 9.685}

The resulting feature layer has a column for the area per class, but the corresponding LULC class name is missing. We will add the class names to the dataframe using the code below.

dfm4 = lulc_dissolve.layers[0].query().sdf
lulc_classes = ['Artificial surfaces', 'Agricultural areas', 'Forest and semi natural areas', 'Wetlands']
dfm4['LULC_Classes'] = lulc_classes
dfm5 = dfm4[['gridcode','AnalysisArea', 'LULC_Classes']].copy()
dfm5.rename(columns={'AnalysisArea': 'Area_in_square_miles'}, inplace=True)
dfm5
gridcodeArea_in_square_milesLULC_Classes
017.469743Artificial surfaces
12189.190674Agricultural areas
235.561292Forest and semi natural areas
3414.224226Wetlands

Conclusion

In this notebook, we have demonstrated how to use a UnetClassifier model with ArcGIS API for Python to extract flood water and permanent waterbodies. In Part 1, we covered how Sentinel-1 SAR data can be used for flood inundation mapping and monitoring. This process involved steps to prepare the input data, train a pixel-based classification model, visualize the results, generate accuracy metrics, and inferencing results on a test raster/area. Finally, in Part 2, we demonstrated the flood water inundated area in square kilometers and an infrastructural inundation assessment.

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.