From f6ac1ce62f70170fcec45a49c4cb8398213a6a5d Mon Sep 17 00:00:00 2001 From: Adeolu Mary Oshadare Date: Sat, 2 May 2026 22:53:15 +0300 Subject: [PATCH 1/3] docs(team): add Adeolu role specification Codifies Data Pipeline & GIS Lead responsibilities: real GEE tile downloads, analysis-specific band mapping, SCL cloud masking at inference time, and the synthetic-fallback guardrail. --- team_docs/Adeolu_Mary_Oshadare_Role.pdf | Bin 0 -> 12626 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 team_docs/Adeolu_Mary_Oshadare_Role.pdf diff --git a/team_docs/Adeolu_Mary_Oshadare_Role.pdf b/team_docs/Adeolu_Mary_Oshadare_Role.pdf new file mode 100644 index 0000000000000000000000000000000000000000..51b940ed11df0bbf8565c3f17810938b9b1891cd GIT binary patch literal 12626 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⁣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 R0)af7KmaYR_)7`E{{bDa%+CM- literal 0 HcmV?d00001 From 3e6029046ee606929fe6a6a3baaf02affb7ec8af Mon Sep 17 00:00:00 2001 From: Adeolu Mary Oshadare Date: Sat, 2 May 2026 22:53:20 +0300 Subject: [PATCH 2/3] docs(data): document data pipeline modules and band contract Single page covering each file in the data package, the analysis-type band contract, the SCL cloud-masking rules, and the synthetic-fallback metadata convention. Helps new contributors avoid hardcoding band lists. --- src/climatevision/data/README.md | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/climatevision/data/README.md diff --git a/src/climatevision/data/README.md b/src/climatevision/data/README.md new file mode 100644 index 0000000..ee3ea52 --- /dev/null +++ b/src/climatevision/data/README.md @@ -0,0 +1,46 @@ +# Data Pipeline + +Sentinel-2 ingestion, band mapping, and preprocessing for ClimateVision. + +## Modules + +| File | Purpose | +|------|---------| +| `gee_downloader.py` | Download real Sentinel-2 tiles from Google Earth Engine for a given bbox + date range. Falls back to a labelled synthetic tile (`is_synthetic: true`) when GEE credentials are missing. | +| `band_mapping.py` | Single source of truth for which spectral bands each analysis type requires. Reads from `config.yaml`. | +| `preprocessing.py` | Cloud masking (SCL band), normalisation, resampling 20m bands to 10m, tiling to 256×256. | +| `transforms.py` | Augmentation pipeline (flips, rotations, spectral jitter) for training DataLoaders. | +| `sampling.py` | Tile sampling strategies (random, balanced, stratified by region). | +| `quality.py` | Per-tile QA (cloud %, NaN ratio, band coverage). | +| `validation.py` | Schema validation for incoming requests and downloaded tiles. | + +## Analysis-Type Band Contract + +Every analysis type has its own band list in `config.yaml`. The pipeline must use `get_bands_for_analysis(analysis_type)` — never hardcode band lists. + +| Analysis | Bands | Channels | +|----------|-------|----------| +| `deforestation` | B04, B03, B02, B08 | 4 | +| `ice_melting` | B02, B03, B04, B11 | 4 | +| `flooding` | B03, B08, B11 | 3 | + +## Cloud Masking + +`apply_scl_cloud_mask(image, scl_band)` zeroes out pixels classified as cloud, shadow, snow/ice, or no-data using the Sentinel-2 Scene Classification Layer (SCL). This must run **before** the model forward pass. + +Valid SCL classes kept: 4 (vegetation), 5 (bare soil), 6 (water), 7 (low cloud), 10 (thin cirrus). +Masked out: 0 (no-data), 1 (saturated), 2 (dark), 3 (shadow), 8/9 (medium/high cloud), 11 (snow/ice). + +## Synthetic Fallback + +If GEE auth fails, the downloader returns a deterministic synthetic tile seeded by the bbox so the same region always yields the same fallback. The metadata always includes `is_synthetic: true` so the API can warn the caller. + +## Environment + +``` +GEE_PROJECT_ID=your-project-id +GEE_SERVICE_ACCOUNT=svc@project.iam.gserviceaccount.com +GEE_SERVICE_ACCOUNT_KEY=secrets/gee-key.json +``` + +Run `python scripts/setup_gee.py` to verify credentials. From 488e159ef89ae171ded685f9bdea3ee52cf01408 Mon Sep 17 00:00:00 2001 From: Adeolu Mary Oshadare Date: Sat, 2 May 2026 22:53:26 +0300 Subject: [PATCH 3/3] test(data): add band mapping smoke tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verifies the analysis-type → band contract holds: - Sentinel-2 13-band canonical order - Per-analysis band counts (4/4/3 for deforestation/ice/flood) - SCL append-without-duplicate invariant - Band index resolution and rejection of unknown bands - Enabled vs disabled analysis types from config.yaml --- tests/test_band_mapping.py | 83 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 tests/test_band_mapping.py diff --git a/tests/test_band_mapping.py b/tests/test_band_mapping.py new file mode 100644 index 0000000..4f5832a --- /dev/null +++ b/tests/test_band_mapping.py @@ -0,0 +1,83 @@ +"""Smoke tests for analysis-aware Sentinel-2 band mapping.""" +from __future__ import annotations + +import pytest + +from climatevision.data.band_mapping import ( + SCL_BAND, + SENTINEL2_BAND_ORDER, + get_band_indices, + get_bands_for_analysis, + get_bands_for_analysis_with_scl, + get_model_config, + is_analysis_enabled, + list_enabled_analysis_types, +) + + +def test_sentinel2_band_order_has_13_bands(): + assert len(SENTINEL2_BAND_ORDER) == 13 + assert SENTINEL2_BAND_ORDER[0] == "B01" + assert SENTINEL2_BAND_ORDER[-1] == "B12" + + +def test_deforestation_uses_four_bands(): + bands = get_bands_for_analysis("deforestation") + assert len(bands) == 4 + assert set(bands) == {"B02", "B03", "B04", "B08"} + + +def test_flooding_uses_three_bands(): + bands = get_bands_for_analysis("flooding") + assert len(bands) == 3 + assert "B11" in bands + + +def test_ice_melting_uses_swir(): + bands = get_bands_for_analysis("ice_melting") + assert "B11" in bands + + +def test_scl_appended_for_cloud_masking(): + bands = get_bands_for_analysis_with_scl("deforestation") + assert SCL_BAND in bands + assert bands[-1] == SCL_BAND + + +def test_scl_not_duplicated(): + bands_with_scl = get_bands_for_analysis_with_scl("deforestation") + bands_again = get_bands_for_analysis_with_scl("deforestation") + assert bands_with_scl.count(SCL_BAND) == 1 + assert bands_again.count(SCL_BAND) == 1 + + +def test_band_indices_resolve_correctly(): + indices = get_band_indices(["B04", "B03", "B02", "B08"]) + assert indices == [3, 2, 1, 7] + + +def test_band_indices_rejects_unknown(): + with pytest.raises(ValueError, match="Unknown"): + get_band_indices(["B99"]) + + +def test_band_indices_rejects_scl_directly(): + with pytest.raises(ValueError, match="SCL"): + get_band_indices([SCL_BAND]) + + +def test_enabled_analysis_types_include_active_three(): + enabled = list_enabled_analysis_types() + for name in ("deforestation", "ice_melting", "flooding"): + assert name in enabled, f"{name} should be enabled" + + +def test_disabled_analysis_types(): + assert not is_analysis_enabled("drought") + assert not is_analysis_enabled("wildfire") + + +def test_model_config_carries_channels_and_classes(): + cfg = get_model_config("flooding") + assert cfg["in_channels"] == 3 + assert cfg["num_classes"] == 3