Raise configured YOLO max detections
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 10:14:07 +02:00
parent 9bef905da8
commit 25f098bde0
16 changed files with 82 additions and 1 deletions
+1
View File
@@ -15,6 +15,7 @@ YOLO_CONFIG_DIR=./storage/ultralytics
YOLO_DEVICE=cpu
YOLO_IMAGE_SIZE=640
YOLO_MAX_TILES=100
YOLO_MAX_DETECTIONS=1000
YOLO_BATCH_SIZE=1
ENABLE_GRB_WFS=false
GRB_WFS_URL=
+8
View File
@@ -7,6 +7,14 @@
# Changelog
## Sprint 148 YOLO max-detection cap hardening (2026-07-09)
- Added `YOLO_MAX_DETECTIONS` with default `1000` and forward it to Ultralytics as `max_det`.
- Wired the setting through `.env.example`, Docker Compose, Unraid env examples and the Dockerman run script.
- Documented why dense building AOIs should not inherit the Ultralytics default cap of 300 detections before persisted QA/QC.
- Added regression coverage for adapter forwarding and Docker/Unraid runtime exposure.
- No model was activated, no detections were faked, and no API route or migration changed.
## Sprint 147 AOI512 YOLOv8s scale-match candidate gate (2026-07-09)
- Built and audited an AOI-scale YOLO dataset at `512px` tile size to test whether the previous `160px` training scale was the main quality blocker.
+5
View File
@@ -359,6 +359,7 @@ YOLO_CONFIG_DIR=/app/storage/ultralytics
YOLO_DEVICE=cpu
YOLO_IMAGE_SIZE=640
YOLO_MAX_TILES=100
YOLO_MAX_DETECTIONS=1000
YOLO_BATCH_SIZE=1
```
@@ -384,6 +385,10 @@ python scripts/yolo_preflight.py --model-path /absolute/path/to/local-model.pt -
The preflight checks configuration, dependency availability, local model file existence, tile manifest validity, tile count and referenced tile paths. JSON output also includes runtime diagnostics for the model directory, `YOLO_CONFIG_DIR`, installed `torch`/`ultralytics` versions and CUDA availability when dependency checks pass. It does not load a YOLO model, run inference or download weights.
`YOLO_MAX_DETECTIONS` is forwarded to Ultralytics as `max_det`. The default is
`1000` because dense building AOIs can exceed the upstream default cap of 300
detections before QA/QC can measure recall honestly.
The same read-only status is available through the API and Detection Lab UI:
```bash
+1
View File
@@ -31,6 +31,7 @@ class Settings(BaseSettings):
yolo_device: str = Field(default="cpu", validation_alias="YOLO_DEVICE")
yolo_image_size: int = Field(default=640, validation_alias="YOLO_IMAGE_SIZE")
yolo_max_tiles: int = Field(default=100, validation_alias="YOLO_MAX_TILES")
yolo_max_detections: int = Field(default=1000, validation_alias="YOLO_MAX_DETECTIONS")
yolo_batch_size: int = Field(default=1, validation_alias="YOLO_BATCH_SIZE")
cors_origins: list[str] | str = Field(
default=["http://localhost:5173", "http://127.0.0.1:5173"],
+1
View File
@@ -72,6 +72,7 @@ class YoloDetectionAdapter:
conf=float(confidence_threshold),
imgsz=int(self.settings.yolo_image_size),
device=self.settings.yolo_device,
max_det=int(self.settings.yolo_max_detections),
verbose=False,
)
except AppError:
@@ -93,6 +93,7 @@ def test_env_example_uses_runtime_env_names_read_by_backend_and_frontend() -> No
assert "YOLO_MODEL_PATH=" in env_example
assert "YOLO_CONFIG_DIR=./storage/ultralytics" in env_example
assert "YOLO_MAX_TILES=100" in env_example
assert "YOLO_MAX_DETECTIONS=1000" in env_example
assert "ENABLE_YOLO" not in env_example
assert "ENABLE_SAM" not in env_example
assert "VITE_API_BASE_URL=" in env_example
@@ -245,4 +246,5 @@ def test_unraid_deploy_passes_ai_build_arg_and_yolo_runtime_env() -> None:
assert '-e YOLO_MODELS_DIR="$YOLO_MODELS_DIR"' in run_script
assert '-e YOLO_MODEL_PATH="$YOLO_MODEL_PATH"' in run_script
assert '-e YOLO_MAX_TILES="$YOLO_MAX_TILES"' in run_script
assert '-e YOLO_MAX_DETECTIONS="$YOLO_MAX_DETECTIONS"' in run_script
assert "-v \"${GEOINTEL_MODELS_PATH}:/app/models\"" in run_script
+16 -1
View File
@@ -93,7 +93,7 @@ class RecordingPredictModel:
def __init__(self) -> None:
self.seen_sources: list[dict] = []
def predict(self, *, source, conf, imgsz, device, verbose):
def predict(self, *, source, conf, imgsz, device, verbose, max_det):
from PIL import Image
with Image.open(source) as image:
@@ -106,6 +106,7 @@ class RecordingPredictModel:
"imgsz": imgsz,
"device": device,
"verbose": verbose,
"max_det": max_det,
}
)
return []
@@ -399,6 +400,20 @@ def test_yolo_adapter_converts_single_band_tiles_to_rgb_before_prediction(tmp_pa
assert model.seen_sources[0]["imgsz"] == 64
assert model.seen_sources[0]["device"] == "cpu"
assert model.seen_sources[0]["verbose"] is False
assert model.seen_sources[0]["max_det"] == 1000
def test_yolo_adapter_uses_configured_max_detections(tmp_path: Path) -> None:
Image = pytest.importorskip("PIL.Image")
tile_path = tmp_path / "rgb_tile.png"
Image.new("RGB", (16, 16), (10, 20, 30)).save(tile_path)
model = RecordingPredictModel()
settings = _settings(tmp_path, yolo_max_detections=1500)
detections = YoloDetectionAdapter(settings).predict_tile(model, tile_path, confidence_threshold=0.25)
assert detections == []
assert model.seen_sources[0]["max_det"] == 1500
def test_yolo_adapter_wraps_prediction_runtime_errors(tmp_path: Path) -> None:
+1
View File
@@ -36,4 +36,5 @@ YOLO_CONFIG_DIR=/app/storage/ultralytics
YOLO_DEVICE=cpu
YOLO_IMAGE_SIZE=640
YOLO_MAX_TILES=100
YOLO_MAX_DETECTIONS=1000
YOLO_BATCH_SIZE=1
+2
View File
@@ -30,6 +30,7 @@ YOLO_CONFIG_DIR="${YOLO_CONFIG_DIR:-/app/storage/ultralytics}"
YOLO_DEVICE="${YOLO_DEVICE:-cpu}"
YOLO_IMAGE_SIZE="${YOLO_IMAGE_SIZE:-640}"
YOLO_MAX_TILES="${YOLO_MAX_TILES:-100}"
YOLO_MAX_DETECTIONS="${YOLO_MAX_DETECTIONS:-1000}"
YOLO_BATCH_SIZE="${YOLO_BATCH_SIZE:-1}"
install_dockerman_metadata() {
@@ -90,6 +91,7 @@ docker run -d \
-e YOLO_DEVICE="$YOLO_DEVICE" \
-e YOLO_IMAGE_SIZE="$YOLO_IMAGE_SIZE" \
-e YOLO_MAX_TILES="$YOLO_MAX_TILES" \
-e YOLO_MAX_DETECTIONS="$YOLO_MAX_DETECTIONS" \
-e YOLO_BATCH_SIZE="$YOLO_BATCH_SIZE" \
-v "${GEOINTEL_POSTGIS_DATA_PATH}:/var/lib/postgresql/data" \
-v "${GEOINTEL_STORAGE_PATH}:/app/storage" \
+1
View File
@@ -28,6 +28,7 @@ services:
YOLO_DEVICE: ${YOLO_DEVICE:-cpu}
YOLO_IMAGE_SIZE: ${YOLO_IMAGE_SIZE:-640}
YOLO_MAX_TILES: ${YOLO_MAX_TILES:-100}
YOLO_MAX_DETECTIONS: ${YOLO_MAX_DETECTIONS:-1000}
YOLO_BATCH_SIZE: ${YOLO_BATCH_SIZE:-1}
ports:
- "${GEOINTEL_FRONTEND_PORT:-1202}:80"
+1
View File
@@ -33,6 +33,7 @@ services:
YOLO_DEVICE: ${YOLO_DEVICE:-cpu}
YOLO_IMAGE_SIZE: ${YOLO_IMAGE_SIZE:-640}
YOLO_MAX_TILES: ${YOLO_MAX_TILES:-100}
YOLO_MAX_DETECTIONS: ${YOLO_MAX_DETECTIONS:-1000}
YOLO_BATCH_SIZE: ${YOLO_BATCH_SIZE:-1}
ports:
- "${GEOINTEL_BACKEND_PORT:-8000}:8000"
+8
View File
@@ -109,8 +109,16 @@ Environment variables:
- `YOLO_DEVICE`
- `YOLO_IMAGE_SIZE`
- `YOLO_MAX_TILES`
- `YOLO_MAX_DETECTIONS`
- `YOLO_BATCH_SIZE`
`YOLO_MAX_DETECTIONS` is forwarded to Ultralytics as `max_det` for each
prediction call. GeoIntel defaults it to `1000` because building-rich AOIs can
contain far more than the Ultralytics default of 300 candidate boxes; keeping
the upstream default would cap recall before QA/QC begins. Operators may lower
the value for small rasters or raise it for dense urban tiles after reviewing
runtime and false-positive behavior.
### Local model asset catalog
GeoIntel can list local runtime model files mounted into the backend model
+3
View File
@@ -782,6 +782,9 @@ Validation errors:
- `DETECTION_TILE_MANIFEST_NOT_FOUND` when the provided manifest path does not exist.
- `DETECTION_TILE_MANIFEST_INVALID` when the manifest cannot be parsed or lacks tile metadata.
- `DETECTION_TILE_LIMIT_EXCEEDED` when the manifest exceeds `YOLO_MAX_TILES`.
- Configured YOLO inference forwards `YOLO_MAX_DETECTIONS` to Ultralytics
`max_det` and defaults to `1000` so dense building AOIs are not silently
limited by the upstream default of 300 detections before persisted QA/QC.
- `DETECTION_DEPENDENCY_UNAVAILABLE` when YOLO dependencies are not installed.
- `DETECTION_MODEL_LOAD_FAILED` when the local model file exists but cannot be loaded.
+29
View File
@@ -1,3 +1,32 @@
## Sprint 148 YOLO max-detection cap hardening (2026-07-09)
Changed:
- Added backend setting `YOLO_MAX_DETECTIONS` / `Settings.yolo_max_detections`.
- `YoloDetectionAdapter` now forwards the value to Ultralytics as `max_det`.
- Default is `1000` instead of relying on Ultralytics' upstream default of 300.
- Added Docker/Unraid/runtime wiring:
- `.env.example`
- `docker-compose.yml`
- `docker-compose.unraid.yml`
- `deploy/unraid/geointel.env.example`
- `deploy/unraid/run-dockerman-container.sh`
- Updated backend/API/AI environment documentation.
Why:
- Real Kempen building AOIs often contain more than 300 reference buildings.
- The previous configured-YOLO path could saturate at 300 detections before QA/QC, capping recall independently of model quality.
- This does not activate a model and does not fake detections; it removes an inference runtime cap so persisted QA/QC can measure candidate models honestly.
Tested:
- Red step: targeted YOLO adapter tests failed because `max_det` was not passed to the model.
- `python -m pytest backend\tests\test_sprint8b_yolo_foundation.py::test_yolo_adapter_converts_single_band_tiles_to_rgb_before_prediction backend\tests\test_sprint8b_yolo_foundation.py::test_yolo_adapter_uses_configured_max_detections -q` (`2 passed`)
- Red step: Docker runtime config tests failed before `.env.example` and Unraid runner exposed `YOLO_MAX_DETECTIONS`.
- `python -m pytest backend\tests\test_docker_runtime_config.py::test_env_example_uses_runtime_env_names_read_by_backend_and_frontend backend\tests\test_docker_runtime_config.py::test_unraid_deploy_passes_ai_build_arg_and_yolo_runtime_env backend\tests\test_sprint8b_yolo_foundation.py -q` (`16 passed`)
Open:
- Rebuild/redeploy the Tower all-in-one image before rerunning live calibration so the container uses `YOLO_MAX_DETECTIONS=1000`.
- After deploy, rerun at least one high-density AOI calibration to confirm detection counts are no longer capped at 300.
## Sprint 147 AOI512 YOLOv8s scale-match candidate gate (2026-07-09)
Changed:
+1
View File
@@ -21,6 +21,7 @@ YOLO_ENABLED=false
YOLO_MODEL_PATH=
YOLO_MODEL_VERSION=
YOLO_MAX_TILES=100
YOLO_MAX_DETECTIONS=1000
ENABLE_GRB_WFS=false
GRB_WFS_URL=
OSM_OVERPASS_URL=https://overpass-api.de/api/interpreter
+2
View File
@@ -110,9 +110,11 @@ This file now starts with the current implementation status. Older preparation/b
- [x] Benchmark an external remote-sensing YOLOv8l building candidate as an explicit local model asset.
- [x] Train and gate the `uniquehardneg160e50` YOLOv8s candidate through 7 positive AOIs and 9 hard-negative/background samples.
- [x] Train and gate an AOI-scale `aoi512e80` YOLOv8s candidate to test the 160px training-scale hypothesis.
- [x] Raise configured-YOLO `max_det` through `YOLO_MAX_DETECTIONS` so dense AOIs are not capped at 300 detections before QA/QC.
- [ ] Find or train a materially stronger aerial/Kempen building model candidate; `geointel-building-yolov8n-expanded160e50-pt` is the best current dense-AOI candidate but still too weak and too noisy for a V1 default.
- [ ] Train a higher-capacity local aerial-building detector with stronger positive recall while preserving the hard-negative false-positive gate.
- [ ] Add more diverse positive AOIs and revisit geometry-to-box label strategy before the next default-model training attempt.
- [ ] Rerun live dense-AOI calibration after redeploy with `YOLO_MAX_DETECTIONS=1000` to measure uncapped recall and false-positive pressure.
## Sprint 8 status