132 lines
4.5 KiB
Python
132 lines
4.5 KiB
Python
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",
|
|
"dessel_heide",
|
|
"ravels_bos",
|
|
"meerhout_bos",
|
|
"geel_bel",
|
|
"arendonk_heide",
|
|
}
|
|
|
|
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_operator_background_candidates_are_unique_enough_for_hard_negative_training() -> None:
|
|
module = load_sample_preparer()
|
|
|
|
background_samples = [
|
|
sample
|
|
for sample in module.SAMPLES.values()
|
|
if sample.sample_role == "background_candidate"
|
|
]
|
|
centers = {(round(sample.center_lon, 4), round(sample.center_lat, 4)) for sample in background_samples}
|
|
half_sizes = {sample.half_size_m for sample in background_samples}
|
|
|
|
assert len(background_samples) >= 8
|
|
assert len(centers) == len(background_samples)
|
|
assert min(sample.center_lon for sample in background_samples) < 4.85
|
|
assert max(sample.center_lon for sample in background_samples) > 5.25
|
|
assert min(sample.center_lat for sample in background_samples) < 51.18
|
|
assert max(sample.center_lat for sample in background_samples) > 51.33
|
|
assert half_sizes == {260.0}
|
|
|
|
|
|
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])
|