We will design the complete system in this tutorial. GeoAI Workflow for extracting footprints of buildings from high resolution NAIP aerial images. Configuring the deep geospatial learning environment is first, then downloading vector and raster labels and inspecting spatial properties, before creating georeferenced images chips and segmentation Masks. Then, we train a UNet model using a ResNet 34 encoder. We evaluate the learning behavior of this model and then apply sliding window inference on an unknown scene. In addition to semantic segmentation we also explore other methods, such as converting predicted masks into regularized and cleaned building polygons. The pipeline is also extended to actual areas by using NAIP imagery and Overture Maps building labels.
Import os
Subprocess import
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
If you get an ImportError, it's because your import is not working.
print(">>> Installing geoai-py and friends (takes ~2-4 minutes on Colab)...")
pip_install(
[
"geoai-py",
"segmentation-models-pytorch",
"buildingregulariser",
]
)
try:
import geoai
In the absence of e.
raise SystemExit(
The 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."
)
Geopandas can be imported as GPD
Import matplotlib.pyplot into plt
Import numpy as an np
Import rasterio
Import torch
Import plotting_extent from rasterio.plot
Display import from IPython
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_deviceCFG =()
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,
}
The 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)
Label: def timed
"""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 Excl.
Import traceback
print(f"n[SKIPPED] {label}n{type(exc).__name__}: {exc}")
traceback.print_exc(limit=3)
Return None
H = "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)
If p is in the (train_raster train_vector test_raster), then:
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
Configure the environment and install GeoAI, deep learning libraries. Verify GPU compatibility. The central parameters are defined for the dataset generation, training of models, inference and optional post-processing stages. Then we create the directory for the work, specify the reusable utilities and download NAIP images and footprint labels.
Step2():
info = geoai.get_raster_info(TRAIN_RASTER)
Information about k and v():
print(f" {k:
In order to better understand the datasets, we examine their dimension, statistics and feature structure. Visualizing the labels of the buildings over aerial imagery, we create an interactive map to explore spatially. After dividing the original imagery into overlapping georeferenced tiles, we create matching raster masks to train models.
MODEL_DIR = os.path.join(WORK, "models_unet")
BEST_MODEL = os.path.join(MODEL_DIR, "best_model.pth")
Def step4():
geoai.train_segmentation_model(
images_dir=f"{TILES_DIR}/images",
labels_dir=f"{TILES_DIR}/labels",
output_dir=MODEL_DIR,
architecture=CFG["architecture"],
encoder_name=CFG["encoder"],
encoder_weights=CFG["encoder_weights"],
num_channels=CFG["num_channels"],
num_classes=CFG["num_classes"],
batch_size=CFG["batch_size"],
num_epochs=CFG["num_epochs"],
learning_rate=CFG["learning_rate"],
val_split=CFG["val_split"],
save_best_only=True,
early_stopping_patience=5,
verbose=True,
)
print(f"n best checkpoint: {BEST_MODEL}")
print(f" size: {os.path.getsize(BEST_MODEL) / 1e6:.1f} MB")
Best_Model
Step4: Timed"STEP 4 — Training {CFG['architecture']}/{CFG['encoder']} "
The n"for {CFG['num_epochs']} epochs")
Def Step5():
hist_path = os.path.join(MODEL_DIR, "training_history.pth")
geoai.plot_performance_metrics(
history_path=hist_path,
figsize=(15, 5),
verbose=True,
save_path=os.path.join(WORK, "training_curves.png"),
)
h = torch.load(hist_path, weights_only=False)
best_ep = int(np.argmax(h["val_iou"])) + 1
print(f"n best val IoU {max(h['val_iou']):.4f} at epoch {best_ep}")
print(" Reading the curves: val loss rising while train loss falls => overfitting;")
print(" both flat and high => underfitting (more epochs, bigger encoder, or more chips).")
timed(step5, "STEP 5 — Training diagnostics")
We then train the U-Net model to segment images and masks using ResNet-34. Configure the training with early stopping, validation splitting, checkpoint saving and performance monitoring. Then we load the history of training, plot learning curves and determine the epoch with the highest IoU for validation.
PRED_MASK = os.path.join(WORK, "test_prediction.tif")
PRED_PROB = os.path.join(WORK, "test_probability.tif")
Step6():
geoai.semantic_segmentation(
input_path=TEST_RASTER,
output_path=PRED_MASK,
model_path=BEST_MODEL,
architecture=CFG["architecture"],
encoder_name=CFG["encoder"],
num_channels=CFG["num_channels"],
num_classes=CFG["num_classes"],
window_size=CFG["window_size"],
overlap=CFG["overlap"],
batch_size=4,
probability_path=PRED_PROB,
)
geoai.print_raster_info(PRED_MASK, show_preview=False)
geoai.plot_prediction_comparison(
original_image=TEST_RASTER,
prediction_image=PRED_MASK,
titles=["NAIP test scene", "Predicted building mask"],
figsize=(16, 8),
prediction_colormap="viridis",
save_path=os.path.join(WORK, "prediction_comparison.png"),
)
with rasterio.open(PRED_MASK) as src:
The m value is src.read(1)
px = float(abs(src.transform.a) * abs(src.transform.e))
print(f" predicted building pixels: {int((m > 0).sum()):,} "
F"({100 * (m > 0).mean():.2f}% of scene, ~{(m > 0).sum() * px:,.0f} m2)")
timed(step6, "STEP 6 — Sliding-window inference on the test scene")
VEC_RAW = os.path.join(WORK, "buildings_raw.geojson")
VEC_ORTHO = os.path.join(WORK, "buildings_orthogonal.geojson")
VEC_FINAL = os.path.join(WORK, "buildings_final.geojson")
Step7():
Grouped = Geoai.region_groups
PRED_MASK,
connectivity=2,
min_size=50,
out_image=os.path.join(WORK, "test_prediction_cleaned.tif"),
)
clean_mask = os.path.join(WORK, "test_prediction_cleaned.tif")
raw = geoai.raster_to_vector(
clean_mask,
output_path=VEC_RAW,
threshold=0,
min_area=15,
simplify_tolerance=0.5,
)
print(f" raw polygons : {len(raw)}")
Ortho = Geoai.Orthogonalize
input_path=clean_mask,
output_path=VEC_ORTHO,
epsilon=1.5,
min_area=15,
)
print(f" orthogonalized : {len(ortho)}")
final = geoai.regularization(ortho, angle_tolerance=12, simplify_tolerance=0.4)
final = geoai.add_geometric_properties(
final, properties=["area", "perimeter", "solidity", "elongation", "orientation"]
)
final.to_file(VEC_FINAL, driver="GeoJSON")
print(f" final footprints : {len(final)}")
print(final.head())
If you want to know more about if "area" In final.columns
print("n footprint area stats (m2):")
print(final["area"].describe().round(1).to_string())
Axes = plt.subplots (1, 2, figsize= (16, 8).
with rasterio.open(TEST_RASTER) as src:
rgb= src.read[1, 2, 3]).transpose(1, 2, 0)
rgb= np.clip (rgb/ np.percentile()(rgb), 99), 0, 1
The plotting_extent() function returns the extent of the image.
for ax, g, t in zip(axes, [raw, final], ["Raw polygonization", "Orthogonalized + regularized"]):
ax.imshow(rgb, extent=ext)
g.plot(ax=ax, facecolor="none", edgecolor="red", linewidth=1.1)
ax.set_title(t)
ax.set_axis_off()
plt.tight_layout()
plt.show()
try:
display(geoai.view_vector_interactive(VEC_FINAL, layer_name="Predicted buildings"))
Except Exception
pass
Return final
FINAL_GDF = timed(step7, "STEP 7 — Vectorizing and regularizing the predicted footprints")
The sliding-window method is used to generate prediction and probability rasters for an unknown NAIP scene. To produce more clean building boundaries, we remove noisy areas, convert the predicted mask to vector polygons and normalize the footprint geometry. Also, we calculate geometric properties. We compare raw polygonized results to the regularized and orthogonalized outputs.
Def step8():
gt_raster = os.path.join(WORK, "train_gt_mask.tif")
geoai.vector_to_raster(
vector_path=TRAIN_VECTOR,
output_path=gt_raster,
reference_raster=TRAIN_RASTER,
fill_value=0,
all_touched=True,
dtype=np.uint8,
)
train_pred = os.path.join(WORK, "train_prediction.tif")
geoai.semantic_segmentation(
input_path=TRAIN_RASTER,
output_path=train_pred,
model_path=BEST_MODEL,
architecture=CFG["architecture"],
encoder_name=CFG["encoder"],
num_channels=CFG["num_channels"],
num_classes=CFG["num_classes"],
window_size=CFG["window_size"],
overlap=CFG["overlap"],
quiet=True,
)
metrics = geoai.calc_segmentation_metrics(
ground_truth=gt_raster,
prediction=train_pred,
num_classes=2,
metrics=["iou", "f1"],
)
print("n --- pixel-wise metrics (class 0 = background, class 1 = building) ---")
Items for k and v():
print(f" {k:
The segmentation model is evaluated by comparing it to the rasterized labels of real-world buildings. Calculating pixel-level IoU metrics, we visualize imagery, prediction, and the reference masks. Then, we use Grounding DINO/SAM for zero-shot segmentation of buildings using only text prompts.
Step10():
If not CFG["run_pretrained"]:
print(" disabled in CFG"( ) Return
extractor = geoai.BuildingFootprintExtractor(model_path="building_footprints_usa.pth")
gdf = extractor.process_raster(
TEST_RASTER,
output_path=os.path.join(WORK, "buildings_maskrcnn.geojson"),
batch_size=4,
confidence_threshold=0.5,
overlap=0.25,
mask_threshold=0.5,
min_object_area=100,
filter_edges=True,
)
If len(gdf), or None is gdf, then:
print(" no instances returned"( ) Return
print(f" building instances: {len(gdf)}")
reg = extractor.regularize_buildings(gdf, min_area=20, angle_threshold=15)
reg.to_file(os.path.join(WORK, "buildings_maskrcnn_regularized.geojson"), driver="GeoJSON")
extractor.visualize_results(TEST_RASTER, gdf=reg, figsize=(12, 12))
If FINAL_GDF does not equal None:
print(f"n your U-Net : {len(FINAL_GDF)} polygons")
print(f" pretrained R-CNN: {len(reg)} polygons")
print(" Different counts are expected: U-Net merges adjacent roofs, Mask R-CNN splits")
print(" them into instances. Pick the paradigm that matches your downstream question.")
geoai.empty_cache()
timed(step10, "STEP 10 — Pretrained Mask R-CNN instance segmentation")
Step 11():
If not CFG["run_real_aoi"]:
print(" disabled (set CFG['run_real_aoi'] = True to run; needs open internet)")
Return to the Homepage
bbox= (-83.9400. 35.9500. -83.9250. 35.9600).
items = geoai.pc_stac_search(
collection="naip",
bbox=list(bbox),
time_range="2021-01-01/2023-12-31",
max_items=3,
)
print(f" STAC items found: {len(items)}")
aoi_dir = os.path.join(WORK, "aoi")
tif = geoai.download_naip(bbox=bbox, output_dir=aoi_dir, max_items=1, preview=False)
print(f" NAIP: {tif}")
ovt = os.path.join(aoi_dir, "overture_buildings.geojson")
geoai.download_overture_buildings(bbox=bbox, output=ovt, overture_type="building")
print(f" Overture buildings: {ovt}")
print(geoai.extract_building_stats(ovt))
The tif file format is raster.[0] If isinstance (tif, list, tuple), else tif
geoai.export_geotiff_tiles(
in_raster=raster,
out_folder=os.path.join(aoi_dir, "tiles"),
in_class_data=ovt,
tile_size=512,
stride=256,
)
print(" AOI dataset ready — feed it to train_segmentation_model() exactly as in STEP 4.")
timed(step11, "STEP 11 — (optional) Real AOI: Planetary Computer NAIP + Overture Maps labels")
Def step12():
Outputs = [f for f in sorted(os.listdir(WORK))
if f.endswith((".tif", ".geojson", ".png", ".pth"))]
print(" artifacts produced:")
For f, outputs are:
print(f" {os.path.getsize(os.path.join(WORK, f)) / 1e6:8.2f} MB {f}")
zip_path = os.path.join(WORK, "geoai_results.zip")
subprocess.run(
["zip", "-qr", zip_path, ".", "-i", "*.geojson", "*.png", "*.tif", "-x", "*tiles*"],
cwd=WORK, check=False,
)
print(f"n bundle: {zip_path}")
If IN_COLAB
print(" Download it with: from google.colab import files; "
"files.download('%s')" % zip_path)
timed(step12, "STEP 12 — Results summary")
banner("DONE")
print("""
Next stop:
----------------
* Swap the head, keep the code: architecture="deeplabv3plus", encoder_name="efficientnet-b3"
(or any timm encoder) in train_segmentation_model().
* 4-band NAIP (RGB+NIR): num_channels=4 everywhere; the first conv is auto-adapted.
* Multi-class land cover: geoai.train_segmentation_landcover() + geoai.export_landcover_tiles(),
with DiceLoss / FocalLoss / TverskyLoss from geoai.landcover_train for class imbalance.
* Instance segmentation you train yourself: geoai.train_MaskRCNN_model() You can also read about the importance of a good quality eyewear
geoai.instance_segmentation(..., vectorize=True).
* Object detection with georeferenced boxes: geoai.train.object_detection() /
geoai.object_detection(text="cars"() is the open-vocabulary basis of DINO.
* Foundation models: geoai.prithvi_inference (NASA/IBM Prithvi), geoai.universat_inference,
geoai.DINOv3GeoProcessor for embeddings and similarity maps.
* Change detection: geoai.change_detection (torchange backends).
* Deploy: geoai.export_to_onnx() + geoai.onnx_semantic_segmentation()QGIS is a plugin that can be used to enhance QGIS.
Docs and notebooks: https://opengeoai.org | Book: https://book.opengeoai.org
""")
The U-Net model is compared with a pre-trained Mask R-CNN to identify individual buildings. We can optionally generate a real dataset using NAIP imagery downloaded from Microsoft Planetary Computer, and building labels matched from Overture Maps. The generated plots, vectors and model outputs are then compiled into a results package that can be downloaded or analyzed.
We have completed an end to end geospatial pipeline for deep learning that converts aerial imagery into structured data on building footprints. In order to refine raster geometries and generate useful spatial attributes, we refined the raster outputs and prepared training samples. We also trained and evaluated an orthogonalized segmentation model. We explored alternative extraction methods using zero-shot models, pretrained segmentation and pre-trained foundations. This helped us better understand the differences between custom-training, prompt-based detection and ready-to use models. The generated masks were packaged with the probability rasters and evaluation plots. This created a base that can be used for large-scale GeoAI, land-cover mapping and infrastructure detection.
Take a look at the Full Codes here. Also, feel free to follow us on Twitter Don’t forget about our 150k+ML SubReddit Subscribe now our Newsletter. Wait! Are you using Telegram? now you can join us on telegram as well.
Want to promote your GitHub repo, Hugging Face page, Product release or Webinar?? Connect with us
Sana Hassan is a dual-degree IIT Madras student and consulting intern with Marktechpost. She loves to apply technology and AI in order to solve real-world problems. Sana Hassan, an intern at Marktechpost and dual-degree student at IIT Madras is passionate about applying technology and AI to real-world challenges.

