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

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
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.

building_footprints = gis.content.search('buildings_woolsey', item_type='Feature Layer Collection')[0]
building_footprints
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.

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
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.

gis2 = GIS("https://ivt.maps.arcgis.com")
input_raster = gis2.content.search("111318_USAA_W_Malibu")[0]
input_raster
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

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

# add the building_buffer layer in the webmap
m1 = gis.map('Malibu')
m1.add_layer(Building_buffer)
m1
m1.extent
{'spatialReference': {'latestWkid': 3857, 'wkid': 102100},
 'xmin': -13232751.181919832,
 'ymin': 4033059.637996407,
 'xmax': -13228050.304680169,
 'ymax': 4034970.5637035873}
ext = {'spatialReference': {'latestWkid': 3857, 'wkid': 102100},
       'xmin': -13233716.541372407,
       'ymin': 4033550.84250738,
       'xmax': -13229015.664132744,
       'ymax': 4035461.76821456}
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.

training_data = gis.content.get('14f3f9421c6b4aa3bf224479c0eaa4f9')
training_data
feature_categorization_using_satellite_imagery_and_deep_learning1
Image Collection by api_data_owner
Last Modified: December 10, 2020
0 comments, 10 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]))
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.

data.show_batch()
<Figure size 1152x1152 with 16 Axes>

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]

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.

# 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()
<Figure size 432x288 with 1 Axes>

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.

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

epochtrain_lossvalid_lossaccuracy
10.1885880.0910870.976510
20.1856160.0668370.969799
30.1955570.1744600.919463
40.1266040.0493420.983221
50.1319440.0518840.984899
60.1126070.0521490.978188
70.1010930.0250410.996644
80.0748090.0319960.986577
90.0586370.0254280.989933
100.0557280.0238030.991611

Visualize classification results in validation set

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

model.show_results(rows=4)
<Figure size 1152x1152 with 16 Axes>

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.

model.save('model-10', publish=True, gis=gis_ent)
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.

# Get your trained model
fc_model_package = gis_ent.content.search("model-10", item_type='Deep Learning Package')[0]
fc_model_package
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.

fc_model = Model(fc_model_package)
fc_model.install()
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.
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
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.

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)
objectidtypestatusconfidence_classifyobjects
01CourtyardUndamaged0.999948
12CourtyardUndamaged0.999177
23CourtyardUndamaged0.999988
34CourtyardUndamaged0.999957
45CourtyardUndamaged0.999875
56CourtyardUndamaged0.999691
67CourtyardDAMAGED0.990788
78CourtyardUndamaged0.954395
89CourtyardUndamaged0.999292
910CourtyardUndamaged0.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.

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
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.

webmap = gis.content.search('woolsey_building_damage', outside_org=True)[0]
webmap
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.