Skip to content

Add coastal gap-fill module for extending GeoTessera coverage#18

Open
PatBall1 wants to merge 1 commit into
ucam-eo:masterfrom
PatBall1:coastal-gap-fill
Open

Add coastal gap-fill module for extending GeoTessera coverage#18
PatBall1 wants to merge 1 commit into
ucam-eo:masterfrom
PatBall1:coastal-gap-fill

Conversation

@PatBall1

@PatBall1 PatBall1 commented Apr 9, 2026

Copy link
Copy Markdown

Summary

GeoTessera precomputed embeddings use a relatively coarse land mask that causes two problems for coastal work:

  1. Ocean and nearshore areas are blanked out. Reefs, seagrass beds, and shallow shelf areas contain no embedding data.
  2. Coastal land is often clipped. The coarse mask cuts into shorelines, removing mangroves, intertidal zones, salt marshes, and coastal wetlands.

This PR adds tessera_coastal/, a self-contained module that identifies tiles with incomplete coverage, regenerates them from Sentinel-1/2 imagery via the existing QAT pipeline, and exports seamless GeoTIFF tiles extending well beyond the shoreline.

Before and after (Jamaica, PCA false-colour)

GeoTessera only With coastal gap-fill
before after

What's included

New module -- tessera_coastal/:

  • identify_coastal_tiles.py -- queries GeoTessera, downloads tiles, scans for blank pixels, groups tiles for regeneration
  • run_coastal_pipeline.sh -- orchestrates S1/S2 download, stacking, patchification, QAT inference, and stitching per group
  • export_tiles.py -- merges GeoTessera + gap-fill into sliver-free 0.1-degree GeoTIFF tiles
  • create_preview.py -- PCA false-colour previews for visual QA
  • Full README with motivation, workflow diagram, CLI reference, and before/after example

Changes to existing files

This PR also touches two existing files. Both changes are backward-compatible and do not alter behaviour for existing workflows.


tessera_infer_QAT/infer_all_tiles.sh -- make settings overridable via environment variables

The coastal pipeline processes many small tile groups in a loop, calling infer_all_tiles.sh once per group with different data directories and memory settings each time. With the original hardcoded assignments, the only way to change settings like BASE_DATA_DIR or GPU_BATCH_SIZE was to edit the script before each invocation.

This change wraps the assignments in ${VAR:=default} so that a caller can pass configuration via environment variables. If no environment variable is set, the original default is used -- so anyone calling infer_all_tiles.sh directly today will see no difference. The only defaults that change are CPU_GPU_SPLIT (1:00:1) and GPU_BATCH_SIZE (1024512), reflecting the more common GPU-based usage.


tessera_infer/stitch_tiled_representation.py -- apply QAT dequantization scales during stitching

The coastal module uses the QAT inference path (tessera_infer_QAT/) because GeoTessera's public embeddings are aligned with the QAT checkpoint -- not the older float32 one. This is the only way to produce new embeddings that are numerically compatible with existing GeoTessera tiles.

The QAT inference pipeline already saves both int8 quantized representations and per-pixel float32 scale factors (*_scales.npy via tile_processor.py). These scales are needed to dequantize the int8 values back to their original float32 range. However, stitch_tiled_representation.py was not applying them -- it loads the raw int8 array, which NumPy auto-casts to float32, leaving values in the -128 to 127 range rather than the true embedding scale (magnitudes around 20-45 for GeoTessera tiles).

This means anyone using QAT inference followed by stitching would get output in a different numeric range to GeoTessera. For the coastal module this is critical because the export step merges QAT-stitched tiles with existing GeoTessera tiles, and the values must be in the same range.

The fix is five lines: if a *_scales.npy file exists alongside a representation tile, multiply through before stitching. If no scale file exists (the float32 inference path), the check is skipped and behaviour is identical to before.

Test plan

  • Run identify_coastal_tiles.py on a small AOI to verify tile identification and manifest generation
  • Run the full pipeline on a small group (2-3 tiles) to verify end-to-end inference
  • Run export_tiles.py and create_preview.py to verify GeoTIFF and preview output
  • Verify that existing tessera_infer_QAT and tessera_infer workflows are not broken (no scale files present → no change; no env vars set → original defaults used)

GeoTessera precomputed embeddings use a coarse land mask that clips
coastal land (mangroves, intertidal zones, salt marshes) and blanks
out nearshore waters entirely. This makes it difficult to use TESSERA
embeddings for work that spans the land-sea boundary.

tessera_coastal/ is a self-contained module that identifies tiles with
incomplete coverage, regenerates them from Sentinel-1/2 imagery via the
existing QAT inference pipeline, and exports seamless GeoTIFF tiles
extending well beyond the shoreline.

Also includes two small upstream improvements:
- tessera_infer_QAT/infer_all_tiles.sh: make batch size, split ratio,
  and worker settings configurable via environment variables
- tessera_infer/stitch_tiled_representation.py: support optional
  per-pixel scale factors during stitching
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant