Files
geointel/docs/AI_PIPELINES.md
T
Jens 2cdf9c99c6
Managed validation / Managed repository validation (pull_request) Successful in 1m46s
GeoIntel release gates / Compile, test, contracts and builds (pull_request) Successful in 1m51s
GeoIntel release gates / Python and npm vulnerability policy (pull_request) Successful in 20s
GeoIntel release gates / Production AI image, SBOM and container scan (pull_request) Successful in 15m3s
GeoIntel release gates / Deploy exact gated revision to Unraid (pull_request) Skipped
Prepare GeoIntel for public release
2026-08-31 21:33:10 +02:00

35 KiB

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

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:

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:

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:

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:

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 the controlled model-review evidence outside Git; 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 scripts/verify_model_asset_detection_workflow.sh http://192.0.2.10: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:

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.0.2.10: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:

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:

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.0.2.10: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:

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.0.2.10: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:

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.0.2.10: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:

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:

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.0.2.10: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.

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.0.2.10:1202

The underlying single-category matrix remains available:

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.0.2.10: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:

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:

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.0.2.10: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:

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:

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:

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:

CALIBRATION_SUMMARY_PATH=/mnt/user/appdata/geointel/artifacts/detection-calibration/20260707T002103Z/calibration_summary.json \
bash scripts/export_detection_calibration_evidence.sh http://192.0.2.10: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

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.

Run A detections
+
Run B detections
↓
Spatial matching
↓
added / removed / changed
↓
Change polygons
↓
Metrics

Fase 2: raster index based.

Raster A index
+
Raster B index
↓
Difference raster
↓
Threshold
↓
Polygonize changed zones

Fase 3: segmentation based.

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