Skip to content

fix(s1-rtc): store nodata as NaN, not 0, so titiler masks it transparent#202

Merged
lhoupert merged 1 commit into
feat/s1-rtc-stac-builderfrom
fix/s1-rtc-nodata-nan
Jun 23, 2026
Merged

fix(s1-rtc): store nodata as NaN, not 0, so titiler masks it transparent#202
lhoupert merged 1 commit into
feat/s1-rtc-stac-builderfrom
fix/s1-rtc-nodata-nan

Conversation

@lhoupert

Copy link
Copy Markdown
Contributor

Why

S1 RTC previews render the out-of-swath region as opaque black while the S2 reference renders it transparent. The cause is the data, not the metadata: #201 already gave vv/vh the same dtype / fill_value=NaN / _FillValue / grid_mapping encoding as S2, but those are inert because no NaN is ever written — s1tiling stores 0.0 out of swath, and titiler treats 0 as valid data and paints it black.

nodata in data renders
S2 b04 NaN transparent ✅
S1 vv (rc1, _FillValue) 0.0, NaN 0% opaque black ❌

This is the data half; #201 was the metadata half.

What

Mask nodata → NaN at the writer (src/eopf_geozarr/conversion/s1_ingest.py):

  • vv/vhnp.where(border_mask == 0, NaN, …) before overview generation. border_mask is the authoritative valid-data mask (0 = no-data). _downsample_2d already uses np.nanmean for floats, so NaN propagates to every overview level.
  • float conditions (gamma_area/lia) — masked read off the GeoTIFF's declared nodata (border_mask is N/A for static conditions); a no-op when no nodata is declared.

No change to the create-store paths — only the two read sites are touched (12 src lines).

⚠️ Only newly re-ingested cubes get NaN. Existing cubes are remediated separately.

Tests (TDD, RED→GREEN)

tests/test_s1_rtc_ingest.py — all 4 fail on 082913a, pass after the fix:

  • test_nodata_masked_to_nan (new) — NaN ⟺ border_mask == 0 at native and the r20m overview; valid pixels stay finite.
  • test_fill_value_masking_roundtrip (rewritten) — masked region now comes from border_mask, not a pre-seeded NaN; round-trips via use_zarr_fill_value_as_mask.
  • test_conditions_nodata_masked_to_nan (new) — declared-nodata → NaN.
  • test_preserves_data_integrity (updated) — valid region preserved exactly, nodata region NaN.

Plan trio (test_s1_rtc_ingest + test_array_attrs + test_integration_sentinel1) = 85 passed; whole tests/ green (exit 0, optional-dep skips only).

Stacks on #201; targets #180.

🤖 Generated with Claude Code

S1 RTC previews render the out-of-swath region as opaque black while the
S2 reference renders it transparent. The cause is the *data*, not the
metadata: #201 already gave vv/vh the same dtype/`fill_value`=NaN/
`_FillValue`/`grid_mapping` encoding as S2, but those are inert because no
NaN is ever written — s1tiling stores `0.0` out of swath, and titiler
treats `0` as valid data and paints it black.

Mask nodata to NaN at the writer:
- vv/vh: `np.where(border_mask == 0, NaN, ...)` — border_mask is the
  authoritative valid-data mask (0 = no-data). `_downsample_2d` already
  uses `np.nanmean` for floats, so NaN propagates to every overview level.
- float conditions (gamma_area/lia): masked read off the GeoTIFF's
  declared nodata (border_mask is N/A for static conditions); a no-op when
  no nodata is declared.

Only newly re-ingested cubes get NaN; existing cubes are remediated
separately. Tests: NaN ⟺ border_mask==0 at native + overview levels,
masking round-trips via `use_zarr_fill_value_as_mask`, conditions
declared-nodata → NaN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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