Expand operator samples for YOLO hard negatives
This commit is contained in:
+10
-5
@@ -426,16 +426,21 @@ manifests generated for AI handoff include source CRS metadata so pixel-space
|
||||
model outputs can be transformed to WGS84 GeoJSON coordinates. Current V1 upload
|
||||
support is limited to GeoTIFF-style rasters and GeoJSON/JSON reference vectors.
|
||||
|
||||
To prepare the documented Geel/Mol/Turnhout operator sample pairs inside the
|
||||
all-in-one runtime container, run:
|
||||
To prepare the documented operator sample corpus inside the all-in-one runtime
|
||||
container, run:
|
||||
|
||||
```bash
|
||||
docker exec -it geointel python /app/scripts/prepare_operator_real_data_samples.py --samples geel,mol,turnhout
|
||||
docker exec -it geointel python3 /app/scripts/prepare_operator_real_data_samples.py
|
||||
```
|
||||
|
||||
The helper writes GeoTIFF orthophotos, GRB GBG building GeoJSON files and
|
||||
`operator_samples_manifest.json` under `/app/storage/operator-data`. These are
|
||||
runtime artifacts only and are not committed to Git.
|
||||
`operator_samples_manifest.json` under `/app/storage/operator-data`. The corpus
|
||||
contains dense reference AOIs for Geel, Mol, Turnhout, Herentals, Balen, Retie
|
||||
and Westerlo plus explicitly marked background candidates for Postel-bos,
|
||||
Lommel-heide and Kasterlee-bos. Background candidates can persist empty GRB
|
||||
FeatureCollections for negative-tile training; normal reference AOIs still fail
|
||||
when GRB returns no buildings. These are runtime artifacts only and are not
|
||||
committed to Git.
|
||||
|
||||
For model-quality calibration, run the confidence sweep wrapper:
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def load_sample_preparer():
|
||||
script_path = ROOT / "scripts" / "prepare_operator_real_data_samples.py"
|
||||
spec = importlib.util.spec_from_file_location("operator_sample_preparer", script_path)
|
||||
assert spec is not None
|
||||
assert spec.loader is not None
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def test_operator_sample_registry_includes_kempen_reference_and_background_candidates() -> None:
|
||||
module = load_sample_preparer()
|
||||
|
||||
expected_reference_slugs = {"geel", "mol", "turnhout", "herentals", "balen", "retie", "westerlo"}
|
||||
expected_background_slugs = {"postel_bos", "lommel_heide", "kasterlee_bos"}
|
||||
|
||||
assert expected_reference_slugs.issubset(module.SAMPLES)
|
||||
assert expected_background_slugs.issubset(module.SAMPLES)
|
||||
assert all(not module.SAMPLES[slug].allow_empty_reference for slug in expected_reference_slugs)
|
||||
assert all(module.SAMPLES[slug].allow_empty_reference for slug in expected_background_slugs)
|
||||
assert all(module.SAMPLES[slug].sample_role == "background_candidate" for slug in expected_background_slugs)
|
||||
|
||||
|
||||
def test_background_candidate_can_write_empty_reference_geojson(tmp_path: Path, monkeypatch) -> None:
|
||||
module = load_sample_preparer()
|
||||
|
||||
class EmptyFeatureResponse:
|
||||
headers = {"content-type": "application/geo+json"}
|
||||
|
||||
def raise_for_status(self) -> None:
|
||||
return None
|
||||
|
||||
def json(self) -> dict:
|
||||
return {"type": "FeatureCollection", "features": []}
|
||||
|
||||
class FakeRequests:
|
||||
@staticmethod
|
||||
def get(*args, **kwargs):
|
||||
return EmptyFeatureResponse()
|
||||
|
||||
monkeypatch.setattr(module, "requests", FakeRequests)
|
||||
monkeypatch.setattr(module, "prepared_url", lambda url, params: f"{url}?prepared=true")
|
||||
|
||||
sample = module.OperatorSample(
|
||||
slug="background",
|
||||
display_name="Background",
|
||||
center_lon=5.0,
|
||||
center_lat=51.0,
|
||||
allow_empty_reference=True,
|
||||
sample_role="background_candidate",
|
||||
)
|
||||
reference_path = tmp_path / "background.geojson"
|
||||
|
||||
source_url, feature_count = module.fetch_reference(sample, reference_path, [4.9, 50.9, 5.1, 51.1])
|
||||
|
||||
assert source_url.endswith("?prepared=true")
|
||||
assert feature_count == 0
|
||||
payload = reference_path.read_text(encoding="utf-8")
|
||||
assert '"features": []' in payload
|
||||
assert '"sample_role": "background_candidate"' in payload
|
||||
|
||||
|
||||
def test_reference_sample_still_rejects_empty_grb_response(tmp_path: Path, monkeypatch) -> None:
|
||||
module = load_sample_preparer()
|
||||
|
||||
class EmptyFeatureResponse:
|
||||
def raise_for_status(self) -> None:
|
||||
return None
|
||||
|
||||
def json(self) -> dict:
|
||||
return {"type": "FeatureCollection", "features": []}
|
||||
|
||||
class FakeRequests:
|
||||
@staticmethod
|
||||
def get(*args, **kwargs):
|
||||
return EmptyFeatureResponse()
|
||||
|
||||
monkeypatch.setattr(module, "requests", FakeRequests)
|
||||
monkeypatch.setattr(module, "prepared_url", lambda url, params: f"{url}?prepared=true")
|
||||
|
||||
sample = module.OperatorSample(
|
||||
slug="urban",
|
||||
display_name="Urban",
|
||||
center_lon=5.0,
|
||||
center_lat=51.0,
|
||||
)
|
||||
|
||||
with pytest.raises(SystemExit, match="returned no building features"):
|
||||
module.fetch_reference(sample, tmp_path / "urban.geojson", [4.9, 50.9, 5.1, 51.1])
|
||||
Reference in New Issue
Block a user