Files
geointel/docs/AI_PIPELINES.md
T
Jens b76cd1837b
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s
fix(ai): bind model scope to immutable geometry
2026-08-09 10:54:52 +02:00

794 lines
35 KiB
Markdown

# AI Pipelines
The cross-task PyTorch scope, capability matrix and national promotion waves are
defined in `docs/PYTORCH_MODEL_PROGRAM.md`. PyTorch is used only for trainable
imagery tasks; authoritative GIS measurements remain source-derived. The
single-class tile exporter accepts explicit `--class-name`,
`--reference-source` and `--reference-layer` bindings and persists them in its
evidence summary. Production Tower training uses `TRAIN_DEVICE=cuda:0` with
`TRAIN_REQUIRE_CUDA=true`, which fails closed without CUDA and records the
PyTorch/CUDA runtime in `training_summary.json`.
## 1. Object Detection Pipeline
```text
Raster dataset
Clip to analysis area
Tile raster
Normalize tiles
Run YOLO/PyTorch inference
Filter by confidence
Convert pixel boxes to geospatial polygons
Merge overlapping detections
Store in PostGIS
Expose as GeoJSON layer
Run QA/QC if reference data exists
```
### Sprint 8 foundation status
Sprint 8 implements the detection persistence and execution boundary only:
- `detections` are first-class PostGIS records linked to project, dataset, job and analysis run.
- `analysis_runs` remain separate from jobs and store model metadata, parameters, result summaries and lifecycle status.
- `yolo-placeholder` reports `not_configured`; no YOLO/PyTorch model is downloaded or executed.
- `manual-fixture-detector` is test/demo-only and persists detections only when `fixture_mode=true` and fixture detections are explicitly supplied.
- Normal application behavior must not create fake detections.
### Sprint 8B configured YOLO status
Sprint 8B adds an import-safe real YOLO adapter path:
- `ultralytics` and `torch` are optional backend extras, not default runtime dependencies.
- `yolo-configured` reports `not_configured` until `YOLO_ENABLED=true`, `YOLO_MODEL_PATH` points to an existing local model file and optional AI dependencies are installed.
- GeoIntel never downloads model weights automatically.
- Real YOLO inference uses an existing raster tile manifest generated by the raster tile operation.
- YOLO raster tiles are normalized to RGB for inference when the tile artifact is not already a 3-band RGB image; the persisted georeferencing still comes from the tile manifest.
- YOLO pixel boxes are converted to EPSG:4326 detection polygons from tile transform or tile bounds metadata.
- YOLO class labels are normalized to lowercase for persisted detection records and filtering, while the original model label remains available in detection provenance.
- Detection runs remain synchronous behind the existing job and analysis-run persistence boundary for Sprint 8B.
The guided Detection Lab action does not introduce another inference pipeline. It creates a tile manifest through the existing raster service, validates that manifest and the selected local asset through YOLO preflight, then invokes the same configured detection service. Persisted `Detection` geometry remains the authoritative map output; QA continues to compare those rows against persisted reference `vector_features` and stores `QualityCheck`/`Metric` records.
### Sprint 13 YOLO operational preflight
Sprint 13 adds a local preflight command for configured YOLO operation:
```bash
python scripts/yolo_preflight.py --model-path /absolute/path/to/model.pt --tile-manifest-path /absolute/path/to/manifest.json
```
For machines without optional AI dependencies, path and manifest checks can be exercised without pretending inference is available:
```bash
python scripts/yolo_preflight.py --model-path /absolute/path/to/model.pt --tile-manifest-path /absolute/path/to/manifest.json --assume-dependencies --json
```
The preflight checks:
- `YOLO_ENABLED` / explicit enabled state;
- optional dependency availability unless `--assume-dependencies` is used;
- local model file existence;
- tile manifest JSON validity;
- tile count against `YOLO_MAX_TILES`;
- referenced tile file existence.
JSON output also reports runtime diagnostics: whether dependencies were assumed,
the configured model directory, `YOLO_CONFIG_DIR`, installed `torch` and
`ultralytics` versions, and CUDA availability when dependency checks pass.
The preflight does not load the model, does not import Ultralytics unless dependency discovery requires package metadata, does not run inference and never downloads model weights.
Sprint 25 adds an explicit local model compatibility smoke:
```bash
python scripts/yolo_preflight.py --model-path /absolute/path/to/model.pt --tile-manifest-path /absolute/path/to/manifest.json --check-model-load --json
```
`--check-model-load` requires real optional AI dependencies and an existing local
model file. It loads that local file through the configured adapter to verify
Ultralytics/PyTorch compatibility, but it still does not run tile prediction and
does not download weights. It cannot be combined with `--assume-dependencies`
because that would turn the smoke into a false positive.
Docker AI dependencies remain opt-in. Set `GEOINTEL_INSTALL_AI=true`
at build time to install the backend `.[gis,ai]` extra into the container. Leave
it unset or `false` for the default GIS-only image. Runtime model files should be
mounted into the container, for example `/app/models/local-model.pt`, and enabled
with `YOLO_ENABLED=true` plus `YOLO_MODEL_PATH=/app/models/local-model.pt`.
GeoIntel never downloads weights automatically.
Environment variables:
- `GEOINTEL_INSTALL_AI`
- `YOLO_ENABLED`
- `YOLO_MODELS_DIR`
- `YOLO_MODEL_PATH`
- `YOLO_MODEL_ID`
- `YOLO_MODEL_DISPLAY_NAME`
- `YOLO_MODEL_VERSION`
- `YOLO_DEVICE`
- `YOLO_REQUIRE_CUDA` (set to `true` on the production server; inference then
fails closed when CUDA is unavailable or `YOLO_DEVICE` selects CPU)
- `YOLO_MODEL_CLASSES` (the active promoted detector is `building` only)
- `YOLO_ENFORCE_VALIDATION_SCOPE` (keep `true` in production)
- `YOLO_VALIDATION_SCOPE_MANIFEST_PATH` and
`YOLO_VALIDATION_SCOPE_MANIFEST_SHA256` (production accepts inference only
when the exact active model bytes match the manifest and the complete
persisted Dataset AOI is covered by its valid EPSG:4326 geometry)
- `YOLO_VALIDATED_AREA_NAMES` is deprecated display metadata and never grants
inference access
- `YOLO_IMAGE_SIZE`
- `YOLO_MAX_TILES`
- `YOLO_MAX_DETECTIONS`
- `YOLO_DUPLICATE_IOU_THRESHOLD`
- `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.
Create a new immutable scope artifact whenever either the model bytes or the
governed validation boundary changes:
```bash
python /app/scripts/build_model_validation_scope_manifest.py \
--model /app/models/active-building.pt \
--model-id yolo-configured \
--scope-geojson /app/storage/operator-data/geographic-scopes/kempen-transport-region/kempen_transport_region_boundary_YYYY-MM-DD.geojson \
--scope-key kempen-transport-region \
--authority "Digitaal Vlaanderen VRBG/Refgem" \
--snapshot-date YYYY-MM-DD \
--output /app/storage/operator-data/model-validation-scopes/active-building-model.json
```
The command refuses to overwrite an existing manifest and prints the checksum
for `YOLO_VALIDATION_SCOPE_MANIFEST_SHA256`. Area names are intentionally not
part of this decision: they are mutable presentation text, not accuracy or
authorization evidence.
After YOLO boxes are georeferenced, configured-YOLO runs apply a GeoIntel
cross-tile duplicate suppression pass before persistence. Candidates are grouped
by canonical class and sorted by confidence; lower-confidence same-class
candidates with EPSG:4326 geometry IoU greater than or equal to
`YOLO_DUPLICATE_IOU_THRESHOLD` are suppressed. The default is `0.5`; set it to
`0` to disable this post-processing for operator debugging. Run summaries record
raw, persisted and suppressed detection counts so calibration evidence remains
auditable.
### Persisted false-positive visual review
Detection QA labels a candidate as a false-positive only relative to the
selected persisted reference dataset and matching tolerance. That finding is
not automatically a model error: the reference can be incomplete or stale, and
alignment can be wrong. GeoIntel therefore exposes persisted detection
confidence/model/tile/bbox provenance in the existing QA evidence GeoJSON and
provides a read-only contact-sheet workflow.
The operator must explicitly select one of:
- `confirmed_model_false_positive`;
- `reference_gap_or_change`;
- `qa_alignment_mismatch`;
- `uncertain`;
- `unreviewed`.
Only records explicitly marked `confirmed_model_false_positive` are emitted by
the validator as possible hard-negative review input. The workflow does not
train a model, mutate QA persistence, fetch data or infer review decisions.
### Persisted false-negative visual review
False negatives require the same manual distinction. A missed reference can be
a true model miss, a stale reference, an obscured object, a box-to-footprint
matching failure or an object outside the raster actually presented to the
model. The read-only false-negative renderer resolves the persisted tile
manifest from the fixed-threshold run and overlays:
- red: the missed GRB/reference footprint;
- blue: nearby persisted candidate detections;
- green: nearby matched reference footprints.
The decision contract is `confirmed_model_false_negative`,
`reference_gap_or_change`, `qa_alignment_mismatch`,
`imagery_obscured_or_uncertain` or `unreviewed`. References outside every
persisted inference tile are written to a separate exclusion GeoJSON and are
not treated as reviewable model misses. This renderer does not alter persisted
QA metrics; coverage-adjusted values remain audit diagnostics until the QA
service evaluation population is deliberately hardened.
`validate_detection_false_negative_review_decisions.py` provides the same
fail-closed validation as the false-positive workflow. It requires an exact
one-to-one set of reviewed reference ids and emits only explicit
`confirmed_model_false_negative` geometries. `--require-complete` rejects any
remaining `unreviewed` row. The July 2026 96-card review is recorded in
`docs/reviews/2026-07-15-small-building-model-review.md`; it yielded no novel,
leakage-free labels and therefore did not trigger model training.
### Local model asset catalog
GeoIntel can list local runtime model files mounted into the backend model
directory through `GET /api/v1/detection/model-assets`. The catalog is
filesystem-backed and read-only: it reports existing `.pt`, `.onnx` and
`.engine` files, size, checksum and whether the file matches `YOLO_MODEL_PATH`.
Detection runs still use `model_id="yolo-configured"` for the configured YOLO
execution path. A selected `model_asset_id` can be supplied to use one specific
cataloged file for that run. The backend resolves the ID to a local path and
persists the selected asset metadata in Job/AnalysisRun parameters. GeoIntel
does not download weights or accept arbitrary model paths from the browser.
Operational runtime validation can be run against Docker/Tower with:
```bash
bash scripts/verify_model_asset_detection_workflow.sh http://192.168.10.150:1202
```
The smoke seeds the explicit offline demo raster, creates a tile manifest,
selects a local model asset, checks read-only preflight, runs the existing
configured-YOLO detection endpoint and verifies persisted AnalysisRun,
Detection list and Detection GeoJSON outputs. It intentionally does not inject
detector fixtures or download weights. A zero detection count is acceptable on
the synthetic demo raster; production usefulness still requires validation on
real georeferenced orthophotos and reference vectors.
### Map-driven building analysis
The primary map can hand an explicit EPSG:4326 rectangle to the bounded
orthophoto acquisition endpoint. Its canonical raster Dataset then uses the
unchanged configured-YOLO pipeline: 512 px tiles with 64 px overlap, preflight,
local inference, Job + AnalysisRun + Detection persistence and persisted
GeoJSON. When a ready GRB buildings reference Dataset exists, the same action
launches existing detection QA and persists QualityCheck and Metric rows.
This flow does not download a model, bypass the model registry, write directly
to Detection/vector tables or present AI boxes as official building truth.
The 1 m request sampling is an operational model profile; provenance retains
the official orthophoto source and latest-mosaic limitation.
### Real-data detection and QA validation
The real operational validation path uses operator-provided files rather than
demo fixtures:
```bash
REAL_RASTER_PATH=/mnt/user/appdata/geointel/data/orthophoto.tif \
REAL_REFERENCE_VECTOR_PATH=/mnt/user/appdata/geointel/data/reference-buildings.geojson \
bash scripts/verify_real_data_detection_qa_workflow.sh http://192.168.10.150:1202
```
The script verifies the full persisted chain:
- source raster upload with CRS and bounds metadata;
- reference building vector upload as `dataset_role=reference`;
- raster inspect and tile manifest generation;
- local model asset selection and read-only YOLO preflight;
- configured-YOLO detection run through Job, AnalysisRun and Detection rows;
- detection GeoJSON generated from persisted geometry;
- detection QA against persisted reference `vector_features` with persisted
`QualityCheck` and `Metric` rows;
- detection run GeoJSON export.
It refuses to run without a real GeoTIFF-style raster and GeoJSON/JSON reference
vector. It does not seed demo data, use `fixture_mode`, fetch live providers or
download model weights. A zero detection count is valid as runtime evidence only
when the selected model genuinely returns no usable detections after canonical
class filtering; it does not prove the model is useful for the target imagery.
Documented operator samples can be prepared inside the all-in-one runtime
container:
```bash
docker exec -it geointel python3 /app/scripts/prepare_operator_real_data_samples.py
```
The helper fetches explicit Digitaal Vlaanderen orthophoto/GRB GBG sample pairs
for the documented AOIs only and writes `operator_samples_manifest.json`. The
default corpus includes dense reference AOIs for Geel, Mol, Turnhout, Herentals,
Balen, Retie and Westerlo plus explicitly marked background candidates for
Postel-bos, Lommel-heide, Kasterlee-bos, Dessel-heide, Ravels-bos,
Meerhout-bos, Geel-Bel, Arendonk-heide and Herenthout-bos. Background
candidates may persist empty GRB FeatureCollections for negative-tile training;
normal reference AOIs still fail on empty GRB responses. Dense GRB references
are fetched through OGC API `rel=next` pagination links instead of trusting only
the first 1000-feature page. Generated reference GeoJSON records
`reference_pages_fetched`, `reference_truncated`, `reference_page_limit`,
`reference_max_features` and `source_urls` for auditability. The application
itself still does not perform live provider fetching.
### Mol operational validation pack
The operator registry includes a Mol-first validation pack: Mol center,
Achterbos residential, Gompel mixed settlement, Donk canal/industrial and
Postel rural village. The four new zones are marked as validation holdouts so
future training exports cannot silently consume the operational benchmark.
Postel-bos is evaluated separately as a background control.
`run_mol_operational_validation.sh` composes the existing positive multi-sample
quality matrix and background detection matrix. Positive runs use persisted GRB
`vector_features` and create real `QualityCheck`/`Metric` rows; background runs
only report persisted detection pressure and never synthesize QA metrics. AOI
bounds from the operator manifest are persisted as EPSG:4326 `Area` records so
every generated project opens as a complete map context.
In the all-in-one runtime combined JSON/Markdown evidence defaults to
`/app/storage/operator-evidence/mol-operational-validation`, which is part of
the persistent storage mount rather than the replaceable container layer.
For confidence-threshold calibration, use the sweep wrapper:
```bash
REAL_RASTER_PATH=/mnt/user/appdata/geointel/storage/operator-data/geel_orthophoto_wms_512.tif \
REAL_REFERENCE_VECTOR_PATH=/mnt/user/appdata/geointel/storage/operator-data/geel_grb_gbg_buildings.geojson \
CALIBRATION_THRESHOLDS="0.50 0.35 0.25 0.15" \
bash scripts/run_detection_calibration_sweep.sh http://192.168.10.150:1202
```
The sweep runs the real-data workflow once per threshold and then reads the
persisted project quality-check list to build `calibration_summary.json`.
Results are honest QA/QC evidence from persisted detections and persisted
reference `vector_features`; no demo detections, live provider fetches or model
downloads are introduced by the calibration tool.
Summaries include raw detection candidate count, persisted detection count and
suppressed duplicate count so operators can distinguish model output volume from
GeoIntel post-processing.
For model/tile/threshold selection, use the quality matrix wrapper:
```bash
REAL_RASTER_PATH=/mnt/user/appdata/geointel/storage/operator-data/geel_orthophoto_wms_512.tif \
REAL_REFERENCE_VECTOR_PATH=/mnt/user/appdata/geointel/storage/operator-data/geel_grb_gbg_buildings.geojson \
QUALITY_MODEL_ASSET_IDS="yolov8n-building-segmentation-pt yolov8n-pt" \
QUALITY_TILE_SIZES="512 640" \
QUALITY_TILE_OVERLAPS="64" \
QUALITY_THRESHOLDS="0.50 0.15" \
bash scripts/run_detection_quality_matrix.sh http://192.168.10.150:1202
```
The matrix repeats the same persisted real-data workflow for every combination
and writes `quality_matrix_summary.json` with detection count, QA score,
precision, recall, F1, mean IoU and false-positive/false-negative counts. The
rankings `best_by_score`, `best_by_recall` and `best_by_precision` are operator
decision aids only; GeoIntel still does not download models, seed fixture
detections or treat AI detections as ground truth without QA/QC. The same
candidate should also pass the background false-positive matrix before it is
considered as a default:
```bash
OPERATOR_SAMPLE_MANIFEST_PATH=storage/operator-data/operator_samples_manifest.json \
OPERATOR_BACKGROUND_SAMPLE_SLUGS="postel_bos lommel_heide kasterlee_bos" \
QUALITY_MODEL_ASSET_IDS="geointel-building-yolov8s-aoi1024bg512r3e50-pt" \
QUALITY_TILE_SIZES="512" \
QUALITY_TILE_OVERLAPS="64" \
QUALITY_THRESHOLDS="0.35 0.15" \
BACKGROUND_SPLIT_OUTPUT_DIR=artifacts/detection-hard-negatives/aoi1024bg512r3e50-split \
bash scripts/run_background_corpus_split_matrix.sh http://192.168.10.150:1202
```
The split runner writes `background_corpus_split_summary.json` and Markdown
handoff output with a strict `pure_empty_negative` gate and a separate
review-only `sparse_building_context` block.
Use that split summary directly in the model promotion report:
```bash
python scripts/build_detection_model_promotion_report.py \
--positive-portfolio artifacts/detection-quality-matrix/multi-sample/aoi1024bg512r3e50-positive/multi_sample_quality_summary.json \
--background-split-summary artifacts/detection-hard-negatives/aoi1024bg512r3e50-split/background_corpus_split_summary.json \
--output-dir artifacts/detection-model-promotion/aoi1024bg512r3e50-split-aware \
--min-positive-samples 7 \
--min-background-samples 2 \
--min-mean-f1 0.25 \
--max-background-detections-per-sample 0
```
The promotion report follows the split contract: `pure_empty_negative` is the
only strict background gate for default promotion, while
`sparse_building_context` remains review-only evidence in the report. This keeps
contextual buildings from being treated as empty-background false positives.
For the Tower/runtime pass, run the split matrix and promotion report together:
```bash
PROMOTION_POSITIVE_PORTFOLIO_PATH=artifacts/detection-quality-matrix/multi-sample/aoi1024bg512r3e50-positive/multi_sample_quality_summary.json \
OPERATOR_SAMPLE_MANIFEST_PATH=storage/operator-data/operator_samples_manifest.json \
QUALITY_MODEL_ASSET_IDS="geointel-building-yolov8s-aoi1024bg512r3e50-pt" \
QUALITY_TILE_SIZES="512" \
QUALITY_TILE_OVERLAPS="64" \
QUALITY_THRESHOLDS="0.35 0.15" \
BACKGROUND_SPLIT_OUTPUT_DIR=artifacts/detection-hard-negatives/aoi1024bg512r3e50-split \
PROMOTION_OUTPUT_DIR=artifacts/detection-model-promotion/aoi1024bg512r3e50-split-aware \
bash scripts/run_split_background_promotion_workflow.sh http://192.168.10.150:1202
```
The wrapper keeps the same safety boundary: existing dataset upload, configured
YOLO detection and report tooling only. It does not change model configuration
or bypass the persisted QA/QC evidence requirement.
For a quick post-redeploy check before the long matrix starts, use
`--preflight-only` with the same positive portfolio and operator manifest. This
checks local paths, required background categories and the runtime API envelope
without running inference:
Legacy operator manifests that do not yet contain explicit `background_category`
remain supported: the preflight derives `pure_empty_negative` from
`reference_feature_count == 0` and `sparse_building_context` from background
samples with persisted reference features, matching the matrix runner.
```bash
PROMOTION_POSITIVE_PORTFOLIO_PATH=artifacts/detection-quality-matrix/multi-sample/aoi1024bg512r3e50-positive/multi_sample_quality_summary.json \
OPERATOR_SAMPLE_MANIFEST_PATH=storage/operator-data/operator_samples_manifest.json \
bash scripts/run_split_background_promotion_workflow.sh --preflight-only http://192.168.10.150:1202
```
The underlying single-category matrix remains available:
```bash
OPERATOR_SAMPLE_MANIFEST_PATH=storage/operator-data/operator_samples_manifest.json \
OPERATOR_BACKGROUND_CATEGORIES="pure_empty_negative" \
OPERATOR_BACKGROUND_SAMPLE_SLUGS="postel_bos lommel_heide kasterlee_bos" \
QUALITY_MODEL_ASSET_IDS="geointel-building-yolov8n-expanded160e50-pt geointel-building-yolov8n-tile30-pt yolov8s-building-segmentation-pt" \
QUALITY_TILE_SIZES="640" \
QUALITY_TILE_OVERLAPS="64" \
QUALITY_THRESHOLDS="0.25 0.15 0.05" \
bash scripts/run_operator_hard_negative_detection_matrix.sh http://192.168.10.150:1202
```
The hard-negative matrix uploads only background rasters and counts detections
as false-positive pressure. It does not run QA/QC or invent reference metrics
for empty/sparse background AOIs. Operator manifests classify background
samples as `pure_empty_negative` when GRB returns zero reference buildings and
`sparse_building_context` when contextual buildings are present. Use
`OPERATOR_BACKGROUND_CATEGORIES="pure_empty_negative"` for default-promotion
hard-negative gates, then run `sparse_building_context` as a separate review
matrix. The first expanded local model improved dense AOI F1, but Kasterlee-bos
false positives block default promotion.
The focused small-building local model asset,
`geointel-building-yolov8s-smallbld-minpx3-img640-ft30-pt`, is the current
recommended Detection Lab operator profile. Use tile size `512`, overlap `64`
and confidence threshold `0.15`. Its original promotion evidence at match IoU
`0.25` across seven positive AOIs measured mean precision `0.5898`, recall
`0.5770` and F1 `0.5825`; minimum per-AOI F1 was `0.5528`. The strict
three-sample pure-empty
background gate produced zero detections. Compared with the previous balanced
profile, the same persisted reference populations contain 1,571 fewer false
negatives, including 745 fewer misses in the 25-100 m2 bucket and 181 fewer
below 25 m2. This recall gain increases the false-positive review load, so the
previous `geointel-building-yolov8s-aoi1024expandedminpx4vis035e50-pt` profile
remains available as a higher-precision legacy `0.15` choice. The older
`geointel-building-yolov8s-aoi1024bg512r3e50-pt` remains the conservative
`0.35` profile. Sparse-context detections remain review-only evidence, not a
default-promotion blocker. Every production-like run still requires persisted
QA/QC against suitable reference data.
The map-driven building workflow uses canonical footprint IoU `0.25`, matching
the promotion evidence above. A July 2026 Mol-only holdout audit compared
confidence `0.10` and `0.15` over Achterbos, Gompel, Donk and Postel. Confidence
`0.15` produced the better F1 in all four positive zones; both thresholds
produced zero detections in the pure-empty Postel forest control. The active
confidence therefore remains `0.15`. This result does not claim production
perfection and does not justify another model-training run by itself.
A coverage-aligned July 2026 rerun supersedes the older displayed profile
averages above without changing the active model or threshold. On the exact
current pipeline, the seven independent Mol/Kempen zones measured mean
precision `0.6141`, recall `0.6062` and F1 `0.6069`; the minimum zone F1 was
`0.4749` in Mol Postel. The active model again produced zero detections in
Postel-bos, Lommel-heide and Arendonk-heide. These are the values shown in the
Detection Lab operator profile.
The reviewed-accuracy experiment added six training-only AOIs from Arendonk,
Dessel, Meerhout, Laakdal, Nijlen and Hulshout. The paged GRB export contained
9,964 complete reference features. Its audited `512`-tile corpus retained 252
tiles and 79,192 labels with no invalid or missing labels. The inactive
`geointel-building-yolov8s-reviewedexp6-minpx3-img640-ft20-pt` challenger
improved mean seven-zone F1 to `0.6248`, but produced two detections in the
explicitly empty Postel-bos control. The formal fail-closed promotion report
therefore retained the current active model. Positive-score gains never
override a failed pure-empty background gate.
False-positive and false-negative evidence from persisted detection QA can be
classified through `detection_reviews`. The queue derives from quality-check
evidence ids and resolves persisted Detection and reference VectorFeature rows.
`qa_alignment_mismatch`, `reference_gap_or_change`, uncertain imagery and
unreviewed items must never be exported as hard-negative or missed-positive
training labels. Canonical QA metrics remain unchanged after review.
The persisted seven-AOI evidence for this profile contains 5,568 false
positives among 13,613 candidate detections. The read-only audit command in
`scripts/README.md` reports the largest review volumes in Turnhout, Herentals
and Geel, a median false-positive geometry area of about 184.5 m2, and 25.8%
tiny/small geometry below 100 m2. Current evidence does not include
per-detection confidence, so model-review reports must retain confidence
coverage as zero rather than treating threshold `0.15` as an observed score.
Combined false-positive GeoJSON is evidence for operator review only; a feature
must be visually confirmed before it is used as a hard-negative label.
To update a Tower/Unraid `.env` from a promoted report, use the guarded
activation helper. It validates the exact report candidate key, verifies that
the candidate has `promotion_status=promote_candidate`, resolves the local model
asset under the mounted models directory, and writes environment updates only
when `--apply` is supplied:
```bash
python scripts/activate_promoted_yolo_candidate.py \
--promotion-report storage/operator-data/model-review/small-building-candidate/promotion/detection_model_promotion_report.json \
--candidate-key 'geointel-building-yolov8s-smallbld-minpx3-img640-ft30-pt|512|64|0.15' \
--models-dir /mnt/user/appdata/geointel/models \
--env-file /mnt/user/appdata/geointel/.env \
--json
```
Re-run with `--apply` only after reviewing the emitted env updates. The helper
does not download weights, load a model or run inference. Restart or rebuild the
runtime after applying because `YOLO_MODEL_PATH` is read from environment
configuration.
To compare the same model/tile/threshold grid across all prepared operator
samples, use:
```bash
OPERATOR_SAMPLE_MANIFEST_PATH=storage/operator-data/operator_samples_manifest.json \
QUALITY_MODEL_ASSET_IDS="yolov8n-building-segmentation-pt yolov8n-pt" \
QUALITY_TILE_SIZES="512 640" \
QUALITY_TILE_OVERLAPS="64" \
QUALITY_THRESHOLDS="0.50 0.15" \
bash scripts/run_multi_sample_detection_quality_matrix.sh http://192.168.10.150:1202
```
The multi-sample summary exposes `best_overall_by_score`,
`best_overall_by_recall`, `best_overall_by_precision` and `best_by_sample` so
model-quality decisions are based on repeated persisted QA/QC evidence rather
than one AOI.
When repeated public model benchmarks remain too weak, the operator can convert
the prepared real-data samples into a local YOLO training dataset:
```bash
docker exec -it geointel python3 /app/scripts/export_operator_yolo_dataset.py \
--manifest-path /app/storage/operator-data/operator_samples_manifest.json \
--output-dir /app/storage/operator-data/yolo-building-dataset \
--val-samples turnhout \
--force
```
The exporter creates a standard YOLO detection layout with `dataset.yaml`,
`images/train`, `labels/train`, `images/val` and `labels/val`. It converts GRB
building reference geometries to pixel-space bounding boxes for the matching
orthophoto sample and records `yolo_dataset_summary.json`.
A minimal local training smoke can then be run explicitly in an AI-enabled
runtime:
```bash
docker exec \
-e OPERATOR_YOLO_DATASET_DIR=/app/storage/operator-data/yolo-building-dataset \
-e YOLO_BASE_MODEL_PATH=/app/models/yolov8n.pt \
-e TRAIN_MODEL_OUTPUT_PATH=/app/models/geointel-building-detector.pt \
-e TRAIN_EPOCHS=8 \
-e TRAIN_IMGSZ=512 \
-e TRAIN_BATCH=2 \
-e TRAIN_WORKERS=0 \
-e TRAIN_DEVICE=cpu \
-e PYTHON_BIN=python3 \
geointel bash /app/scripts/train_operator_yolo_detector.sh
```
This remains operator tooling only. GeoIntel does not expose Training Studio in
V1, does not generate labels from predictions and does not treat the trained
artifact as useful until it passes the same real-data Detection + QA matrix.
If the whole-image dataset underfits or produces unusable detections, export
overlapping tile-level samples:
```bash
docker exec -it geointel python3 /app/scripts/export_operator_yolo_tile_dataset.py \
--manifest-path /app/storage/operator-data/operator_samples_manifest.json \
--output-dir /app/storage/operator-data/yolo-building-tile-dataset \
--tile-size 192 \
--stride 96 \
--negative-keep-ratio 0.5 \
--val-samples turnhout \
--force
```
The tile exporter clips reference building boxes into tile-local YOLO labels
and records the positive/negative tile counts. This gives the training smoke
more image samples while preserving the same explicit operator-data and QA/QC
validation boundary.
Focused small-building experiments use Beerse, Rijkevorsel, Hoogstraten and
Vorselaar as training AOIs, with Vosselaar and Grobbendonk retained as
independent validation AOIs. The exporter accepts an explicit `--samples`
subset and records `source_manifest_sample_count`, `selected_sample_slugs` and
`excluded_sample_slugs` in its summary. Manifest-backed validation samples
cannot silently enter training.
For visual error inspection, export the persisted QA evidence from a calibration
summary:
```bash
CALIBRATION_SUMMARY_PATH=/mnt/user/appdata/geointel/artifacts/detection-calibration/20260707T002103Z/calibration_summary.json \
bash scripts/export_detection_calibration_evidence.sh http://192.168.10.150:1202
```
The evidence bundle calls
`/api/v1/projects/{project_id}/quality-checks/{quality_check_id}/evidence/geojson`
for the persisted quality checks and writes combined GeoJSON plus an HTML/SVG
review artifact. This is an inspection aid only; it does not rerun inference or
alter stored detections.
### Sprint 8C detection visualization and QA status
Sprint 8C makes persisted detections reviewable:
- Detection runs can be listed and selected.
- Persisted detections can be listed and filtered by run, dataset, class and minimum confidence.
- Persisted detection geometries can be returned as GeoJSON FeatureCollections for MapLibre display.
- Detection QA compares candidate detection geometries against persisted reference `vector_features`.
- QA results reuse `quality_checks` and `metrics`; no parallel QA persistence system is introduced.
- Configured-YOLO QA derives its evaluation extent from the persisted tile
manifest. Tile bounds are transformed from their explicit source CRS to
EPSG:4326 and unioned. The union is first applied as a GiST-backed PostGIS
spatial predicate, then used to clip the bounded candidate/reference
populations before canonical footprint-IoU matching. Complete source counts
remain in QA evidence, but regional geometries outside inference coverage are
not materialized in application memory and do not count as false negatives.
- Canonical one-to-one IoU matching uses an in-memory spatial index only to
discard geometries whose envelopes cannot intersect. It does not change the
configured IoU threshold, greedy match ownership or persisted metrics.
- A separate reference-envelope IoU pass is persisted as
`box_to_footprint_diagnostics`. It quantifies possible matching artifacts from
comparing rectangular detections with irregular building footprints, but is
diagnostic only and never changes canonical QA metrics.
- Segmentation remains out of scope for Sprint 8C.
## 2. Tile Metadata
Elke tile moet opslaan:
- tile path
- parent raster id
- pixel window
- geospatial bounds
- transform
- CRS, and the manifest must also carry source CRS metadata
- tile size
- overlap
Zonder tile metadata kunnen modeloutputs niet correct teruggeprojecteerd worden.
## 3. Detection Output Contract
Elke detectie bevat:
- class_name
- confidence
- bbox pixel coords
- source tile
- geospatial polygon
- model id/version
- analysis run id
## 4. Segmentation Pipeline
```text
Raster dataset
Clip/tile
Run segmentation model
Generate mask
Georeference mask
Polygonize mask
Simplify/clean geometries
Store polygons + mask path
Expose as map layer
```
### Sprint 9 segmentation foundation status
Sprint 9 implements the segmentation persistence and review boundary only:
- `segmentations` are first-class PostGIS records linked to project, dataset, job and analysis run.
- PostGIS MultiPolygon geometry in EPSG:4326 is authoritative for map display, QA and GeoJSON output.
- Mask paths are persisted as artifact/provenance references, not authoritative feature state.
- `segmentation-placeholder`, `yolo-seg-configured` and `sam-configured` report `not_configured`.
- `fixture-segmenter` is test/demo-only and persists segmentations only when `fixture_mode=true` and fixture segmentations are explicitly supplied.
- Segmentation QA compares persisted segmentation geometries against persisted reference `vector_features`.
- QA results reuse `quality_checks` and `metrics`; no parallel QA system is introduced.
- GeoIntel does not install SAM, run YOLO-seg, download model weights or fake production segmentations in Sprint 9.
## 5. Change Detection Pipeline
Fase 1: vector/detection based.
```text
Run A detections
+
Run B detections
Spatial matching
added / removed / changed
Change polygons
Metrics
```
Fase 2: raster index based.
```text
Raster A index
+
Raster B index
Difference raster
Threshold
Polygonize changed zones
```
Fase 3: segmentation based.
```text
Mask A
+
Mask B
Class difference
Change polygons
```
## 6. Model Strategy
V1:
- gebruik een bestaande YOLO-integratie met configureerbaar modelpad
- demo-model mag lokaal worden geplaatst in `models/`
- code moet ook zonder model kunnen starten, maar detection job moet dan duidelijke fout geven
V2:
- SAM/YOLO segmentation
V3:
- annotation export
- finetuning
## 7. Reproduceerbaarheid
Elke analysis run moet bewaren:
- model id
- model version
- parameters
- confidence threshold
- tile size
- overlap
- input dataset id
- code path/version indien mogelijk