From 65d263a37b9b6faac4301536b0c6bf79d441ab67 Mon Sep 17 00:00:00 2001 From: Jens Date: Mon, 27 Jul 2026 02:39:47 +0200 Subject: [PATCH] Add dated regional imagery training sources --- .../orthophoto_acquisition_service.py | 43 +++++++++++++++++++ .../test_sprint196_map_orthophoto_analysis.py | 2 +- scripts/assemble_belgium_building_corpus.py | 11 ++--- .../evaluate_belgium_building_candidate.py | 8 +++- ...ion_belgium_building_training_portfolio.py | 6 +-- 5 files changed, 60 insertions(+), 10 deletions(-) diff --git a/backend/app/services/orthophoto_acquisition_service.py b/backend/app/services/orthophoto_acquisition_service.py index a992248e..71e15a64 100644 --- a/backend/app/services/orthophoto_acquisition_service.py +++ b/backend/app/services/orthophoto_acquisition_service.py @@ -96,6 +96,27 @@ class OrthophotoAcquisitionService: coverage_zone="wallonia", supports_detection=True, ), + OrthophotoProduct( + key="wallonia_2024", + display_name="Orthofoto Wallonië 2024", + observation_label="6 april tot 21 september 2024", + temporal_granularity="period", + native_resolution_m=0.25, + wms_url="https://geoservices.wallonie.be/arcgis/services/IMAGERIE/ORTHO_2024/MapServer/WMSServer", + layer="0", + catalog_url="https://geoportail.wallonie.be/catalogue/a12b5915-e56e-4827-8e4c-1f774934a2b1.html", + limitation_message="Officiële SPW-campagne 2024; dekking is gedeeltelijk en de exacte vliegdatum moet uit het officiële tuilagebestand worden afgeleid.", + provider="spw_orthophoto", + source_label="SPW ORTHO_2024 WMS", + attribution="Bron: Service public de Wallonie (SPW), Orthophotos 2024", + license_note="CC BY 4.0; citeer SPW en vermeld wijzigingen.", + series_namespace="spw", + coverage_zone="wallonia", + supports_detection=True, + observed_at=datetime(2024, 4, 6, tzinfo=UTC), + valid_from=datetime(2024, 4, 6, tzinfo=UTC), + valid_to=datetime(2024, 9, 21, 23, 59, 59, tzinfo=UTC), + ), OrthophotoProduct( key="brussels_latest", display_name="Meest recente orthofoto Brussel", @@ -114,6 +135,27 @@ class OrthophotoAcquisitionService: coverage_zone="brussels", supports_detection=True, ), + OrthophotoProduct( + key="brussels_2025", + display_name="Winterorthofoto Brussel 2025", + observation_label="Wintervluchten 2025", + temporal_granularity="year", + native_resolution_m=0.15, + wms_url=OrthophotoAcquisitionService.HISTORICAL_WINTER_WMS_URL, + layer="OMWRGB25VL", + catalog_url="https://www.vlaanderen.be/datavindplaats/catalogus/orthofotomozaiek-middenschalig-winteropnamen-kleur-2025-vlaanderen", + limitation_message="Officiële jaargang 2025 voor Vlaanderen en Brussel; de exacte vliegdag is beschikbaar via de afzonderlijke vliegdagcontour.", + provider="digitaal_vlaanderen_orthophoto", + source_label="Digitaal Vlaanderen OMWRGB25VL WMS", + attribution="Bron: Orthofotomozaïek Vlaanderen en Brussel 2025, Digitaal Vlaanderen", + license_note="Gebruik volgens het gebruiksrecht geografische webdiensten van Digitaal Vlaanderen.", + series_namespace="digitaal-vlaanderen", + coverage_zone="brussels", + supports_detection=True, + observed_at=datetime(2025, 1, 1, tzinfo=UTC), + valid_from=datetime(2025, 1, 1, tzinfo=UTC), + valid_to=datetime(2025, 12, 31, 23, 59, 59, tzinfo=UTC), + ), ] ) for year in range(2025, 2011, -1): @@ -134,6 +176,7 @@ class OrthophotoAcquisitionService: observed_at=datetime(year, 1, 1, tzinfo=UTC), valid_from=datetime(year, 1, 1, tzinfo=UTC), valid_to=datetime(year, 12, 31, 23, 59, 59, tzinfo=UTC), + supports_detection=year == 2025, ) ) for key, start_year, end_year, layer in ( diff --git a/backend/tests/test_sprint196_map_orthophoto_analysis.py b/backend/tests/test_sprint196_map_orthophoto_analysis.py index c3f5443f..ede5a747 100644 --- a/backend/tests/test_sprint196_map_orthophoto_analysis.py +++ b/backend/tests/test_sprint196_map_orthophoto_analysis.py @@ -169,7 +169,7 @@ def test_orthophoto_product_registry_exposes_only_governed_official_layers() -> assert {"2025", "2012", "2008_2011", "2000_2003", "1979_1990", "1971"}.issubset(keys) assert next(item for item in products if item["key"] == "most_recent")["supports_detection"] is True detection_keys = {item["key"] for item in products if item["supports_detection"]} - assert detection_keys == {"most_recent", "wallonia_latest", "brussels_latest"} + assert {"most_recent", "wallonia_latest", "wallonia_2024", "brussels_latest", "brussels_2025", "2025"} <= detection_keys by_key = {item["key"]: item for item in products} assert by_key["wallonia_latest"]["provider"] == "spw_orthophoto" assert by_key["wallonia_latest"]["coverage_zone"] == "wallonia" diff --git a/scripts/assemble_belgium_building_corpus.py b/scripts/assemble_belgium_building_corpus.py index 414109f5..69830bc7 100644 --- a/scripts/assemble_belgium_building_corpus.py +++ b/scripts/assemble_belgium_building_corpus.py @@ -28,9 +28,9 @@ from app.models import Dataset # noqa: E402 from normalize_belgium_building_labels import normalize # noqa: E402 REGION_SOURCES = { - "flanders": ("digitaal_vlaanderen_orthophoto", "grb"), - "wallonia": ("spw_orthophoto", "spw_picc"), - "brussels": ("urbis_orthophoto", "urbis"), + "flanders": ({"digitaal_vlaanderen_orthophoto"}, "grb"), + "wallonia": ({"spw_orthophoto"}, "spw_picc"), + "brussels": ({"urbis_orthophoto", "digitaal_vlaanderen_orthophoto"}, "urbis"), } SPLITS = {"train", "val", "calibration", "test", "background-test"} @@ -56,8 +56,8 @@ def _validate_pair(sample: dict[str, Any], raster: Dataset, reference: Dataset) region = str(sample.get("region") or "").lower() if region not in REGION_SOURCES: raise SystemExit(f"Unsupported region for {sample.get('sample_slug')}: {region}") - expected_raster, expected_reference = REGION_SOURCES[region] - if raster.source_name != expected_raster: + expected_rasters, expected_reference = REGION_SOURCES[region] + if raster.source_name not in expected_rasters: raise SystemExit(f"Raster provider mismatch for {sample['sample_slug']}: {raster.source_name}") if reference.source_name != expected_reference or reference.reference_layer_name != "buildings": raise SystemExit(f"Reference provider/layer mismatch for {sample['sample_slug']}") @@ -156,6 +156,7 @@ def main() -> int: { "sample_slug": slug, "sample_role": sample.get("sample_role", "positive"), + "require_empty": bool(sample.get("require_empty", False)), "region": region, "context": sample.get("context"), "split": sample["split"], diff --git a/scripts/evaluate_belgium_building_candidate.py b/scripts/evaluate_belgium_building_candidate.py index e313af61..5be74a84 100644 --- a/scripts/evaluate_belgium_building_candidate.py +++ b/scripts/evaluate_belgium_building_candidate.py @@ -88,7 +88,13 @@ def main() -> int: manifest = json.loads(args.corpus_manifest.read_text(encoding="utf-8")) regions = {item["sample_slug"]: item["region"] for item in manifest["samples"]} pure_empty_slugs = { - item["sample_slug"] for item in manifest["samples"] if bool(item.get("require_empty")) + item["sample_slug"] + for item in manifest["samples"] + if bool(item.get("require_empty")) + or ( + item.get("sample_role") == "background_candidate" + and int(item.get("reference_feature_count") or 0) == 0 + ) } tiles = [item for item in summary["tiles"] if item.get("kept", True) and item["split"] == args.split] image_paths = [item["image_path"] for item in tiles] diff --git a/scripts/provision_belgium_building_training_portfolio.py b/scripts/provision_belgium_building_training_portfolio.py index ae71a954..ff7635c9 100644 --- a/scripts/provision_belgium_building_training_portfolio.py +++ b/scripts/provision_belgium_building_training_portfolio.py @@ -94,19 +94,19 @@ AOIS = ( REGION_CONTRACT = { "flanders": { "area_id": "2a1a064e-cfd6-46f4-981a-d6ffa0ed5b5e", - "orthophoto_product": "most_recent", + "orthophoto_product": "2025", "reference_path": "datasets/grb/acquire", "reference_product": "buildings", }, "wallonia": { "area_id": "e5fd742a-ca18-4520-abe0-d28416aa2ece", - "orthophoto_product": "wallonia_latest", + "orthophoto_product": "wallonia_2024", "reference_path": "datasets/official-vector/acquire", "reference_product": "spw_picc_buildings", }, "brussels": { "area_id": "304b413d-d81c-40fc-922e-49907b8efaaa", - "orthophoto_product": "brussels_latest", + "orthophoto_product": "brussels_2025", "reference_path": "datasets/official-vector/acquire", "reference_product": "urbis_buildings", },