Feature Categorization using Satellite Imagery and Deep Learning

  • 🔬 Data Science
  • 🥠 Deep Learning and Feature Classifier

Introduction and methodology

This sample notebook demonstrates the use of deep learning capabilities in ArcGIS to perform feature categorization. Specifically, we are going to perform automated damage assessment of homes after the devastating Woolsey fires. This is a critical task in damage claim processing, and using deep learning can speed up the process and make it more efficient. The workflow consists of three major steps: (1) extract training data, (2) train a deep learning feature classifier model, (3) make inference using the model.

Figure 1. Feature classification example

Methodology

Figure 2. Methodology

Part 1 - Export training data for deep learning

To export training data for feature categorization, we need two input data:

  • An input raster that contains the spectral bands,
  • A feature class that defines the location (e.g. outline or bounding box) and label of each building.

Import ArcGIS API for Python and get connected to your GIS

Input
import os
from pathlib import Path

import arcgis
from arcgis import GIS
from arcgis import learn, create_buffers
from arcgis.raster import analytics
from arcgis.features.analysis import join_features
from arcgis.learn import prepare_data, FeatureClassifier, classify_objects,  Model, list_models
arcgis.env.verbose = True
Input
gis = GIS('home')
gis_ent = GIS('https://pythonapi.playground.esri.com/portal', 'arcgis_python', 'amazing_arcgis_123')

Prepare data that will be used for training data export

A building footprints feature layer will be used to define the location and label of each building.

Input
building_footprints = gis.content.search('buildings_woolsey', item_type='Feature Layer Collection')[0]
building_footprints
Output
buildings_woolsey
buildings_woolseyFeature Layer Collection by api_data_owner
Last Modified: December 11, 2020
0 comments, 0 views

We will buffer the building footprints layer for 150m using create_buffers . With 150m buffer, when the training data will be exported it will cover the surrounding of houses which will help the model to understand the difference between damaged and undamaged houses.

Input
building_buffer = arcgis.create_buffers(building_footprints, 
                                        distances=[150],
                                        units='Meters', 
                                        dissolve_type='None', 
                                        ring_type='Disks', 
                                        side_type='Full', 
                                        end_type='Round', 
                                        output_name='buildings_buffer', 
                                        gis=gis)
building_buffer
Output
buildings buffer 150m
buildings buffer 150m Feature Layer Collection by arcgis_python
Last Modified: December 11, 2020
0 comments, 2 views

An aerial imagery of West Malibu will be used as input raster that contains the spectral bands. This raster will be used for exporting the training data and inferencing the results.

Input
gis2 = GIS("https://ivt.maps.arcgis.com")
Input
input_raster = gis2.content.search("111318_USAA_W_Malibu")[0]
input_raster
Output
111318_USAA_W_Malibu
Map Image Layer by romeroma
Last Modified: August 30, 2019
0 comments, 171 views

Specify a folder name in raster store that will be used to store our training data

Input
ds = analytics.get_datastores(gis=gis_ent)
ds
Output
<DatastoreManager for https://pythonapi.playground.esri.com/ra/admin>
Input
ds.search()
Output
[<Datastore title:"/fileShares/ListDatastoreContent" type:"folder">,
 <Datastore title:"/rasterStores/RasterDataStore" type:"rasterStore">]
Input
rasterstore = ds.get("/rasterStores/LocalRasterStore")
rasterstore
['Item not found.']
Input
samplefolder = "feature_classifier_sample"
samplefolder
Output
'feature_classifier_sample'

Export training data using arcgis.learn

Now ready to export training data using the export_training_data() method in arcgis.learn module. In addtion to feature class, raster layer, and output folder, we also need to specify a few other parameters such as tile_size (size of the image chips), stride_size (distance to move each time when creating the next image chip), chip_format (TIFF, PNG, or JPEG), metadata_format (how we are going to store those training labels). Note that unlike Unet and object detection, the metadata is set to be Labeled_Tiles here. More detail can be found here.

Depending on the size of your data, tile and stride size, and computing resources, this operation can take a while. In our experiments, this took 15mins~2hrs. Also, do not re-run it if you already run it once unless you would like to update the setting.

We will export the training data for a small sub-region of our study area and the whole study area will be used for inferencing of results. We will create a map widget, zoom in to the western corner of our study area and get the extent of the zoomed in map. We will use this extent in Export training data using deep learning function.

Input
# add the building_buffer layer in the webmap
m1 = gis.map('Malibu')
m1.add_layer(Building_buffer)
m1

Input
m1.extent
Output
{'spatialReference': {'latestWkid': 3857, 'wkid': 102100},
 'xmin': -13232751.181919832,
 'ymin': 4033059.637996407,
 'xmax': -13228050.304680169,
 'ymax': 4034970.5637035873}
Input
ext = {'spatialReference': {'latestWkid': 3857, 'wkid': 102100},
       'xmin': -13233716.541372407,
       'ymin': 4033550.84250738,
       'xmax': -13229015.664132744,
       'ymax': 4035461.76821456}
Input
export = learn.export_training_data(input_raster=input_raster_layer,
                                    output_location=samplefolder,
                                    input_class_data=building_label, 
                                    classvalue_field = "class_ecode",
                                    chip_format="PNG", 
                                    tile_size={"x":600,"y":600}, 
                                    stride_size={"x":0,"y":0}, 
                                    metadata_format="Labeled_Tiles",                                        
                                    context={"startIndex": 0, "exportAllTiles": False, "cellSize": 0.1, 'extent':ext},
                                    gis = gis_ent)

Part 2 - Model training

If you've already done part 1, you should already have the training chips. Please change the path to your own export training data folder that contains "images" and "labels" folder.

Input
training_data = gis.content.get('14f3f9421c6b4aa3bf224479c0eaa4f9')
training_data
Output
feature_categorization_using_satellite_imagery_and_deep_learning1
Image Collection by api_data_owner
Last Modified: December 10, 2020
0 comments, 10 views
Input
filepath = training_data.download(file_name=training_data.name)
Input
import zipfile
with zipfile.ZipFile(filepath, 'r') as zip_ref:
    zip_ref.extractall(Path(filepath).parent)
Input
data_path = Path(os.path.join(os.path.splitext(filepath)[0]))
Input
data = prepare_data(data_path, {1:'Damaged', 2:'Undamaged'}, chip_size=600, batch_size=16)

Visualize training data

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

Input
data.show_batch()

Load model architecture

Now the building classification problem has become a standard image classfication problem. By default arcgis.learn uses Resnet34 as its backbone model followed by a softmax layer.

Figure 2. Resnet34 architecture [1]
Input
model = FeatureClassifier(data)

Train a model through learning rate tuning and transfer learning

Learning rate is one of the most important hyperparameters in model training. Here we explore a range of learning rates to guide us to choose the best one.

Input
# The users can visualize the learning rate of the model with comparative loss. This case - loss rate decreases, so we are detecting more n more objects as it learns more
model.lr_find()

Based on the learning rate plot above, we can see that the loss going down most dramatically at 1e-2. Therefore, we set learning rate to be to 1e-2. Let's start with 10 epochs for the sake of time.

Input
model.fit(epochs=10, lr=1e-2)
Total time: 2:02:31

epoch train_loss valid_loss accuracy
1 0.188588 0.091087 0.976510
2 0.185616 0.066837 0.969799
3 0.195557 0.174460 0.919463
4 0.126604 0.049342 0.983221
5 0.131944 0.051884 0.984899
6 0.112607 0.052149 0.978188
7 0.101093 0.025041 0.996644
8 0.074809 0.031996 0.986577
9 0.058637 0.025428 0.989933
10 0.055728 0.023803 0.991611

Visualize classification results in validation set

Now we have the model, let's look at how the model performs.

Input
model.show_results(rows=4)

As we can see, with only 10 epochs, we are already seeing reasonable results. Further improvment can be acheived through more sophisticated hyperparameter tuning. Let's save the model for further training or inference later. The model should be saved into a models folder in your folder. By default, it will be saved into your data_path that you specified in the very beginning of this notebook.

Input
model.save('model-10', publish=True, gis=gis_ent)
Output
WindowsPath('D:/feature_classify_folder_100m_buffer/models/model-10')

Part 3 - Inference and post processing

Now we have the model ready, let's apply the model to a new feature class with a few new buildings and see how it performs.

Input
# Get your trained model
fc_model_package = gis_ent.content.search("model-10", item_type='Deep Learning Package')[0]
fc_model_package
Output
model-10
Deep Learning Package by api_data_owner
Last Modified: June 25, 2020
0 comments, 0 views

Now we are ready to install the model. Installation of the deep learning model item will unpack the model definition file, model file and the inference function script, and copy them to "trusted" location under the Raster Analytic Image Server site's system directory.

Input
fc_model = Model(fc_model_package)
Input
fc_model.install()
Input
fc_model.query_info()

We will use Classify Objects Using Deep Learning for inferencing the results. The parameters required to run the function are:

  • in_raster:The input raster dataset to classify. The input can be a single raster or multiple rasters in a mosaic dataset, an image service, or a folder of images.
  • out_feature_class: The output feature class that will contain geometries surrounding the objects from the input feature class, as well as a field to store the classification label.
  • in_model_definition: It contains the path to the deep learning binary model file, the path to the Python raster function to be used, and other parameters such as preferred tile size or padding.
  • in_features: input feature class represents a single object. If no input feature class is specified, the tool assumes that each input image contains a single object to be classified.
  • class_label_field: The name of the field that will contain the classification label in the output feature class. If no field name is specified, a new field called ClassLabel will be generated in the output feature class.
Input
inferenced_lyr = classify_objects(input_raster=input_raster,
                                  model = fc_model,
                                  model_arguments={'batch_size': 4}
                                  input_features=building_buffer,
                                  output_name="inferenced_layer_fc",
                                  class_value_field='status',
                                  context={'cellSize': 0.5, 'processorType':'GPU'},
                                  gis=gis_ent)
inferenced_lyr
Output
inferenced_layer_fc
inferenced_layer_fcFeature Layer Collection by api_data_owner
Last Modified: June 25, 2020
0 comments, 0 views

We can load the inference layer into a spatially enabled dataframe to examine the inference result. As we can see, all sample buildings are classified correctly with high confidence.

Input
import pandas as pd
from arcgis.features import GeoAccessor, GeoSeriesAccessor

sdf = pd.DataFrame.spatial.from_layer(inferenced_lyr.layers[0])
sdf[['objectid', 'type', 'status', 'confidence_classifyobjects']].head(10)
Output
objectid type status confidence_classifyobjects
0 1 Courtyard Undamaged 0.999948
1 2 Courtyard Undamaged 0.999177
2 3 Courtyard Undamaged 0.999988
3 4 Courtyard Undamaged 0.999957
4 5 Courtyard Undamaged 0.999875
5 6 Courtyard Undamaged 0.999691
6 7 Courtyard DAMAGED 0.990788
7 8 Courtyard Undamaged 0.954395
8 9 Courtyard Undamaged 0.999292
9 10 Courtyard Undamaged 0.999616

The next step is to join the inferenced_lyr with building_footprints so that the building_footprints layer will have the status column. We will use the output layer of join features function for visualization of results in map widget.

Input
final_lyr = arcgis.features.analysis.join_features(building_footprints, 
                                                   inferenced_lyr, 
                                                   attribute_relationship=['{"targetField":"building_i","operator":"equal","joinField":"building_i"}'],
                                                   join_operation='JoinOneToOne',  
                                                   output_name='bfootprint_withstatus1',  
                                                   gis=gis)
final_lyr
Output
bfootprint_withstatus1
bfootprint_withstatus1Feature Layer Collection by api_data_owner
Last Modified: June 25, 2020
0 comments, 5 views

We can see the results in the webmap, click on the webmap a new tab will open which will show the classified building footprint layer overlayed on the aerial imagery. You can also add your inferenced layer to this webmap.

Input
webmap = gis.content.search('woolsey_building_damage', outside_org=True)[0]
webmap
Output
woolsey_building_damage
woolsey_building_damageWeb Map by api_data_owner
Last Modified: June 25, 2020
0 comments, 5 views

Conclusion

In this notebook, we have covered a lot of ground. In part 1, we discussed how to export training data for deep learning using ArcGIS python API. In part 2, we demonstrated how to prepare the input data, train a feature classifier model, visualize the results, as well as apply the model to an unseen image. Then we covered how to installation of model, inferencing and post processing of inferenced results to make it production-ready in part 3. The same workflow can be applied to many other use cases. For example, when we know the locations of swimming pools, we can use it to indentify which ones are dirty and not being properly maintained.

Reference

[1] Ruiz Pablo, Understanding and Visualizing ResNets, https://towardsdatascience.com/understanding-and-visualizing-resnets-442284831be8, Accessed 2 September 2019.

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