A workflow for extracting building footprints from high-resolution NAIP aerial imagery has been detailed, combining U-Net, Grounding DINO, SAM, and Mask R-CNN. The process covers environment setup, data preparation, model training, and inference on unseen scenes.
In this article
Setup and Data
The tutorial begins by configuring the geospatial deep learning environment. It downloads raster imagery and vector labels, then inspects their spatial properties. The system generates georeferenced image chips and segmentation masks for training.
The following Python script handles the initial configuration:
import os
import subprocess
import sys
import time
import warnings
warnings.filterwarnings("ignore")
IN_COLAB = "google.colab" in sys.modules
def pip_install(packages, quiet=True):
"""Install packages with pip from inside the notebook process."""
cmd = [sys.executable, "-m", "pip", "install", "--upgrade"]
if quiet:
cmd.append("-q")
subprocess.run(cmd + list(packages), check=False)
try:
import geoai
except ImportError:
print(">>> Installing geoai-py and friends (takes ~2-4 minutes on Colab)...")
pip_install(
[
"geoai-py",
"segmentation-models-pytorch",
"buildingregulariser",
]
)
try:
import geoai
except Exception as e:
raise SystemExit(
f"Import failed after install ({e}).\n"
">> Runtime > Restart session, then re-run this cell. "
"The install is cached, so it will be fast the second time."
)
import geopandas as gpd
import matplotlib.pyplot as plt
import numpy as np
import rasterio
import torch
from rasterio.plot import plotting_extent
from IPython.display import display
print(f"geoai : {geoai.__version__}")
print(f"torch : {torch.__version__}")
print(f"CUDA available: {torch.cuda.is_available()}")
if torch.cuda.is_available():
print(f"GPU : {torch.cuda.get_device_name(0)}")
else:
print("!! No GPU detected. Training will still run but be much slower.")
print(" Colab: Runtime > Change runtime type > Hardware accelerator > T4 GPU")
DEVICE = geoai.get_device()
print(f"geoai device : {DEVICE}")
CFG = {
"tile_size": 512,
"stride": 256,
"buffer_radius": 0,
"architecture": "unet",
"encoder": "resnet34",
"encoder_weights": "imagenet",
"num_channels": 3,
"num_classes": 2,
"batch_size": 8,
"num_epochs": 12,
"learning_rate": 1e-3,
"val_split": 0.2,
"window_size": 512,
"overlap": 256,
"run_zero_shot": True,
"run_pretrained": True,
"run_real_aoi": False,
}
WORK = "/content/geoai_tutorial" if IN_COLAB else os.path.abspath("geoai_tutorial")
os.makedirs(WORK, exist_ok=True)
os.chdir(WORK)
print(f"working dir : {WORK}")
def banner(text):
print("\n" + "=" * 92 + f"\n {text}\n" + "=" * 92)
def timed(fn, label):
"""Run fn(), report wall time, never let one step kill the notebook."""
banner(label)
t0 = time.time()
try:
out = fn()
print(f"\n[OK] {label} — {time.time() - t0:.1f}s")
return out
except Exception as exc:
import traceback
print(f"\n[SKIPPED] {label}\n{type(exc).__name__}: {exc}")
traceback.print_exc(limit=3)
return None
HF = "https://huggingface.co/datasets/giswqs/geospatial/resolve/main"
train_raster_url = f"{HF}/naip_rgb_train.tif"
train_vector_url = f"{HF}/naip_train_buildings.geojson"
test_raster_url = f"{HF}/naip_test.tif"
def step1():
train_raster = geoai.download_file(train_raster_url)
train_vector = geoai.download_file(train_vector_url)
test_raster = geoai.download_file(test_raster_url)
for p in (train_raster, train_vector, test_raster):
print(f" {os.path.getsize(p) / 1e6:8.2f} MB {p}")
return train_raster, train_vector, test_raster
paths = timed(step1, "STEP 1 — Downloading sample NAIP imagery and building labels")
TRAIN_RASTER, TRAIN_VECTOR, TEST_RASTER = paths
The script installs the necessary libraries and checks for GPU availability. It defines configuration parameters for dataset creation, model training, and inference. A working directory is created, and the NAIP imagery along with building footprint labels are downloaded.
Inspection and Tiling
The next phase inspects the raster and vector datasets to understand their coordinate systems, dimensions, statistics, and feature structures. Building labels are visualised over the aerial imagery, and an interactive map is generated for spatial exploration.
Source imagery is then divided into overlapping georeferenced chips, with matching raster masks created for model training.
def step2():
info = geoai.get_raster_info(TRAIN_RASTER)
for k, v in info.items():
print(f" {k:<16}: {v}")
print("\n--- per-band statistics ---")
print(geoai.get_raster_stats(TRAIN_RASTER))
print("\n--- vector info ---")
vinfo = geoai.get_vector_info(TRAIN_VECTOR)
for k, v in vinfo.items():
print(f" {k:<16}: {v}")
gdf = gpd.read_file(TRAIN_VECTOR)
print(f"\n {len(gdf)} training buildings | CRS {gdf.crs}")
print(gdf.head(3))
geoai.view_vector(
gdf,
raster_path=TRAIN_RASTER,
outline_only=True,
edge_color="yellow",
outline_linewidth=0.8,
figsize=(11, 11),
title="NAIP training scene + building footprints",
)
try:
display(geoai.view_vector_interactive(TRAIN_VECTOR, layer_name="Buildings"))
except Exception as e:
print(f" (interactive map unavailable here: {e})")
return gdf
LABELS_GDF = timed(step2, "STEP 2 — Inspecting raster + vector data")
TILES_DIR = os.path.join(WORK, "tiles")
def step3():
stats = geoai.export_geotiff_tiles(
in_raster=TRAIN_RASTER,
out_folder=TILES_DIR,
in_class_data=TRAIN_VECTOR,
tile_size=CFG["tile_size"],
stride=CFG["stride"],
buffer_radius=CFG["buffer_radius"],
all_touched=True,
skip_empty_tiles=False,
quiet=False,
)
n_img = len(os.listdir(f"{TILES_DIR}/images"))
n_lbl = len(os.listdir(f"{TILES_DIR}/labels"))
print(f"\n chips: {n_img} images / {n_lbl} masks")
if isinstance(stats, dict):
tot = max(stats.get("total_tiles", n_img), 1)
print(f" tiles containing buildings: {stats.get('tiles_with_features')} "
f"({100 * stats.get('tiles_with_features', 0) / tot:.1f}%)")
print(f" foreground pixels: {stats.get('feature_pixels'):,}")
geoai.display_training_tiles(TILES_DIR, num_tiles=6, figsize=(18, 6))
return stats
TILE_STATS = timed(step3, "STEP 3 — Exporting image chips and label masks")
Training and Diagnostics
A U-Net semantic segmentation model is trained using a ResNet-34 encoder. The prepared image and mask tiles feed into the training process. Configuration includes validation splitting, early stopping, checkpoint saving, and performance monitoring.
After training, the history is loaded to plot learning curves. This identifies the epoch producing the highest validation Intersection over Union (IoU).




