Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

v2.5 — Per-AOI pixel-level cloud climatology

Given an AOI (point with radius, polygon, or bbox up to ~50 km), use the actual QA bands of every intersecting scene to compute the truthful clear-fraction time series — on demand, in seconds.

This is the single-AOI, on-demand version of the pixel-level problem. It shares the AOI flow with satellite_viewer (the same input shape — draw a polygon, pick a sensor, pick a date range) but the output is the clear-fraction climatology for that AOI, not a thumbnail preview.

Goal

For one user-supplied AOI and time window:

  1. Discover intersecting scenes (reuse satellite_viewer.search).
  2. For each scene, lazily read the QA band windowed to the AOI via georeader.
  3. Decode the QA band to a per-pixel clear mask using a per-sensor decoder.
  4. Reduce the mask to a single number: clear-pixel fraction inside the AOI for this acquisition.
  5. Return the (datetime, clear_fraction, scene_id) time series plus aggregates (clear_obs_count, mean_clear_fraction, longest_clear_gap_days).

Question answered

“Across all scenes that touched this AOI in [t0,t1][t_0, t_1], what fraction of the AOI’s pixels were actually clear in each scene, and what does the resulting clear-fraction time series look like?”

Validates v3’s algorithm at user-facing scale before committing to the global compute. Also a standalone product: ecologists, agronomists, plume hunters all care about “how often can I see this specific field / hotspot / region clearly?” — exactly the v2.5 deliverable.

Scope

Algorithm

1. items = satellite_viewer.search(sensor, aoi, t0, t1, cloud_lt=None, max_items=1000)
2. for item in items:                                 # → parallelisable
       qa = georeader.read_window(item.assets[qa_band_key], aoi)
       clear_mask = decoder(qa, clear_classes=cfg.clear_classes)
       clear_frac = clear_mask.mean()                 # float in [0, 1]
       record (item.datetime, item.id, clear_frac)
3. emit:
   - per-scene time series: [(datetime, scene_id, clear_frac, eo:cloud_cover)]
   - aggregates: clear_obs_count = sum(frac > clear_threshold),
                 mean_clear_fraction = mean(frac),
                 longest_clear_gap_days = max(gap between frac > threshold)

The per-item work is the same code that v3 will run in its inner loop — the abstraction split is “v2.5 = one AOI, run interactively” vs. “v3 = every cell of the global grid, run as a batch.”

Architecture

projects/satellite_climatology/
└── src/satellite_climatology/
    ├── grid.py             # (shared with v1/v2/v3) — not used by v2.5
    ├── sensors.py          # (shared) — adds qa_band_key, clear_classes per sensor
    ├── qa_decoders/        # (shared with v3)
    │   ├── s2_scl.py
    │   ├── landsat_qapixel.py
    │   └── modis_state1km.py
    ├── operators.py        # (shared with v3) — ReadQA, DecodeQA, CellClearFrac
    └── aoi_pipeline.py     # the v2.5 entry point — runs the per-scene op over an AOI

Repo wiring:

Output

A pandas DataFrame, not a Zarr band:

columns: ["datetime", "scene_id", "sensor", "scene_cloud_pct", "clear_fraction"]
shape:   (n_scenes, 5)
index:   reset
crs:     no geometry (the AOI is shared across rows)

Plus the three scalar aggregates returned alongside.

UI integration

A new tab in the satellite_viewer dashboard, or a sibling subapp. Same AOI input shape as the preview tool. Output panes:

  1. Clear-fraction time series — scatter or line of clear_fraction vs. datetime, colour-coded by sensor.
  2. Climatology stats cardclear_obs_count, mean_clear_fraction, longest_clear_gap_days, n_scenes.
  3. Side-by-side scenes — for each acquisition, RGB thumbnail (from STAC rendered_preview, reused from satellite_viewer) next to the QA-derived clear mask preview at AOI scale.
  4. Comparison with scene-level — overlay eo:cloud_cover (the v2 answer) vs. 1 − clear_fraction (the v2.5 answer). Where they diverge is the entire point of v2.5.

Compute budget

Per AOI, default 50 km bbox cap:

This is the dashboard-tractable target. Above the size cap or beyond ~5-year windows the user gets routed to v3.

Risks & open questions

Acceptance

Out of scope