Expand operator hard-negative AOIs
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-07-09 02:15:54 +02:00
parent 34f09fe323
commit 54e6ad888e
6 changed files with 127 additions and 10 deletions
+1
View File
@@ -1414,3 +1414,4 @@ Added:
- Added `scripts/audit_operator_yolo_dataset_quality.py`, an operator-only YOLO tile dataset quality audit that produces JSON and Markdown reports for sample coverage, split coverage, repeated hard-negative pressure and label-size integrity before further training runs. - Added `scripts/audit_operator_yolo_dataset_quality.py`, an operator-only YOLO tile dataset quality audit that produces JSON and Markdown reports for sample coverage, split coverage, repeated hard-negative pressure and label-size integrity before further training runs.
- Added pytest coverage and readiness syntax checking for the new operator YOLO dataset audit script. - Added pytest coverage and readiness syntax checking for the new operator YOLO dataset audit script.
- Recorded live Tower audit results showing `yolo-building-tile-expanded160` as the clean current baseline and r4/r8 hard-negative datasets as repeat-heavy evidence sets that need more unique background AOIs before further hard-negative training. - Recorded live Tower audit results showing `yolo-building-tile-expanded160` as the clean current baseline and r4/r8 hard-negative datasets as repeat-heavy evidence sets that need more unique background AOIs before further hard-negative training.
- Expanded the explicit operator background-candidate AOI registry from 3 to 9 unique hard-negative locations and added tests for diversity/spread before further YOLO training.
@@ -25,7 +25,16 @@ def test_operator_sample_registry_includes_kempen_reference_and_background_candi
module = load_sample_preparer() module = load_sample_preparer()
expected_reference_slugs = {"geel", "mol", "turnhout", "herentals", "balen", "retie", "westerlo"} expected_reference_slugs = {"geel", "mol", "turnhout", "herentals", "balen", "retie", "westerlo"}
expected_background_slugs = {"postel_bos", "lommel_heide", "kasterlee_bos"} 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_reference_slugs.issubset(module.SAMPLES)
assert expected_background_slugs.issubset(module.SAMPLES) assert expected_background_slugs.issubset(module.SAMPLES)
@@ -34,6 +43,26 @@ def test_operator_sample_registry_includes_kempen_reference_and_background_candi
assert all(module.SAMPLES[slug].sample_role == "background_candidate" 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: def test_background_candidate_can_write_empty_reference_geojson(tmp_path: Path, monkeypatch) -> None:
module = load_sample_preparer() module = load_sample_preparer()
+22
View File
@@ -5670,3 +5670,25 @@ Open:
## Next recommended pass ## Next recommended pass
- Add more unique hard-negative/background AOIs before another hard-negative training run. The current label files are clean, so the bottleneck is dataset diversity and balance rather than label-file corruption. - Add more unique hard-negative/background AOIs before another hard-negative training run. The current label files are clean, so the bottleneck is dataset diversity and balance rather than label-file corruption.
# Sprint 147 - Unique hard-negative AOI expansion
## What changed
- Expanded `scripts/prepare_operator_real_data_samples.py` with six additional explicit background-candidate AOIs: Dessel-heide, Ravels-bos, Meerhout-bos, Geel-Bel, Arendonk-heide and Herenthout-bos.
- Background candidates remain operator/runtime samples only. They are not product providers, not fixtures and not automatic app fetches.
- Added sample-registry test coverage for minimum unique background count, unique centers and regional spread.
- Updated operator documentation with the expanded default corpus and the next required Tower regeneration step.
## What was tested
- `python -m pytest backend\tests\test_sprint131_operator_sample_expansion.py -q`
## Known limitations
- The new AOIs still need to be prepared on Tower before they affect the live operator YOLO tile exports.
- GRB may return sparse buildings in some background candidates; they remain valid hard-negative candidates only after the manifest and tile audit confirm their actual labels.
## Next recommended pass
- Pull this commit on Tower, rerun `prepare_operator_real_data_samples.py`, export a new hard-negative tile dataset without excessive repeat pressure and audit it before training.
+9 -1
View File
@@ -442,5 +442,13 @@ This file now starts with the current implementation status. Older preparation/b
- [x] Report sample coverage, validation coverage, repeated hard-negative pressure and YOLO label area integrity. - [x] Report sample coverage, validation coverage, repeated hard-negative pressure and YOLO label area integrity.
- [x] Wire the audit script into the readiness syntax gate. - [x] Wire the audit script into the readiness syntax gate.
- [x] Use live audit output to decide whether the next model pass needs more positive AOIs, label cleanup or unique hard negatives. - [x] Use live audit output to decide whether the next model pass needs more positive AOIs, label cleanup or unique hard negatives.
- [ ] Add more unique background/hard-negative AOIs before repeating hard-negative-balanced YOLO training. - [x] Add more unique background/hard-negative AOIs before repeating hard-negative-balanced YOLO training.
- [ ] Keep `yolo-building-tile-expanded160` as the clean current training baseline; avoid promoting r4/r8 repeat-heavy datasets as defaults. - [ ] Keep `yolo-building-tile-expanded160` as the clean current training baseline; avoid promoting r4/r8 repeat-heavy datasets as defaults.
- [ ] Regenerate Tower operator samples, export a new unique-hard-negative tile dataset and rerun the dataset audit before training.
# Sprint 147 - Unique hard-negative AOI expansion
- [x] Expand the documented operator background candidates from 3 to 9 unique AOIs.
- [x] Keep every new background AOI explicit, `allow_empty_reference=True`, and `sample_role='background_candidate'`.
- [x] Add test coverage for minimum background candidate count, unique centers and regional spread.
- [ ] Prepare the new samples on Tower and build a fresh hard-negative tile dataset.
+11 -8
View File
@@ -182,13 +182,14 @@ This writes GeoTIFF/GeoJSON pairs and `operator_samples_manifest.json` under
`/app/storage/operator-data` inside the container, which maps to `/app/storage/operator-data` inside the container, which maps to
`storage/operator-data` in the Tower appdata checkout. The default corpus `storage/operator-data` in the Tower appdata checkout. The default corpus
contains reference AOIs for Geel, Mol, Turnhout, Herentals, Balen, Retie and contains reference AOIs for Geel, Mol, Turnhout, Herentals, Balen, Retie and
Westerlo plus background candidates for Postel-bos, Lommel-heide and Westerlo plus background candidates for Postel-bos, Lommel-heide,
Kasterlee-bos. Normal reference AOIs still fail when GRB returns no buildings; Kasterlee-bos, Dessel-heide, Ravels-bos, Meerhout-bos, Geel-Bel,
background candidates are explicitly marked with `sample_role` and may write an Arendonk-heide and Herenthout-bos. Normal reference AOIs still fail when GRB
empty reference FeatureCollection for negative-tile training. The helper fetches returns no buildings; background candidates are explicitly marked with
only the explicit documented AOIs, records Digitaal Vlaanderen attribution and `sample_role` and may write an empty reference FeatureCollection for
reuses existing files by default. Use `--force` only when the local runtime negative-tile training. The helper fetches only the explicit documented AOIs,
artifacts should be regenerated. records Digitaal Vlaanderen attribution and reuses existing files by default.
Use `--force` only when the local runtime artifacts should be regenerated.
The real-data smoke is intentionally mutating and refuses to run without The real-data smoke is intentionally mutating and refuses to run without
operator-supplied files. Current V1 upload support expects a georeferenced operator-supplied files. Current V1 upload support expects a georeferenced
@@ -351,6 +352,8 @@ Current Tower audit status:
- `yolo-building-tile-hardneg160r4` and `yolo-building-tile-hardneg160r8`: - `yolo-building-tile-hardneg160r4` and `yolo-building-tile-hardneg160r8`:
repeat-heavy hard-negative variants; useful evidence, but add more unique repeat-heavy hard-negative variants; useful evidence, but add more unique
background AOIs before training another hard-negative-balanced candidate. background AOIs before training another hard-negative-balanced candidate.
- Regenerate `operator_samples_manifest.json` after pulling Sprint 147+ so the
expanded unique background AOI set is available for the next tile export.
For hard-negative-balanced experiments, repeat only train-split negative tiles For hard-negative-balanced experiments, repeat only train-split negative tiles
from samples marked `sample_role=background_candidate`: from samples marked `sample_role=background_candidate`:
@@ -415,7 +418,7 @@ before changing model defaults:
```bash ```bash
OPERATOR_SAMPLE_MANIFEST_PATH=storage/operator-data/operator_samples_manifest.json \ OPERATOR_SAMPLE_MANIFEST_PATH=storage/operator-data/operator_samples_manifest.json \
OPERATOR_BACKGROUND_SAMPLE_SLUGS="postel_bos lommel_heide kasterlee_bos" \ OPERATOR_BACKGROUND_SAMPLE_SLUGS="postel_bos lommel_heide kasterlee_bos dessel_heide ravels_bos meerhout_bos geel_bel arendonk_heide herenthout_bos" \
QUALITY_MODEL_ASSET_IDS="geointel-building-yolov8n-expanded160e50-pt geointel-building-yolov8n-tile30-pt yolov8s-building-segmentation-pt" \ QUALITY_MODEL_ASSET_IDS="geointel-building-yolov8n-expanded160e50-pt geointel-building-yolov8n-tile30-pt yolov8s-building-segmentation-pt" \
QUALITY_TILE_SIZES="640" \ QUALITY_TILE_SIZES="640" \
QUALITY_TILE_OVERLAPS="64" \ QUALITY_TILE_OVERLAPS="64" \
@@ -114,6 +114,60 @@ SAMPLES: dict[str, OperatorSample] = {
sample_role="background_candidate", sample_role="background_candidate",
allow_empty_reference=True, allow_empty_reference=True,
), ),
"dessel_heide": OperatorSample(
slug="dessel_heide",
display_name="Dessel heath background candidate",
center_lon=5.092,
center_lat=51.235,
half_size_m=260.0,
sample_role="background_candidate",
allow_empty_reference=True,
),
"ravels_bos": OperatorSample(
slug="ravels_bos",
display_name="Ravels forest background candidate",
center_lon=4.977,
center_lat=51.384,
half_size_m=260.0,
sample_role="background_candidate",
allow_empty_reference=True,
),
"meerhout_bos": OperatorSample(
slug="meerhout_bos",
display_name="Meerhout forest background candidate",
center_lon=5.069,
center_lat=51.115,
half_size_m=260.0,
sample_role="background_candidate",
allow_empty_reference=True,
),
"geel_bel": OperatorSample(
slug="geel_bel",
display_name="Geel-Bel rural background candidate",
center_lon=5.046,
center_lat=51.137,
half_size_m=260.0,
sample_role="background_candidate",
allow_empty_reference=True,
),
"arendonk_heide": OperatorSample(
slug="arendonk_heide",
display_name="Arendonk heath background candidate",
center_lon=5.238,
center_lat=51.334,
half_size_m=260.0,
sample_role="background_candidate",
allow_empty_reference=True,
),
"herenthout_bos": OperatorSample(
slug="herenthout_bos",
display_name="Herenthout forest background candidate",
center_lon=4.781,
center_lat=51.143,
half_size_m=260.0,
sample_role="background_candidate",
allow_empty_reference=True,
),
} }