Water Segmentation with OmniWaterMask¶
This notebook demonstrates water body segmentation using OmniWaterMask, a sensor-agnostic tool that combines deep learning, NDWI calculations, and OpenStreetMap reference data to detect water bodies in satellite and aerial imagery.
Key Features¶
- Sensor-agnostic — works with Sentinel-2, NAIP, Landsat, and other multispectral sensors
- Resolution range — supports 0.2m to 50m resolution imagery
- Multi-method fusion — combines deep learning, NDWI, and OSM data for robust detection
- Built-in vectorization — convert raster masks to smoothed vector polygons in one call
Install packages¶
Uncomment the following line to install the required packages.
# %pip install geoai-py omniwatermask smoothify
Import libraries¶
import geoai
Part A: Sentinel-2 Water Segmentation¶
Download Sentinel-2 data¶
Download a sample 6-band Sentinel-2 scene (B2, B3, B4, B8, B11, B12) from the Earth Surface Water Dataset.
s2_url = "https://huggingface.co/datasets/giswqs/s2-water-dataset/resolve/main/val_scene/S2A_L2A_20190318_N0211_R061_6Bands_S2.tif"
s2_path = geoai.download_file(s2_url)
Visualize input data¶
View the Sentinel-2 scene using a false-color composite (NIR, Red, Green — bands 4, 3, 2) for better water visibility. Water appears dark in this combination.
geoai.view_raster(s2_path, indexes=[4, 3, 2], vmax=3000)
Run water segmentation (raster output)¶
Use segment_water() with the "sentinel2" band order preset. This preset maps the 6-band Sentinel-2 composite (B2, B3, B4, B8, B11, B12) to the Red, Green, Blue, NIR channels expected by OmniWaterMask.
The function returns the path to the output water mask GeoTIFF.
s2_mask_path = geoai.segment_water(
s2_path,
band_order="sentinel2",
output_raster="s2_owm_water_mask.tif",
)
Visualize raster mask¶
View the predicted water mask overlaid on the input imagery.
geoai.view_raster(
s2_mask_path,
nodata=0,
basemap=s2_path,
opacity=0.5,
backend="ipyleaflet",
)
s2_gdf = geoai.segment_water(
s2_path,
band_order="sentinel2",
output_raster="s2_owm_water_mask.tif",
output_vector="s2_owm_water_bodies.geojson",
smooth=True,
smooth_iterations=3,
min_area=100,
)
Filter small artifacts¶
Remove small detected regions that are unlikely to be actual water bodies.
s2_filtered = s2_gdf[s2_gdf["area_m2"] > 100]
print(f"Water bodies detected: {len(s2_filtered)}")
print(f"Removed {len(s2_gdf) - len(s2_filtered)} small artifacts")
Visualize water body polygons¶
Display the detected water body polygons on an interactive map, colored by area.
geoai.view_vector_interactive(
s2_filtered,
column="area_m2",
tiles=s2_path,
)
Split map comparison¶
Create a side-by-side comparison between the detected water bodies and the original imagery.
geoai.create_split_map(
left_layer=s2_filtered,
right_layer=s2_path,
left_args={"style": {"color": "blue", "fillOpacity": 0.3}},
basemap=s2_path,
)
naip_url = "https://huggingface.co/datasets/giswqs/geospatial/resolve/main/naip/naip_water_test.tif"
naip_path = geoai.download_file(naip_url)
Visualize NAIP imagery¶
View the NAIP scene using a false-color composite (NIR, Red, Green — bands 4, 1, 2) for better water visibility. Water appears dark in this combination.
geoai.view_raster(naip_path, indexes=[4, 1, 2])
Run water segmentation¶
Use segment_water() with the "naip" band order preset. NAIP imagery has bands in R, G, B, NIR order, which maps directly to the expected input.
naip_gdf = geoai.segment_water(
naip_path,
band_order="naip",
output_raster="naip_owm_water_mask.tif",
output_vector="naip_owm_water_bodies.geojson",
smooth=True,
smooth_iterations=3,
min_area=100,
)
Visualize raster mask¶
View the predicted water mask overlaid on the NAIP imagery.
geoai.view_raster(
"naip_owm_water_mask.tif",
nodata=0,
basemap=naip_path,
opacity=0.5,
backend="ipyleaflet",
)
Filter small artifacts¶
naip_filtered = naip_gdf[naip_gdf["area_m2"] > 100]
print(f"Water bodies detected: {len(naip_filtered)}")
print(f"Removed {len(naip_gdf) - len(naip_filtered)} small artifacts")
Visualize water body polygons¶
Display the detected NAIP water body polygons on an interactive map, colored by area.
geoai.view_vector_interactive(
naip_filtered,
column="area_m2",
tiles=naip_path,
)
Split map comparison¶
geoai.create_split_map(
left_layer=naip_filtered,
right_layer=naip_path,
left_args={"style": {"color": "blue", "fillOpacity": 0.3}},
basemap=naip_path,
)
Water body area statistics¶
Analyze the distribution of water body sizes from the Sentinel-2 detection.
print(s2_filtered["area_m2"].describe())
s2_filtered["area_m2"].hist(bins=50)
import matplotlib.pyplot as plt
plt.xlabel("Area (m\u00b2)")
plt.ylabel("Count")
plt.title("Distribution of Water Body Areas (Sentinel-2)")
plt.show()
Summary¶
This notebook demonstrated:
- Water segmentation with Sentinel-2 using the
"sentinel2"band order preset - Water segmentation with NAIP using the
"naip"band order preset - Vectorization and smoothing of raster masks into natural-looking water body polygons
- Interactive visualization with split-map comparisons
- Area statistics for detected water bodies
OmniWaterMask Details¶
| Property | Value |
|---|---|
| Library | OmniWaterMask |
| Methods | Deep Learning + NDWI + OpenStreetMap |
| Resolution | 0.2m to 50m |
| Input | Red, Green, Blue, NIR bands |
| Output | Binary mask (0 = non-water, 1 = water) |
| Sensors | Sentinel-2, NAIP, Landsat, PlanetScope, Maxar, etc. |
Band Order Presets¶
| Preset | Band Order (RGBN) | Description |
|---|---|---|
"naip" |
[1, 2, 3, 4] | NAIP 4-band (R, G, B, NIR) |
"sentinel2" |
[3, 2, 1, 4] | Sentinel-2 6-band (B2, B3, B4, B8, B11, B12) |
"landsat" |
[4, 3, 2, 5] | Landsat 8/9 standard band order |
References¶
- OmniWaterMask: https://github.com/DPIRD-DMA/OmniWaterMask
- Smoothify: https://github.com/DPIRD-DMA/Smoothify
- Earth Surface Water Dataset: https://zenodo.org/records/5205674