Object Detection from Remote Sensing Imagery with GeoAI¶
- 📓 Notebook: https://geoai.gishub.org/workshops/GeoAI_Workshop_2025
- 💻 GitHub: https://github.com/opengeos/geoai
🧭 Introduction¶
This notebook provides hands-on materials for using the GeoAI package for object detection in remote sensing imagery.
🗂️ Agenda¶
The workshop will guide you through the full pipeline of GeoAI for object detection in remote sensing imagery, including:
- 📦 Package installation
- ⬇️ Data download
- 🖼️ Data visualization
- 🧠 Model training
- 🔍 Model inference
- 🛠️ Post-processing
- 🌐 Real-world applications
⚙️ Prerequisites¶
- A Google Colab account (recommended for ease of setup)
- Basic familiarity with Python and geospatial data
📦 Package installation¶
You can install the required packages using either conda
or pip
:
Option 1: Using Conda (recommended for local environments)¶
conda create -n geoai python=3.12
conda activate geoai
conda install -c conda-forge mamba
mamba install -c conda-forge geoai
Option 2: Using pip (for Colab or quick installation)¶
In [ ]:
Copied!
# %pip install geoai-py overturemaps
# %pip install geoai-py overturemaps
In [ ]:
Copied!
import geoai
import geoai
Retrieve collections¶
Get all STAC collections from Microsoft Planetary Computer.
In [ ]:
Copied!
collections = geoai.pc_collection_list()
collections
collections = geoai.pc_collection_list()
collections
Search NAIP imagery¶
In [ ]:
Copied!
m = geoai.Map(center=[47.653010, -117.592167], zoom=16)
m
m = geoai.Map(center=[47.653010, -117.592167], zoom=16)
m
In [ ]:
Copied!
bbox = m.user_roi_bounds()
if bbox is None:
bbox = [-117.6021, 47.6502, -117.5824, 47.6559]
bbox = m.user_roi_bounds()
if bbox is None:
bbox = [-117.6021, 47.6502, -117.5824, 47.6559]
In [ ]:
Copied!
items = geoai.pc_stac_search(
collection="naip",
bbox=bbox,
time_range="2013-01-01/2024-12-31",
)
items = geoai.pc_stac_search(
collection="naip",
bbox=bbox,
time_range="2013-01-01/2024-12-31",
)
In [ ]:
Copied!
items
items
In [ ]:
Copied!
items[0]
items[0]
Visualize NAIP imagery¶
In [ ]:
Copied!
geoai.pc_item_asset_list(items[0])
geoai.pc_item_asset_list(items[0])
In [ ]:
Copied!
geoai.view_pc_item(item=items[0])
geoai.view_pc_item(item=items[0])
Download NAIP imagery¶
In [ ]:
Copied!
downloaded = geoai.pc_stac_download(
items[0], output_dir="data", assets=["image", "thumbnail"]
)
downloaded = geoai.pc_stac_download(
items[0], output_dir="data", assets=["image", "thumbnail"]
)
In [ ]:
Copied!
items[0]
items[0]
Search Landsat data¶
In [ ]:
Copied!
items = geoai.pc_stac_search(
collection="landsat-c2-l2",
bbox=bbox,
time_range="2023-07-01/2023-07-15",
query={"eo:cloud_cover": {"lt": 1}},
max_items=10,
)
items = geoai.pc_stac_search(
collection="landsat-c2-l2",
bbox=bbox,
time_range="2023-07-01/2023-07-15",
query={"eo:cloud_cover": {"lt": 1}},
max_items=10,
)
In [ ]:
Copied!
items
items
In [ ]:
Copied!
items[0]
items[0]
Visualize Landsat data¶
In [ ]:
Copied!
geoai.pc_item_asset_list(items[0])
geoai.pc_item_asset_list(items[0])
In [ ]:
Copied!
geoai.view_pc_item(item=items[0], assets=["red", "green", "blue"])
geoai.view_pc_item(item=items[0], assets=["red", "green", "blue"])
In [ ]:
Copied!
geoai.view_pc_item(item=items[0], assets=["nir08", "red", "green"])
geoai.view_pc_item(item=items[0], assets=["nir08", "red", "green"])
In [ ]:
Copied!
geoai.view_pc_item(
item=items[0],
expression="(nir08-red)/(nir08+red)",
rescale="-1,1",
colormap_name="greens",
name="NDVI Green",
)
geoai.view_pc_item(
item=items[0],
expression="(nir08-red)/(nir08+red)",
rescale="-1,1",
colormap_name="greens",
name="NDVI Green",
)
Download Landsat data¶
In [ ]:
Copied!
geoai.pc_stac_download(
items[0], output_dir="data", assets=["nir08", "red", "green", "blue"], max_workers=1
)
geoai.pc_stac_download(
items[0], output_dir="data", assets=["nir08", "red", "green", "blue"], max_workers=1
)
Download building data¶
In [ ]:
Copied!
buildings_gdf = geoai.get_overture_data(
overture_type="building",
bbox=bbox,
output="data/buildings.geojson",
)
buildings_gdf = geoai.get_overture_data(
overture_type="building",
bbox=bbox,
output="data/buildings.geojson",
)
In [ ]:
Copied!
buildings_gdf.head()
buildings_gdf.head()
Extract building statistics¶
In [ ]:
Copied!
stats = geoai.extract_building_stats(buildings_gdf)
print(stats)
stats = geoai.extract_building_stats(buildings_gdf)
print(stats)
🖼️ Data Visualization¶
Download sample datasets from Hugging Face¶
In [ ]:
Copied!
train_raster_url = (
"https://huggingface.co/datasets/giswqs/geospatial/resolve/main/naip_train.tif"
)
train_vector_url = "https://huggingface.co/datasets/giswqs/geospatial/resolve/main/naip_train_buildings.geojson"
test_raster_url = (
"https://huggingface.co/datasets/giswqs/geospatial/resolve/main/naip_test.tif"
)
train_raster_url = (
"https://huggingface.co/datasets/giswqs/geospatial/resolve/main/naip_train.tif"
)
train_vector_url = "https://huggingface.co/datasets/giswqs/geospatial/resolve/main/naip_train_buildings.geojson"
test_raster_url = (
"https://huggingface.co/datasets/giswqs/geospatial/resolve/main/naip_test.tif"
)
In [ ]:
Copied!
train_raster_path = geoai.download_file(train_raster_url)
train_vector_path = geoai.download_file(train_vector_url)
test_raster_path = geoai.download_file(test_raster_url)
train_raster_path = geoai.download_file(train_raster_url)
train_vector_path = geoai.download_file(train_vector_url)
test_raster_path = geoai.download_file(test_raster_url)
View metadata¶
In [ ]:
Copied!
geoai.print_raster_info(train_raster_path, figsize=(18, 10))
geoai.print_raster_info(train_raster_path, figsize=(18, 10))
In [ ]:
Copied!
geoai.print_vector_info(train_vector_path, figsize=(18, 10))
geoai.print_vector_info(train_vector_path, figsize=(18, 10))
Interactive visualization¶
In [ ]:
Copied!
geoai.view_vector_interactive(train_vector_path, tiles=train_raster_url)
geoai.view_vector_interactive(train_vector_path, tiles=train_raster_url)
In [ ]:
Copied!
geoai.view_vector_interactive(
train_vector_path,
style_kwds={"color": "red", "fillOpacity": 0},
tiles=train_raster_url,
)
geoai.view_vector_interactive(
train_vector_path,
style_kwds={"color": "red", "fillOpacity": 0},
tiles=train_raster_url,
)
In [ ]:
Copied!
geoai.view_vector_interactive(train_vector_path, tiles="Satellite")
geoai.view_vector_interactive(train_vector_path, tiles="Satellite")
In [ ]:
Copied!
geoai.view_raster(test_raster_url)
geoai.view_raster(test_raster_url)
In [ ]:
Copied!
out_folder = "output"
tiles = geoai.export_geotiff_tiles(
in_raster=train_raster_path,
out_folder=out_folder,
in_class_data=train_vector_path,
tile_size=512,
stride=256,
buffer_radius=0,
)
out_folder = "output"
tiles = geoai.export_geotiff_tiles(
in_raster=train_raster_path,
out_folder=out_folder,
in_class_data=train_vector_path,
tile_size=512,
stride=256,
buffer_radius=0,
)
Train object detection model¶
In [ ]:
Copied!
geoai.train_MaskRCNN_model(
images_dir=f"{out_folder}/images",
labels_dir=f"{out_folder}/labels",
output_dir=f"{out_folder}/models",
num_channels=4,
pretrained=True,
batch_size=4,
num_epochs=10,
learning_rate=0.005,
val_split=0.2,
)
geoai.train_MaskRCNN_model(
images_dir=f"{out_folder}/images",
labels_dir=f"{out_folder}/labels",
output_dir=f"{out_folder}/models",
num_channels=4,
pretrained=True,
batch_size=4,
num_epochs=10,
learning_rate=0.005,
val_split=0.2,
)
🔍 Model inference¶
In [ ]:
Copied!
masks_path = "naip_test_prediction.tif"
model_path = f"{out_folder}/models/best_model.pth"
masks_path = "naip_test_prediction.tif"
model_path = f"{out_folder}/models/best_model.pth"
In [ ]:
Copied!
geoai.object_detection(
test_raster_path,
masks_path,
model_path,
window_size=512,
overlap=256,
confidence_threshold=0.5,
batch_size=4,
num_channels=4,
)
geoai.object_detection(
test_raster_path,
masks_path,
model_path,
window_size=512,
overlap=256,
confidence_threshold=0.5,
batch_size=4,
num_channels=4,
)
In [ ]:
Copied!
output_path = "naip_test_prediction.geojson"
buildings_gdf = geoai.raster_to_vector(masks_path, output_path)
buildings_gdf
output_path = "naip_test_prediction.geojson"
buildings_gdf = geoai.raster_to_vector(masks_path, output_path)
buildings_gdf
In [ ]:
Copied!
geoai.view_vector_interactive(buildings_gdf, tiles=test_raster_url)
geoai.view_vector_interactive(buildings_gdf, tiles=test_raster_url)
Building regularization¶
In [ ]:
Copied!
regularized_gdf = geoai.regularize(
data=buildings_gdf,
simplify_tolerance=2.0,
allow_45_degree=True,
diagonal_threshold_reduction=30,
allow_circles=True,
circle_threshold=0.9,
)
regularized_gdf = geoai.regularize(
data=buildings_gdf,
simplify_tolerance=2.0,
allow_45_degree=True,
diagonal_threshold_reduction=30,
allow_circles=True,
circle_threshold=0.9,
)
In [ ]:
Copied!
geoai.view_vector_interactive(regularized_gdf, tiles=test_raster_url)
geoai.view_vector_interactive(regularized_gdf, tiles=test_raster_url)
In [ ]:
Copied!
geoai.create_split_map(
left_layer=regularized_gdf,
right_layer=test_raster_url,
left_label="Regularized Buildings",
right_label="NAIP Imagery",
left_args={"style": {"color": "red", "fillOpacity": 0.3}},
basemap=test_raster_url,
)
geoai.create_split_map(
left_layer=regularized_gdf,
right_layer=test_raster_url,
left_label="Regularized Buildings",
right_label="NAIP Imagery",
left_args={"style": {"color": "red", "fillOpacity": 0.3}},
basemap=test_raster_url,
)
Result comparison¶
In [ ]:
Copied!
m = geoai.Map()
m.add_cog_layer(test_raster_url, name="NAIP")
m.add_gdf(
buildings_gdf,
style={"color": "yellow", "fillOpacity": 0},
layer_name="Original",
info_mode=None,
)
m.add_gdf(
regularized_gdf,
style={"color": "red", "fillOpacity": 0},
layer_name="Regularized",
info_mode=None,
)
legend = {
"Original": "#ffff00",
"Regularized": "#ff0000",
}
m.add_legend(title="Building Footprints", legend_dict=legend)
m
m = geoai.Map()
m.add_cog_layer(test_raster_url, name="NAIP")
m.add_gdf(
buildings_gdf,
style={"color": "yellow", "fillOpacity": 0},
layer_name="Original",
info_mode=None,
)
m.add_gdf(
regularized_gdf,
style={"color": "red", "fillOpacity": 0},
layer_name="Regularized",
info_mode=None,
)
legend = {
"Original": "#ffff00",
"Regularized": "#ff0000",
}
m.add_legend(title="Building Footprints", legend_dict=legend)
m
Calculate geometric properties¶
In [ ]:
Copied!
props_gdf = geoai.add_geometric_properties(
regularized_gdf, area_unit="m2", length_unit="m"
)
props_gdf.head()
props_gdf = geoai.add_geometric_properties(
regularized_gdf, area_unit="m2", length_unit="m"
)
props_gdf.head()
In [ ]:
Copied!
geoai.view_vector_interactive(props_gdf, column="area_m2", tiles=test_raster_url)
geoai.view_vector_interactive(props_gdf, column="area_m2", tiles=test_raster_url)
Save results¶
In [ ]:
Copied!
props_gdf.to_file("naip_test_buildings.geojson")
props_gdf.to_file("naip_test_buildings.geojson")
🌐 Real-world applications¶
The section demonstrates how to apply pre-trained models to real-world scenarios.
Building footprint extraction¶
In [ ]:
Copied!
test_raster_url = (
"https://huggingface.co/datasets/giswqs/geospatial/resolve/main/naip_test.tif"
)
test_raster_url = (
"https://huggingface.co/datasets/giswqs/geospatial/resolve/main/naip_test.tif"
)
In [ ]:
Copied!
test_raster_path = geoai.download_file(test_raster_url)
test_raster_path = geoai.download_file(test_raster_url)
In [ ]:
Copied!
geoai.view_raster(test_raster_url)
geoai.view_raster(test_raster_url)
In [ ]:
Copied!
masks_path = "buildings_prediction.tif"
geoai.object_detection(
test_raster_path,
masks_path,
model_path="building_footprints_usa.pth",
window_size=512,
overlap=256,
confidence_threshold=0.5,
batch_size=4,
num_channels=3,
)
masks_path = "buildings_prediction.tif"
geoai.object_detection(
test_raster_path,
masks_path,
model_path="building_footprints_usa.pth",
window_size=512,
overlap=256,
confidence_threshold=0.5,
batch_size=4,
num_channels=3,
)
In [ ]:
Copied!
output_path = "buildings_prediction.geojson"
buildings_gdf = geoai.raster_to_vector(masks_path, output_path)
regularized_gdf = geoai.regularize(
data=buildings_gdf,
simplify_tolerance=2.0,
allow_45_degree=True,
diagonal_threshold_reduction=30,
allow_circles=True,
circle_threshold=0.9,
)
props_gdf = geoai.add_geometric_properties(
regularized_gdf, area_unit="m2", length_unit="m"
)
output_path = "buildings_prediction.geojson"
buildings_gdf = geoai.raster_to_vector(masks_path, output_path)
regularized_gdf = geoai.regularize(
data=buildings_gdf,
simplify_tolerance=2.0,
allow_45_degree=True,
diagonal_threshold_reduction=30,
allow_circles=True,
circle_threshold=0.9,
)
props_gdf = geoai.add_geometric_properties(
regularized_gdf, area_unit="m2", length_unit="m"
)
In [ ]:
Copied!
geoai.view_vector_interactive(props_gdf, tiles=test_raster_url)
geoai.view_vector_interactive(props_gdf, tiles=test_raster_url)
Solar panel detection¶
In [ ]:
Copied!
test_raster_url = "https://huggingface.co/datasets/giswqs/geospatial/resolve/main/solar_panels_davis_ca.tif"
test_raster_path = geoai.download_file(test_raster_url)
test_raster_url = "https://huggingface.co/datasets/giswqs/geospatial/resolve/main/solar_panels_davis_ca.tif"
test_raster_path = geoai.download_file(test_raster_url)
In [ ]:
Copied!
geoai.view_raster(test_raster_url)
geoai.view_raster(test_raster_url)
In [ ]:
Copied!
masks_path = "solar_panels_prediction.tif"
geoai.object_detection(
test_raster_path,
masks_path,
model_path="solar_panel_detection.pth",
window_size=400,
overlap=100,
confidence_threshold=0.4,
batch_size=4,
num_channels=3,
)
masks_path = "solar_panels_prediction.tif"
geoai.object_detection(
test_raster_path,
masks_path,
model_path="solar_panel_detection.pth",
window_size=400,
overlap=100,
confidence_threshold=0.4,
batch_size=4,
num_channels=3,
)
In [ ]:
Copied!
output_path = "solar_panels_prediction.geojson"
regularized_gdf = geoai.orthogonalize(masks_path, output_path, epsilon=2)
props_gdf = geoai.add_geometric_properties(
regularized_gdf, area_unit="m2", length_unit="m"
)
output_path = "solar_panels_prediction.geojson"
regularized_gdf = geoai.orthogonalize(masks_path, output_path, epsilon=2)
props_gdf = geoai.add_geometric_properties(
regularized_gdf, area_unit="m2", length_unit="m"
)
In [ ]:
Copied!
geoai.view_vector_interactive(props_gdf, tiles=test_raster_url)
geoai.view_vector_interactive(props_gdf, tiles=test_raster_url)
Car detection¶
In [ ]:
Copied!
test_raster_url = (
"https://huggingface.co/datasets/giswqs/geospatial/resolve/main/cars_test_7cm.tif"
)
test_raster_path = geoai.download_file(test_raster_url)
test_raster_url = (
"https://huggingface.co/datasets/giswqs/geospatial/resolve/main/cars_test_7cm.tif"
)
test_raster_path = geoai.download_file(test_raster_url)
In [ ]:
Copied!
geoai.view_raster(test_raster_url)
geoai.view_raster(test_raster_url)
In [ ]:
Copied!
masks_path = "cars_prediction.tif"
geoai.object_detection(
test_raster_path,
masks_path,
model_path="car_detection_usa.pth",
window_size=512,
overlap=256,
confidence_threshold=0.5,
batch_size=4,
num_channels=3,
)
masks_path = "cars_prediction.tif"
geoai.object_detection(
test_raster_path,
masks_path,
model_path="car_detection_usa.pth",
window_size=512,
overlap=256,
confidence_threshold=0.5,
batch_size=4,
num_channels=3,
)
In [ ]:
Copied!
output_path = "cars_prediction.geojson"
gdf = geoai.orthogonalize(masks_path, output_path, epsilon=2)
output_path = "cars_prediction.geojson"
gdf = geoai.orthogonalize(masks_path, output_path, epsilon=2)
In [ ]:
Copied!
geoai.view_vector_interactive(output_path, tiles=test_raster_url)
geoai.view_vector_interactive(output_path, tiles=test_raster_url)
In [ ]:
Copied!
geoai.create_split_map(
left_layer=output_path,
right_layer=test_raster_url,
left_args={"style": {"color": "red", "fillOpacity": 0.2}},
basemap=test_raster_url,
)
geoai.create_split_map(
left_layer=output_path,
right_layer=test_raster_url,
left_args={"style": {"color": "red", "fillOpacity": 0.2}},
basemap=test_raster_url,
)
Ship detection¶
In [ ]:
Copied!
test_raster_url = "https://huggingface.co/datasets/giswqs/geospatial/resolve/main/ships_sfo_test_15cm.tif"
test_raster_path = geoai.download_file(test_raster_url)
test_raster_url = "https://huggingface.co/datasets/giswqs/geospatial/resolve/main/ships_sfo_test_15cm.tif"
test_raster_path = geoai.download_file(test_raster_url)
In [ ]:
Copied!
geoai.view_raster(test_raster_url)
geoai.view_raster(test_raster_url)
In [ ]:
Copied!
masks_path = "ship_prediction.tif"
geoai.object_detection(
test_raster_path,
masks_path,
model_path="ship_detection.pth",
window_size=512,
overlap=256,
confidence_threshold=0.5,
batch_size=4,
num_channels=3,
)
masks_path = "ship_prediction.tif"
geoai.object_detection(
test_raster_path,
masks_path,
model_path="ship_detection.pth",
window_size=512,
overlap=256,
confidence_threshold=0.5,
batch_size=4,
num_channels=3,
)
In [ ]:
Copied!
output_path = "ship_prediction.geojson"
gdf = geoai.raster_to_vector(masks_path, output_path)
output_path = "ship_prediction.geojson"
gdf = geoai.raster_to_vector(masks_path, output_path)
In [ ]:
Copied!
geoai.view_vector_interactive(output_path, tiles=test_raster_url)
geoai.view_vector_interactive(output_path, tiles=test_raster_url)
Surface water mapping¶
In [ ]:
Copied!
test_raster_url = "https://huggingface.co/datasets/giswqs/geospatial/resolve/main/naip/naip_water_test.tif"
test_raster_path = geoai.download_file(test_raster_url)
test_raster_url = "https://huggingface.co/datasets/giswqs/geospatial/resolve/main/naip/naip_water_test.tif"
test_raster_path = geoai.download_file(test_raster_url)
In [ ]:
Copied!
geoai.view_raster(test_raster_url)
geoai.view_raster(test_raster_url)
In [ ]:
Copied!
masks_path = "water_prediction.tif"
geoai.object_detection(
test_raster_path,
masks_path,
model_path="water_detection.pth",
window_size=512,
overlap=128,
confidence_threshold=0.3,
batch_size=4,
num_channels=4,
)
masks_path = "water_prediction.tif"
geoai.object_detection(
test_raster_path,
masks_path,
model_path="water_detection.pth",
window_size=512,
overlap=128,
confidence_threshold=0.3,
batch_size=4,
num_channels=4,
)
In [ ]:
Copied!
output_path = "water_prediction.geojson"
gdf = geoai.raster_to_vector(
masks_path, output_path, min_area=1000, simplify_tolerance=1
)
gdf = geoai.add_geometric_properties(gdf)
output_path = "water_prediction.geojson"
gdf = geoai.raster_to_vector(
masks_path, output_path, min_area=1000, simplify_tolerance=1
)
gdf = geoai.add_geometric_properties(gdf)
In [ ]:
Copied!
geoai.view_vector_interactive(gdf, tiles=test_raster_url)
geoai.view_vector_interactive(gdf, tiles=test_raster_url)
In [ ]:
Copied!
geoai.create_split_map(
left_layer=gdf,
right_layer=test_raster_url,
left_args={"style": {"color": "red", "fillOpacity": 0.4}},
basemap=test_raster_url,
)
geoai.create_split_map(
left_layer=gdf,
right_layer=test_raster_url,
left_args={"style": {"color": "red", "fillOpacity": 0.4}},
basemap=test_raster_url,
)
Wetland mapping¶
In [ ]:
Copied!
test_raster_url = "https://huggingface.co/datasets/giswqs/geospatial/resolve/main/naip/m_4609932_nw_14_1_20100629.tif"
test_raster_path = geoai.download_file(test_raster_url)
test_raster_url = "https://huggingface.co/datasets/giswqs/geospatial/resolve/main/naip/m_4609932_nw_14_1_20100629.tif"
test_raster_path = geoai.download_file(test_raster_url)
In [ ]:
Copied!
geoai.view_raster(test_raster_url)
geoai.view_raster(test_raster_url)
In [ ]:
Copied!
geoai.object_detection(
test_raster_path,
masks_path,
model_path="wetland_detection.pth",
window_size=512,
overlap=256,
confidence_threshold=0.3,
batch_size=4,
num_channels=4,
)
geoai.object_detection(
test_raster_path,
masks_path,
model_path="wetland_detection.pth",
window_size=512,
overlap=256,
confidence_threshold=0.3,
batch_size=4,
num_channels=4,
)
In [ ]:
Copied!
output_path = "wetland_prediction.geojson"
gdf = geoai.raster_to_vector(
masks_path, output_path, min_area=1000, simplify_tolerance=1
)
gdf = geoai.add_geometric_properties(gdf)
output_path = "wetland_prediction.geojson"
gdf = geoai.raster_to_vector(
masks_path, output_path, min_area=1000, simplify_tolerance=1
)
gdf = geoai.add_geometric_properties(gdf)
In [ ]:
Copied!
geoai.view_vector_interactive(gdf, tiles=test_raster_url)
geoai.view_vector_interactive(gdf, tiles=test_raster_url)
In [ ]:
Copied!
geoai.create_split_map(
left_layer=gdf,
right_layer=test_raster_url,
left_args={"style": {"color": "red", "fillOpacity": 0.4}},
basemap=test_raster_url,
)
geoai.create_split_map(
left_layer=gdf,
right_layer=test_raster_url,
left_args={"style": {"color": "red", "fillOpacity": 0.4}},
basemap=test_raster_url,
)