Skip to main content

AI-ready multi-mission Earth-observation data cubes for machine-learning and deep-learning workflows.

Project description

geoai-datacubes

Turn raw satellite imagery into AI-ready data cubes — pick a place, pick a time, get clean training data.

License: MIT Python 3.11 tests BuckAI Observatory Status: Experimental Earth Observation

New here? Two ready-to-run paths get you started directly in Colab — no install, no credentials needed:

  • Data acquisition & pre-processing — the grand-tour notebook fetches live Columbus data and walks through every pipeline feature on the data side: every mission, AOI format, cloud masking, NaN handling, tiling, multi-mission fusion, and SLURM submission. Open in Colab

  • ML / DL on a data cube — the classification notebook trains and compares Logistic Regression, Random Forest, XGBoost, and a U-Net on a multi-modal fused cube across three Ohio cities, on any ESA WorldCover class you pick at the top (water, tree cover, cropland, built-up, …). Threshold tuning on val, a conditional spectral-index baseline (NDWI for water, NDVI for vegetation), and an unsupervised KMeans bonus. Cached weights for the default water target ship in the repo so a fresh Colab launch lands in ~5 minutes instead of ~30; other classes train from scratch in their full budget. Open in Colab

Also Colab-ready: a YOLO building-detection demo on NAIP (02_building_detection.ipynb) and an opengeos/geoai integration notebook (03_with_opengeos_geoai.ipynb). See Try the notebooks for all four.

Tip: GitHub's HTML sanitizer strips target="_blank" from links, so the badges above will navigate in the current tab. Middle-click (or Cmd-click on macOS, Ctrl-click on Windows/Linux) to open Colab in a new tab and keep this page where it is.


Quick links


What is this?

geoai-datacubes is an open-source tool from the BuckAI Observatory at The Ohio State University. It gives you ready-to-use pipelines that download satellite imagery for any region and time you choose, then pre-process it into AI-ready "data cubes" — cloud-filtered, normalized, tiled, augmented, and split into training/validation/test sets that you can feed straight into a machine learning model.

The BuckAI Observatory's mission is to provide easy-to-use AI tools and tutorials so that OSU staff, faculty, and students — especially newcomers — can accelerate their research instead of building everything from scratch. This repo is built for that audience: if you are a brand-new grad student who has never touched an HPC or a satellite API, you can still follow the steps below and produce a usable dataset.

What is an "AI-ready data cube"?

Raw satellite scenes are huge, messy files: different formats, cloudy pixels, inconsistent value ranges, and far too big to fit on a GPU. A data cube is that imagery cleaned up and reshaped into a tidy, stacked array — think of a deck of aligned image layers (the spectral bands) cut into small, equal-sized tiles. Because every tile is the same size, cloud-free, normalized to a common range, and pre-split into train/validation/test groups, you can load it directly into PyTorch or TensorFlow and start training. The cube does the boring, error-prone data prep so you can focus on the science.


What it does

  • Fetches satellite imagery for any region of interest and date range from four interchangeable providers — three of them with no credentials needed — plus a fifth direct_http path for missions outside any STAC catalogue (e.g. Hansen GFC on Google Cloud Storage). The default PROVIDER = "auto" routes each mission to its best free host (Element 84 Earth Search for Sentinel-2 and Copernicus DEM; Microsoft Planetary Computer for Sentinel-1 RTC, Landsat, ESA WorldCover, NAIP, and the newer MODIS / HLS / JRC-GSW / 3DEP / ALOS / USDA-CDL / LCMAP / IO-LULC / Chloris-Biomass additions). Optional commercial PlanetScope is supported through the Planet Orders API. The classical Sentinel Hub Process API path remains available for advanced use.
  • Twenty-six missions are first-class — 16 direct-observation (15 working + 1 stub Sentinel-5P) plus 10 derived products (8 working + 2 stubs GEDI-L4B and GEBCO). The widely-used core: Sentinel-2 L2A and L1C, Sentinel-1 RTC (SAR), Landsat 8 / 9 C2 L2, Copernicus DEM (GLO-30 and GLO-90), ESA WorldCover, PlanetScope 4-band and 8-band SuperDove, and NAIP (sub-metre US aerial imagery). Long-archive time-series, hydrology, and US-specific high-resolution terrain: MODIS Surface Reflectance and Land Surface Temperature (24-year archive at 500 m / 1 km), HLS Harmonized Landsat-Sentinel (30 m pre-harmonised optical), JRC Global Surface Water (30 m static water occurrence), and 3DEP (LIDAR-derived US DEM at 10 m). Forest / biomass / LULC additions: ALOS PALSAR L-band SAR, ALOS Forest / Non-Forest, Hansen Global Forest Change, USDA Cropland Data Layer, LCMAP CONUS, Impact Observatory annual LULC, and Chloris Aboveground Biomass. A documented Sentinel-5P TROPOMI stub covers atmospheric chemistry pending NetCDF reader support; GEDI-L4B and GEBCO are documented stubs awaiting their respective auth / NetCDF reader paths. Per-mission band tables, value ranges, and ML normalisation recipes are in docs/data_layers.md.
  • User-selectable band lists per mission — each fetch takes a BANDS_<mission> list, so you ask for exactly the channels your model needs (e.g. visible RGB + NIR for true-colour previews, or just B04 + B08 for fast NDVI workflows). The default fetches the mission's standard product plus any helper bands needed for cloud masking.
  • Multi-mission fusion onto a common UTM grid via fuse_response_tiffs(...). Fused cubes carry mission-prefixed band descriptions (Sentinel-2_B04, Sentinel-1_VV, Copernicus-DEM_DEM, ESA-WorldCover_LULC, …) so downstream code can pick exactly the bands it wants by name. See the Multi-mission fusion section below.
  • Robust pre-processing: smear-protected reprojection that prevents nodata edges from bleeding into valid pixels; polygon-aware Sentinel-1 mosaicking that composes adjacent same-day scenes when one scene does not cover the full AOI; cloud / shadow / haze masking through mission-aware QA bands (Sentinel-2 SCL, Landsat BQA, PlanetScope UDM2).
  • Configurable train / val / test splitting with four spatially-aware strategies — random, block, stripes, and regions (explicit per-split AOIs) — plus three NaN-handling modes (drop, interpolate, mask).
  • On-the-fly tile sampling for PyTorch via the LazyTileDataset class. Tile size, stride, augmentation, NaN-handling, and split assignment are runtime parameters; no tile files are ever materialised to disk. This decouples data preparation from experimentation — sweep hyperparameters without re-running the tiler.
  • Tiles to disk when you do want them — tile_geotiff(...) writes AI-ready GeoTIFF or PyTorch-tensor patches with reproducible metadata (every tile carries source-scene provenance plus per-tile parameters embedded as GeoTIFF tags). Augmentation supports flips, rotations, and DN-scale-aware Gaussian noise.
  • STAC catalogs can be built from any fetched / fused / tiled cube, so the data plays cleanly with the wider geospatial ecosystem.
  • Pedagogical notebooks, all Colab-ready:
    • a grand tour (notebooks/00_geoai_datacubes_tour.ipynb) walking through every pipeline feature on a multi-mission Columbus AOI;
    • an end-to-end land-cover classification notebook (notebooks/01_classification.ipynb) training Logistic Regression, Random Forest, XGBoost, and a lightweight U-Net on a fused cube against any ESA WorldCover class you pick at the top (water, tree cover, cropland, built-up, …), with NDVI / NDWI / NDMI sidebars and an unsupervised KMeans bonus;
    • a YOLO building-detection demo (notebooks/02_building_detection.ipynb) — the first object-detection notebook in the series, using NAIP 1 m imagery and Microsoft US Building Footprints across three Ohio cities.

Supported platforms

Platform Type Status Notes
Platform Mission name Type Default provider
--- --- --- :--:
Sentinel-2 L2A Sentinel-2 Optical surface reflectance earthsearch ✅ / sentinelhub ✅
Sentinel-2 L1C Sentinel-2-L1C Optical top-of-atmosphere earthsearch ✅
Sentinel-1 RTC Sentinel-1 SAR (radar) planetary_computer ✅ / sentinelhub ✅
Landsat 8-9 C2 L2 Landsat Optical surface reflectance + thermal planetary_computer ✅ / sentinelhub ✅
Copernicus DEM (GLO-30) Copernicus-DEM 30 m global elevation (static) earthsearch ✅ / planetary_computer ✅
ESA WorldCover ESA-WorldCover 10 m global land-cover (static, 2020 + 2021) planetary_computer ✅
PlanetScope (legacy 4-band) PlanetScope-4b Optical surface reflectance, ~3 m planet ✅
PlanetScope (8-band SuperDove) PlanetScope-8b Optical surface reflectance, ~3 m planet ✅
NAIP (US aerial imagery) NAIP Optical aerial, ~1 m (0.6 m for newer) planetary_computer ✅

Quickstart for beginners — no credentials needed

The default pipeline downloads imagery from free, public AWS Open-Data buckets via Element 84's Earth Search STAC API. You do not need an account, API key, or .env file to run it. Just clone, install, edit a few parameters, and go.

1. Clone the repository

git clone https://github.com/buckai-observatory/geoai-datacubes.git
cd geoai-datacubes

2. Create and activate a clean Python environment

We recommend mamba (a drop-in conda replacement that solves environments dramatically faster). The simplest install is the Miniforge distribution, which ships mamba pre-configured against the conda-forge channel.

mamba create -n geoai python=3.11 -y
mamba activate geoai

If you already have conda installed and prefer not to switch, substitute conda for mamba in the commands above.

3. Install the package

The package ships with a pyproject.toml that declares the core data pipeline as required and bundles everything else into named optional- dependency extras. Every dependency including geoai-py is on conda-forge, so the recommended path is a pure-mamba install from conda-forge with a single pip install -e . at the end for the local repo itself (which is not on conda-forge yet).

This avoids the libLerc / GDAL loader-chain breakage that pure-pip environments occasionally hit on macOS, and gives you conda-forge's CUDA-tested PyTorch builds on Linux clusters.

Recommended: all from conda-forge, plus pip install -e . for this repo

The single command below builds the full geoai-cubes environment used by all notebooks in this repo:

mamba create -y -n geoai-cubes -c conda-forge \
    python=3.11 \
    geoai-py leafmap torchgeo omniwatermask \
    rasterio gdal pyproj shapely \
    pystac pystac-client planetary-computer \
    "pytorch>=2.0" "torchvision>=0.15" \
    zarr lmdb scikit-image pillow \
    matplotlib numpy pandas tqdm requests \
    scikit-learn xgboost ultralytics transformers \
    jupyterlab ipywidgets seaborn geopandas contextily

mamba activate geoai-cubes
pip install -e .                  # geoai-datacubes itself (not on conda-forge yet)

bash smoke-tests/check_env.sh     # verify

This single env is enough for every notebook in the repo:

  • 00_geoai_datacubes_tour.ipynb — multi-mission tour
  • 01_classification.ipynb — RF / XGBoost / U-Net water classification
  • 02_building_detection.ipynb — YOLOv8 + OWLv2 + HF YOLO building detection
  • 03_with_opengeos_geoai.ipynb — the two-package interop demo

Slimmer installs (only the deps you'll actually use)

If you don't want the full ML / DL / notebook stack, drop the corresponding packages from the mamba create line. The four named slices in pyproject.toml are documented as pip install -e ".[<slice>]" recipes for users who already have a working conda env and just want to add the missing pieces:

pip install -e ".[ml]"            # + scikit-learn, XGBoost, Ultralytics YOLO, transformers
pip install -e ".[geoai]"         # + opengeos/geoai (Wu 2026, JOSS 11(118):9605)
pip install -e ".[notebooks]"     # + jupyterlab, ipywidgets, seaborn, geopandas, contextily
pip install -e ".[planet]"        # + Planet Orders + Sentinel Hub (commercial providers)
pip install -e ".[all]"           # everything above

The corresponding conda-forge names are: geoai-py leafmap torchgeo omniwatermask for [geoai]; scikit-learn xgboost ultralytics transformers huggingface_hub for [ml]; jupyterlab ipywidgets seaborn geopandas contextily for [notebooks]; python-dotenv sentinelhub for [planet]. The pip recipes are convenient when you already have a working conda env, and pin slightly faster on a few fast-moving ML packages (ultralytics, transformers).

Pip-only fallback (when conda / mamba isn't available)

python -m venv .venv && source .venv/bin/activate
pip install -e ".[all]"

This works in clean virtual environments but can fail mid-stream if a wheel-less GDAL stack can't compile from source (notably on macOS without Xcode CLT, or on minimal Linux images). When it does, fall back to the mamba path above — it's also faster on a cold cache.

pip install -e . is the editable / developer install. If you just want to use the package without modifying it, drop the -e. The flat requirements.txt file is preserved for tooling that doesn't read pyproject.toml extras (e.g. the smoke-tests/check_env.sh --pip import check).

4. Choose what to download

Open geoai_datacubes/main.py and edit the USER INPUT block at the top to describe the data you want:

# ---- USER INPUT ----
PROVIDER = "auto" # default: ES for S2, PC for S1/Landsat (all no-creds)
MISSION = "Sentinel-2" # "Sentinel-2", "Sentinel-2-L1C", "Sentinel-1", or "Landsat"
BANDS = None # None = mission default bands

# Area of interest -- the default is a ~5-mile square around OSU in Columbus, OH.
# Three other formats are supported; see "Defining the AOI" below.
AOI = {"bbox": [-83.077, 39.964, -82.983, 40.036]}
ROI = resolve_aoi(AOI)

TIME_RANGE = ("2024-06-15", "2024-06-20") # start, end date
RESOLUTION = 10 # metres per pixel
MAX_CLOUD = 0.10 # keep scenes under 10% cloud cover
TILE_SIZE = 256
SPLIT = (0.8, 0.1, 0.1) # train / val / test fractions

Leaving BANDS = None picks sensible defaults per mission (Red+NIR for optical, VV+VH for radar) and auto-adds the cloud/quality bands. To run Landsat instead, just set MISSION = "Landsat" — everything else stays the same.

More band-selection examples

The BANDS argument takes a Python list of band names (case-sensitive, matching the table in docs/data_layers.md). Four common patterns:

# 1. Default behaviour (None) -- mission defaults plus helper bands.
#    For Sentinel-2 L2A this yields B04 + B08 (Red, NIR) plus SCL / AOT / WVP
#    so per-pixel cloud masking and atmospheric correction inputs are present.
BANDS = None

# 2. Just NDVI inputs -- minimal, fastest fetch (~2 bands).
BANDS = ["B04", "B08"]

# 3. True-colour RGB + NIR + SCL for cloud masking. This is what
#    notebook 01 uses as its headline Sentinel-2 set.
BANDS = ["B02", "B03", "B04", "B08", "SCL"]

# 4. All 12 spectral bands plus the three atmospheric helpers --
#    the maximum-information Sentinel-2 L2A fetch.
BANDS = ["B01", "B02", "B03", "B04", "B05", "B06", "B07",
         "B08", "B8A", "B09", "B11", "B12",
         "SCL", "AOT", "WVP"]

Fetching more than one mission

main.py runs one mission per execution. To pull several missions over the same AOI, run the script multiple times (each writes its own <Mission>_full_size.tiff into a per-scene folder under data/):

# In one terminal session or shell loop:
for MISSION in ["Sentinel-2", "Sentinel-1", "Landsat",
                "Copernicus-DEM", "ESA-WorldCover"]:
    # set MISSION, BANDS, etc. in main.py (or pass via environment)
    # then run:  python main.py
    ...

Once each mission has its own <Mission>_full_size.tiff on disk, the Multi-mission fusion step below stacks them onto a common UTM grid for ML.

Defining the AOI

AOI is a small dict. Pick one of four formats:

Format Example Use when
Rectangular bbox {"bbox": [lon_min, lat_min, lon_max, lat_max]} You already have the corners in WGS84.
Polygon file {"shapefile": "/path/to/aoi.shp"} (or .gpkg, .geojson) You have an existing polygon. Requires geopandas. The polygon's bounding box is used.
Square around a point {"center": (40.0067, -83.0305), "side_miles": 5} You know roughly where, just want a square AOI of size N miles.
Native S2 tile around a point {"tile_around": (40.0067, -83.0305)} Quickest first look — returns the full ~100×100 km MGRS tile containing the point.

5. Run the pipeline

# From the repo root:
python -m geoai_datacubes.main

The pipeline will find the least-cloudy scene, download it, mask clouds, compute NDVI, cut the scene into tiles, split them into train/val/test, and export GPU-ready datasets. Outputs land in the data/ folder.

Want to see results without downloading anything first? Skip straight to the example notebook, which runs on bundled sample data.


Data providers — when to use which

The pipeline supports twenty-six missions end-to-end. The full per-mission reference (bands, native resolutions, value ranges, normalisation recipes, tile-seam caveats) lives in docs/data_layers.md; this section covers a widely-used subset and the four interchangeable providers that serve them, plus a fifth direct_http path for missions that don't live in any STAC catalogue (e.g. Hansen GFC). The default is "auto", which routes each mission to the best free option. (The table below is the capability matrix; for a throughput discussion — which provider wins for a Colab demo vs which one wins for a continental-scale workflow — see docs/providers.md.)

PROVIDER = "earthsearch" PROVIDER = "planetary_computer" PROVIDER = "planet" (commercial) PROVIDER = "sentinelhub" (advanced)
Credentials None None PL_API_KEY in .env Free Sentinel Hub OAuth in a .env
Hosted by Element 84 (AWS Open Data) Microsoft Planetary Computer (Azure) Planet Labs Data + Orders API Sentinel Hub Process API
Sentinel-2 L2A ✅ Fast (no sign step)
Sentinel-2 L1C (not wired)
Sentinel-1 ⚠️ Raw GRD only — ground-range, no native CRS (unusable as-is) RTC — terrain-corrected & georeferenced
Landsat 8-9 C2 L2 ⚠️ usgs-landsat bucket is requester-pays (anonymous reads fail) ✅ Same data, served free
PlanetScope (3 m) ✅ 4-band legacy + 8-band SuperDove SR + UDM2
NAIP (1 m US aerial)
Server-side band math No No Server-side clip-to-AOI Yes (evalscripts)
Best for Sentinel-2 (skip the per-asset sign step) Sentinel-1 RTC, Landsat, NAIP, and every newer addition below High-res commercial PlanetScope; users with Planet/NICFI/Education access Production runs, custom band math, very large ROIs

Newer additions broadening the package — served by Microsoft Planetary Computer (with one direct_http exception, called out below), no alternative provider yet:

  • MODIS_SR and MODIS_LST — 500 m / 1 km daily-equivalent, 24-year archive; the workhorse for time-series, phenology, climate baselines.
  • HLS_S30 and HLS_L30 — 30 m pre-harmonised Landsat + Sentinel-2, so you don't have to harmonise yourself.
  • JRC-GSW — 30 m static global surface water (occurrence, seasonality, extent, transitions).
  • 3DEP — 10 m (or 30 m) LIDAR-derived US DEM; the US-specific complement to Copernicus DEM.
  • Copernicus-DEM-90 — 90 m global DEM; the coarser companion to GLO-30, useful for continental-scale workflows or coastal AOIs where GLO-30 has interferometric artefacts.
  • ALOS-PALSAR and ALOS-FNF — 25 m annual L-band SAR backscatter and a derived forest / non-forest mask; the standard inputs for forest-biomass studies.
  • USDA-CDL — 30 m annual US cropland classification (~100 crop classes).
  • LCMAP-CONUS — 30 m annual US LULC (1985–2021), the longest US LULC time series in the registry.
  • IO-LULC — 10 m annual global LULC (2017–2023; Impact Observatory + Esri).
  • Chloris-Biomass — ~4.6 km annual global aboveground biomass (CC-BY-NC-SA).
  • Hansen-GFC — 30 m annual forest-change raster (2000–2023); the first mission served through the direct_http provider (anonymous Google Cloud Storage COGs, no STAC).
  • Sentinel-5P — atmospheric chemistry (NO2, CO, SO2, CH4, O3, HCHO, …); stub only, pending NetCDF reader support.
  • GEDI-L4B and GEBCO — documented stubs; GEDI needs NASA Earthdata Login wiring, GEBCO needs a download-and-cache extension to the direct_http fetcher.

See docs/data_layers.md for the full bands and normalisation recipes for these.

PROVIDER = "auto" (the default) wires this up for you automatically:

Mission Routed to Why
Sentinel-2 / Sentinel-2-L1C earthsearch Faster — no per-asset SAS sign step
Sentinel-1 planetary_computer Gives you the analysis-ready RTC product
Landsat / Landsat-8 / Landsat-9 planetary_computer Avoids usgs-landsat's requester-pays bucket
Copernicus-DEM earthsearch Both work; ES skips the sign step
Copernicus-DEM-90 planetary_computer PC-only
ESA-WorldCover planetary_computer Earth Search does not host WorldCover
NAIP planetary_computer PC is the only public host for NAIP
MODIS_SR / MODIS_LST / HLS_S30 / HLS_L30 / JRC-GSW / 3DEP planetary_computer PC-only for these missions
ALOS-PALSAR / ALOS-FNF / USDA-CDL / LCMAP-CONUS / IO-LULC / Chloris-Biomass planetary_computer PC-only
Hansen-GFC direct_http Non-STAC anonymous Google Cloud Storage COGs
PlanetScope-4b / PlanetScope-8b not auto-routed Commercial — opt in explicitly with PROVIDER="planet" and a key in .env
Sentinel-5P / GEDI-L4B / GEBCO not routed (stubbed) Sentinel-5P needs an xarray-based NetCDF reader; GEDI-L4B needs NASA Earthdata Login auth; GEBCO needs a download-and-cache extension to the direct_http fetcher

The output <Mission>_full_size.tiff is functionally identical regardless of provider; the rest of the pipeline (cloud masking, NDVI, tiling, export) doesn't care which one was used.

Switching to the Sentinel Hub provider

If you need the advanced features above, opt in by:

  1. Register for a free account at the Copernicus Data Space Ecosystem.
  2. Open the Sentinel Hub dashboard at https://shapps.dataspace.copernicus.eu/dashboard/ and go to User settings → OAuth clients → Create new. Copy the client ID and client secret somewhere safe.
  3. Copy the bundled template and paste in your keys:
cp .env.example .env # then open .env in your editor
SH_CLIENT_ID=your-client-id-here
SH_CLIENT_SECRET=your-client-secret-here
SH_INSTANCE_ID= # optional
  1. In geoai_datacubes/main.py, set PROVIDER = "sentinelhub".

Switching to the Planet provider (PlanetScope)

For commercial high-resolution PlanetScope imagery:

  1. Get an API key at https://www.planet.com/account/#/user-settings under API keys (requires a Planet account; researchers can apply to the Education & Research Program for archive access, and humid-tropics work can use the free NICFI program — both surface the same PL_API_KEY here).
  2. Copy the bundled template and paste in your key:
cp .env.example .env # then open .env in your editor
PL_API_KEY=your-planet-api-key-here
  1. In geoai_datacubes/main.py, set PROVIDER = "planet" and MISSION = "PlanetScope-4b" (legacy 4-band B/G/R/NIR, archive back to ~2016) or MISSION = "PlanetScope-8b" (SuperDove 8-band CB/B/GI/G/Y/R/RE/NIR, ~2022 onward).
  2. Pick a finer resolution — PlanetScope's native ground sampling is ~3 m, so RESOLUTION = 3 is a sensible default.

Under the hood, the planet provider uses Planet's Data API (/quick-search) to pick the lowest-cloud-cover scene matching your AOI/dates/instrument, then submits a single-scene Orders API request with server-side clip-to-AOI. The order is asynchronous — expect a few minutes for the order to reach success — and the pipeline polls automatically (default 60 min timeout, override via max_wait_seconds). The order delivers the analytic-SR COG and a UDM2 raster; both are downloaded, reprojected onto the same UTM grid we use for Sentinel/Landsat, and written into a multi-band <Mission>_full_size.tiff with descriptions like "R", "NIR", "udm2_clear" — so cloud masking in the tiler flows through unchanged.

⚠️ Never commit .env. The repository's .gitignore already excludes it; keep it that way and never hardcode keys in source files.


Multi-mission fusion

Each fetch produces one <Mission>_full_size.tiff per scene. To train a model on several missions at once — typical when combining optical (Sentinel-2) with SAR (Sentinel-1) with elevation (DEM) or land-cover labels (WorldCover) — you fuse those per-mission cubes onto a common UTM grid at a chosen resolution.

The fusion helper lives in geoai_datacubes/preprocessing/fusion.py (also re-exported from geoai_datacubes.preprocessing):

from fusion import fuse_response_tiffs

fuse_response_tiffs(
    inputs=[
        "data/Sentinel-2_2024-06-12_.../Sentinel-2_full_size.tiff",
        "data/Sentinel-1_2024-06-29_.../Sentinel-1_full_size.tiff",
        "data/Copernicus-DEM_.../Copernicus-DEM_full_size.tiff",
        "data/ESA-WorldCover_.../ESA-WorldCover_full_size.tiff",
    ],
    output_path="fused/columbus_cube.tiff",
    resolution=10,          # output pixel size in metres
    dst_crs=None,           # default: take the CRS of the first input
    bbox_mode="intersection",   # or "union" (see below)
)

The output is a multi-band GeoTIFF whose band descriptions are mission-prefixed so provenance survives: Sentinel-2_B04, Sentinel-2_SCL, Sentinel-1_VV, Sentinel-1_VH, Copernicus-DEM_DEM, ESA-WorldCover_LULC. Downstream code can pick exactly the bands it wants by name.

Choosing the output grid

  • resolution sets the output pixel size in metres. Pick the highest-resolution mission you care about (10 m for Sentinel-2, 3 m for PlanetScope, etc.); coarser bands are upsampled, finer bands are downsampled. Categorical / QA bands (SCL, BQA, LULC, UDM2 layers) are resampled with nearest neighbour to preserve their integer class codes; continuous reflectance and elevation bands use bilinear.

  • dst_crs defaults to the CRS of the first input — usually the UTM zone of the AOI. Pass an explicit rasterio.crs.CRS or EPSG code to force a different target projection.

  • bbox_mode controls how the fused footprint is computed:

    • "intersection" (default) — only the area covered by every input mission. The safe choice for per-pixel multi-modal models; every pixel of the fused cube has data from every mission.
    • "union" — the bounding box of any input. Missions that do not cover the full union are NaN-filled where missing. Useful when one mission is a sparse layer (e.g. PlanetScope tasked over a subset of a Sentinel-2 footprint).

Picking which bands fuse

By default fuse_response_tiffs takes all bands from each input. Pass tuples instead of paths to subset:

fuse_response_tiffs(
    inputs=[
        # All bands of the S2 cube
        "data/.../Sentinel-2_full_size.tiff",
        # Only VV from the S1 cube
        ("data/.../Sentinel-1_full_size.tiff", ["VV"]),
        # Only the LULC band from WorldCover (drop nothing else; it only has one)
        "data/.../ESA-WorldCover_full_size.tiff",
    ],
    output_path="fused/cube.tiff",
    resolution=10,
)

Worked example

The end-to-end multi-mission fusion is demonstrated in notebooks/00_geoai_datacubes_tour.ipynb (section 9), and the resulting fused cube is the input for every classifier in notebooks/01_classification.ipynb which uses the binary water target from ESA-WorldCover_LULC together with Sentinel-2_B0{2,3,4,8} + Sentinel-1_V{V,H} + DEM-derived features.


Configuration & parameters

These are the main knobs you can turn (set in geoai_datacubes/main.py).

Parameter What it controls Example
PROVIDER Where to fetch the imagery from "auto" (default), "earthsearch", "planetary_computer", "planet" (commercial), "sentinelhub", or "direct_http" (non-STAC HTTPS COGs)
MISSION Which satellite to use Any of the 26 user-facing missions in MISSION_PROFILES — Sentinel-2 / Sentinel-2-L1C / Sentinel-1 / Landsat / Copernicus-DEM / Copernicus-DEM-90 / ESA-WorldCover / NAIP / PlanetScope-4b / PlanetScope-8b / MODIS_SR / MODIS_LST / HLS_S30 / HLS_L30 / JRC-GSW / 3DEP / ALOS-PALSAR / ALOS-FNF / USDA-CDL / LCMAP-CONUS / IO-LULC / Chloris-Biomass / Hansen-GFC. Sentinel-5P / GEDI-L4B / GEBCO are documented stubs. See docs/data_layers.md.
AOI Area of interest, in any of four formats (see Defining the AOI). Resolved to ROI via resolve_aoi(). {"bbox": [-83.077, 39.964, -82.983, 40.036]} (default: OSU, Columbus OH)
ROI The resolved bounding box [lon_min, lat_min, lon_max, lat_max] in WGS84 — populated automatically from AOI [-83.077, 39.964, -82.983, 40.036]
TIME_RANGE Date window to search within (start, end) ("2024-06-15", "2024-06-20")
BANDS Spectral bands to download; None uses the mission default. Cloud/quality bands (SCL for Sentinel-2 L2A, BQA for Landsat) are added automatically None, ["B04", "B08"] (S2), ["B04", "B05"] (Landsat)
RESOLUTION Ground resolution in meters per pixel 10
MAX_CLOUD Maximum cloud cover fraction; scenes above this are skipped 0.10 (= 10%)
tile_size Pixel size of each square training tile 256
stride Step between tiles; "auto" fits edges, smaller values overlap "auto" or 128
train_val_test_split Fractions for the train / validation / test split (0.8, 0.1, 0.1)

Pipeline scripts

All pipeline modules live under the geoai_datacubes/ Python package — organised into three subpackages (fetch/, preprocessing/, ml_dl/). Running python -m geoai_datacubes.main ties the core steps together, but you can also import and use the individual subpackages directly (e.g., from geoai_datacubes.fetch import fetch_sentinel_data).

Script What it does
main.py End-to-end run: fetch → cloud-mask/NDVI → tile → split → export. Start here.
missions.py Per-mission, provider-aware config (collection, default bands, NDVI bands, cloud-mask rules, STAC asset names, Sentinel Hub collection enums). Add a new satellite here.
aoi.py resolve_aoi(spec) — turns any of the four supported AOI formats (bbox / shapefile / centre+side / S2-tile-around-point) into a WGS84 bbox.
fusion.py fuse_<Mission>_full_size.tiffs(...) — fuse per-mission <Mission>_full_size.tiff files into one multi-band cube on a common CRS + resolution grid. Bands are prefixed with their mission (e.g. Sentinel-2_B04, Sentinel-1_VV, Landsat_BQA). Use the intersection of the inputs' footprints (default) or their union.
fetch_data.py Provider dispatcher. earthsearch path: STAC search + COG reads via rasterio + /vsicurl. sentinelhub path: Sentinel Hub Process API. Both produce the same multi-band <Mission>_full_size.tiff.
config.py (Sentinel Hub only) reads OAuth credentials from .env via get_config_from_env.
parallel_fetch.py Fetches multiple scenes/ROIs in parallel for faster throughput.
preprocess.py Normalizes bands to [0, 1] and computes NDVI.
tiler.py / run_tiler.py Cuts a scene (or a fused cube) into AI-ready tiles with configurable stride, optional augmentation, and one of four train/val/test split strategies (random / block (default) / stripes / regions, reusing the aoi.py spec language). NaN handling is selectable: drop (strict — skip any tile that contains a NaN), interpolate (nearest-neighbour fill up to nan_interp_max_dist pixels — for isolated holes and 1-pixel mosaic seams), or mask (keep the tile, replace NaNs with 0, append a binary valid_mask channel so training can be loss-masked — the standard pad-and-ignore approach). All band names are propagated to tiles for downstream identification.
visualize_cloud_mask.py Saves an NDVI-vs-cloud-mask comparison image to confirm cloud filtering.
visualize.py Helper for displaying/saving imagery.
export_zarr.py Exports tiles (+ metadata) to a Zarr dataset.
export_lmdb.py Exports tiles to an LMDB dataset.
dataset_loader.py A PyTorch Dataset / DataLoader that reads the tiles for training.
test_loader_v2.py Quick sanity check that the data loader and augmentations work.
create_stac_catalog.py Generates a STAC catalog/item for geospatial interoperability.
landsat/landsat_pipeline/ Optional multi-sensor harmonization helpers (reproject/resample onto a common grid). Landsat downloads go through main.py like any mission.

Try the notebooks

The repo ships with four complementary notebooks in notebooks/. See the notebooks/README.md for a detailed walkthrough of each.

1. The grand tour (start here if you are new)

notebooks/00_geoai_datacubes_tour.ipynb Open in Colab

A pedagogical walkthrough of every feature on the data side of the pipeline — the four AOI formats, fetching from each free mission, cloud masking, NaN handling, tiling with/without overlap, the four train/val/test split strategies, multi-mission fusion, reading metadata back from a tile, augmentation, and submitting jobs to SLURM. Click the Colab badge to launch it in your browser — the first cell clones the repo and installs everything you need, no local Python required.

# local: launch with Jupyter from anywhere in the repo
jupyter notebook notebooks/00_geoai_datacubes_tour.ipynb

2. Land-cover classification end-to-end (ML/DL)

notebooks/01_classification.ipynb Open in Colab

A complete machine-learning workflow that picks up where the tour leaves off. Fetches and fuses Sentinel-2 + Sentinel-1 + Copernicus DEM + ESA WorldCover for Columbus, Cincinnati, and Cleveland; converts each city's cube to Zarr; then trains and compares four standard classifiers (Logistic Regression, Random Forest, XGBoost, and a lightweight U-Net) on a binary classification target — the chosen ESA WorldCover class vs everything else. The class is a user input at the top of the notebook, with a per-class quality table showing which choices work well in this AOI (water 80, tree cover 10, cropland 40 are easy; built-up 50 is medium-and-interesting; rarer classes are honestly flagged as poor choices). Includes a conditional spectral-index baseline (NDWI for water; NDVI for vegetation classes; skipped otherwise), an NDVI / NDWI / NDMI side-by-side sidebar, an unsupervised KMeans bonus that compares a five-cluster MiniBatchKMeans split against WorldCover ground truth, multi-modal fusion comparison (S2 vs S2 + S1 vs S2 + S1 + DEM with DEM preprocessed into city-relative elevation + gradient magnitude), threshold tuning on validation, per-city test breakdown, and a collapsible explainer for the binary-classification metrics (TP/FP/FN/TN, precision, recall, F1, IoU, AUC).

jupyter notebook notebooks/01_classification.ipynb

3. Building detection on NAIP (object detection / YOLO)

notebooks/02_building_detection.ipynb Open in Colab

The first object-detection notebook in the series — switches the modelling problem from per-pixel labelling (notebook 01) to one bounding box per individual building. Uses NAIP 1 m aerial imagery (the only widely-available sub-metre free public source) and the Microsoft US Building Footprints dataset as ground truth across three Ohio cities (Columbus → train, Cincinnati → val, Cleveland → test). Trains a tiny YOLOv8n detector (~3.2 M parameters) on CPU and reports mAP@0.5, mAP@0.5–0.95, precision, and recall. Includes:

  • a NAIP-vs-Sentinel-2 resolution sidebar that motivates the resolution-vs-object-scale trade-off (a typical residential building is ~10 × 10 px at 1 m GSD but ~1 × 1 px at 10 m — useless for detection);
  • a PlanetScope-at-3-m sidebar discussed in prose only, because Planet's licence forbids embedding pixels in published outputs.
jupyter notebook notebooks/02_building_detection.ipynb

4. Integration with opengeos/geoai (segmentation hand-off)

notebooks/03_with_opengeos_geoai.ipynb Open in Colab

A worked example of composing geoai-datacubes (data-prep front-end) with opengeos/geoai (Wu, 2026, JOSS 11(118):9605 — modelling back-end). Builds fused multi-mission cubes for three Ohio cities (Cleveland → lake, Cincinnati → wide river, Columbus → narrow rivers) and hands them off to geoai-py in two patterns: (1) geoai.segment_water on a NAIP scene — one-call pretrained inference using OmniWaterMask + OSM; and (2) custom training via select_bands + geoai.train_segmentation_landcover + geoai.semantic_segmentation on a 3-band NDWI subset. Trains on Cleveland + Cincinnati and holds Columbus out entirely as an unseen test region — in-distribution F1 reaches ~0.95 while out-of-distribution F1 on Columbus collapses to ~0.05. The honesty about generalisation is deliberate: the integration works cleanly in both directions, and the OOD failure is the normal remote-sensing-ML reality of training on a handful of AOIs. The new select_bands helper + BAND_PRESETS (ndwi / nbr / ndsi / rgb_nir / rgb_dem / rgb_sar_vv / ndwi_sar_vv / …) handles the channel-count and nodata=nan → uint8 mismatches that otherwise block geoai-py's loaders on multi-mission cubes.

jupyter notebook notebooks/03_with_opengeos_geoai.ipynb

Project structure

geoai-datacubes/
├── README.md
├── LICENSE
├── requirements.txt
├── .env.example # copy to .env and add your keys
├── docs/
│ └── data_layers.md # mission/band/resolution/range reference
├── notebooks/
│ ├── README.md # per-notebook walkthrough
│ ├── 00_geoai_datacubes_tour.ipynb # pedagogical data-pipeline tour (Colab-ready)
│ ├── 01_classification.ipynb # end-to-end ML/DL training (Colab-ready)
│ ├── 02_building_detection.ipynb # NAIP + YOLO building-detection demo (Colab-ready)
│ ├── benchmark_lulc_class.py # per-class binary benchmark CLI
│ ├── lulc_leaderboard.md # per-class results table
│ └── sample_data/                       # bundled inputs for the demo notebooks
│   ├── README.md
│   └── building_footprints_oh_3cities_5mi.gpkg  # for the building-detection notebook
├── slurm_examples/ # generic SBATCH templates for HPC clusters
├── paper.md # JOSS-format paper draft
├── HISTORY.md # project timeline
├── CONTRIBUTORS.md # contributor list
└── geoai_datacubes/                 # Python package
 ├── README.md                       # package overview + how to extend
 ├── main.py                         # CLI entry: edit USER INPUT, then `python -m geoai_datacubes.main`
 ├── fetch/                          # data acquisition
 │ ├── aoi.py                        # AOI helpers (bbox / shapefile / centre+miles / S2-tile)
 │ ├── missions.py                   # the 26-mission registry (MISSION_PROFILES)
 │ ├── fetch_data.py                 # generic STAC dispatcher + SH + Planet drivers
 │ ├── config.py                     # SH OAuth env helper
 │ ├── parallel_fetch.py             # ThreadPoolExecutor wrapper
 │ └── create_stac_catalog.py        # STAC catalog builder
 ├── preprocessing/                  # raw imagery -> AI-ready cube
 │ ├── fusion.py                     # multi-mission UTM-grid fusion
 │ ├── tiler.py                      # tile a fused cube into fixed-size chips
 │ ├── lazy_dataset.py               # on-the-fly PyTorch tile sampler
 │ ├── band_ops.py                   # normalise / NDVI / cloud-mask
 │ ├── export_zarr.py                # GeoTIFF -> Zarr (faster cluster training)
 │ ├── export_lmdb.py                # GeoTIFF -> LMDB
 │ └── visualize_cloud_mask.py       # debug helper: cloud-mask vs imagery
 └── ml_dl/                          # downstream ML/DL helpers
   ├── object_detection.py           # YOLO + polygon-ground-truth plumbing
   └── (future: classification, segmentation, super-resolution)

Credentials & security

The default earthsearch provider needs no credentials at all. Skip this section unless you opt into PROVIDER = "sentinelhub".

For the Sentinel Hub path:

  • Credentials are read from environment variables, loaded from a local .env file at the repo root that you create by copying .env.example.
  • The variables are:
  • SH_CLIENT_ID
  • SH_CLIENT_SECRET
  • SH_INSTANCE_ID (optional)
  • Get or manage your OAuth client at https://shapps.dataspace.copernicus.eu/dashboard/.
  • Never commit .env to git, and never hardcode keys in source files. If you ever expose a secret accidentally, revoke it in the dashboard and create a new one.

License & ownership

Released under the MIT License — see LICENSE for the full text.

Copyright © The Ohio State University / BuckAI Observatory.

This tool was developed by the BuckAI Observatory, with contributions from a master's student. Intellectual property is held by The Ohio State University.


Acknowledgements & contact

Built and maintained by the BuckAI ObservatoryArtificial Intelligence for Earth Observation and the Natural Sciences — at The Ohio State University.

Development history and contributors

This project was developed over approximately one year by Jain, Bhavika; Radhakrishnan, Aswathnarayan; Chowdhury, Satyaki Roy; Hsu, Hsiao Jou (Amy); and Moortgat, Joachim (principal investigator) at The Ohio State University. Initial prototyping began in August 2025 in a separate repository (see HISTORY.md for the full timeline) and the codebase moved to its current home at github.com/buckai-observatory/geoai-datacubes in December 2025.

Code development since May 2026 was substantially accelerated by Claude Code, Anthropic's AI coding assistant, used as a development tool under continuous human direction and review. All design decisions, scientific judgements, and validation against domain knowledge were made by the human authors. See CONTRIBUTORS.md for a per-area breakdown of who contributed what.

We welcome collaboration. If this tool helps your research, we'd love to hear about it.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

geoai_datacubes-0.1.0.tar.gz (219.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

geoai_datacubes-0.1.0-py3-none-any.whl (152.5 kB view details)

Uploaded Python 3

File details

Details for the file geoai_datacubes-0.1.0.tar.gz.

File metadata

  • Download URL: geoai_datacubes-0.1.0.tar.gz
  • Upload date:
  • Size: 219.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for geoai_datacubes-0.1.0.tar.gz
Algorithm Hash digest
SHA256 deb266c3a469cbd39054acb83a7d748c0caff86de44f7ff924aa37b6fb545da4
MD5 7aff5267420724da7c615cc024fc1c80
BLAKE2b-256 b7e2d73f5ed48b76a8f573c414da3d12afb82eb6bd5f0c5bce8b7116b224f62b

See more details on using hashes here.

Provenance

The following attestation bundles were made for geoai_datacubes-0.1.0.tar.gz:

Publisher: publish.yml on buckai-observatory/geoai-datacubes

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file geoai_datacubes-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: geoai_datacubes-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 152.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for geoai_datacubes-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b40dbd82712f679d558623b0eb211a99aec5aa1233a877a11e20ab65880e319a
MD5 18adb89274db373434066c7e039bb83b
BLAKE2b-256 d599d9f9436e15b02fed82c93e003991f12451df7ab2bbec64a5b48eafa462b1

See more details on using hashes here.

Provenance

The following attestation bundles were made for geoai_datacubes-0.1.0-py3-none-any.whl:

Publisher: publish.yml on buckai-observatory/geoai-datacubes

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page