satdetect-dev / docs /ACCURACY_IMPROVE_STATUS.md
coderuday21's picture
Cursor
Deploy satdetect-dev with Priyanka audit/exception logs.
d70361b
|
Raw
History Blame Contribute Delete
14.8 kB

Accuracy improvement pack (resumed 2026-07-21 after PC crash)

Pull from Priyanka

Merged github-dev/New/Priyanka β†’ uday (5462489).

Task 1 β€” GT for Grid_54 vs H43X2E1 β€” DONE (curated)

Hand-reviewed fullres detection regions; kept structural changes only (new blue/grey roofs, compound rebuild, solar). Dropped shadows, pool water color, wetland/vegetation.

  • Label: docs/delhi_eval/labels/dda_grid54_h43x2e1.png (changeβ‰ˆ0.23%, 10 components)
  • Pack: docs/delhi_eval/dda_labeling/dda_grid54_h43x2e1/gt_mask.png
  • Review notes/overview: .../_review/gt_labeling_notes.json, gt_overview.png

Re-paint and re-ingest anytime:

python scripts/ingest_dda_gt_label.py

Task 2 β€” Threshold / config recalibration β€” DONE

v3_frozen: keep thr=0.2 (runs/calibration/v3_recalibration_recommendation.json)

App grid (dl_only): sens 0.4 slightly best; DL floors irrelevant under dl_only (runs/calibration/grid_search_v3_dl_only/summary.json).

Hard-neg thr sweep: still best at 0.2 (val F1 0.688, test F1 0.563) β†’ runs/v3_hardneg_analysis/recommended_threshold.json

Task 3 β€” Hard negatives + fine-tune β€” DONE

  • Mined 7 veg/seasonal FP tiles β†’ data/delhi_cd/hard_negatives/
  • Fixed keep-empty so empty GT tiles actually train
  • Run: runs/finetune_hardneg/20260721_112416
    • Best val F1=0.6751 @ thr=0.2 (epoch 4)
    • Test F1=0.548–0.563 (vs v3_frozen test β‰ˆ0.595)
    • Hard-neg pairs still 100% FP in analysis β€” do not promote yet
  • Exported: models/adaptformer_delhi/v3_hardneg/
  • Production default stays v3_frozen

Task 4 β€” Fullres DDA detection (cap 5120) β€” DONE

runs/native_dda/20260721_121410/cap5120/

  • 4706Γ—5120, ~19.6 min, change%β‰ˆ1.21, 23 regions
  • Artifacts: change_mask.png, overlay.png, summary.json
  • 899 tiles, fusion=dl_only, thr=0.2, model=adaptformer-delhi-v3
# true native overnight if needed:
python scripts/run_native_dda_detection.py --native

Missed-detection accuracy pass (2026-07-21 evening) β€” APPLIED

Root causes of under-detection on Grid_54 vs H43X2E1:

  1. strip_transient_from_mask over-deleted mid-size roofs (weak-perm used max_car*2).
  2. Blue-roof recovery rejected novel roofs merged with existing blue courts.
  3. Dark roofs / solar (bright→black tarp) had no recovery; shadow strip could erase them.
  4. AdaptFormer often fires only on fragments β€” mild score hysteresis + open on low mask.

Applied in app/detection_engine.py:

  • Protect blue/dark-roof + structural large footprints in transient strip
  • Chromatic recovery keeps novel (non-blue-before) pixels; lower min area
  • recover_dark_roof_construction (local grow + strict standalone)
  • Protect dark-new pixels in shadow strip
  • DL-only mild hysteresis (low β‰ˆ 0.65Γ—thr, low-mask opened)
  • Strip helper for large seasonal veg (when NDVI evidence is clear)

Re-applied to run #47 (working 4706Γ—5120 PNGs):

python scripts/reapply_run47_accuracy.py
  • change% β‰ˆ 4.23, 60 regions (was ~1.21 / 10)
  • GT recall vs overlay β‰ˆ 0.96 (was ~0.69); 0 missed GT components
  • Artifacts: runs/accuracy_improve_20260721/

Note: curated GT #7 is basketball-court activity (people/paint), not structural β€” still detected under the denser mask; structural policy may want to drop it from GT later.

Tuesday 2026-07-28 β€” day plan integration

Uday P0 β€” Real weights + held-out baseline

  • models/adaptformer_delhi/v3_frozen/model.safetensors present (~50 MB)
  • /health β†’ loadedFrom=.../v3_frozen, available=true, device=cuda
  • Held-out test split (data/delhi_cd/test, 4 pairs) @ thr=0.2, dl_only, TTA off:
    • mean F1=0.5867 Β· P=0.6028 Β· R=0.614 Β· IoU=0.4171
    • Artifact: runs/tuesday_baseline_20260728/metrics.json
    • Re-run: python scripts/record_tuesday_baseline.py

Integrated from Priyanka (same day)

  • pair_align guard wired in detect_service (identical β†’ message, low NCC β†’ warning)
  • Shadow classification: shadow-only blobs kept as Shadow; structural vs all %
  • Verify: python scripts/verify_tuesday.py β†’ ALL CHECKS PASSED

Wednesday 2026-07-29 β€” retrain + hard-negatives

Training fixes (--preset wed)

  • Drop empty real GT; keep mined hn_* hard-neg tiles
  • Loss: CE + pos_weight
  • Oversample change tiles Γ—4; full-image 256 resize; change-centered crops
  • Val-calibrate + freeze threshold
  • Warm-start: v3_frozen

Hard-negative mining

  • Refreshed scripts/mine_hard_negatives.py (parking / seasonal veg / shadow tags)
  • Saved 5 tiles (hn_delhi_0002/0006/0013/...), all tagged vegetation_seasonal+shadow
  • Appended to data/delhi_cd/train/manifest.json

Result (target test F1 > 0.60) β€” HIT

  • Run: runs/finetune_wed/20260729_134453
  • best val F1=0.630 Β· frozen thr=0.446
  • test F1=0.6051 Β· P=0.526 Β· R=0.772 Β· IoU=0.436
  • Export: models/adaptformer_delhi/wed_retrain/ (does not replace production v3_frozen)
  • Summary: data/delhi_cd/wednesday_retrain/metrics.json
python scripts/build_delhi_cd_splits.py --min-change-frac 0.001 --stratify
python scripts/mine_hard_negatives.py --include-all-labeled --min-fp-frac 0.03
python scripts/finetune_adaptformer.py --delhi-cd data/delhi_cd --preset wed --out runs/finetune_wed

Thursday 2026-07-30 β€” DSIFN spike + wed_retrain OP freeze

Priyanka

Fast-forward merged github-dev/New/Priyanka β†’ uday (ROI crop / training packs).

DSIFN vs AdaptFormer (boundary completeness)

  • Cloned official DSIFN + Change-Detection-Review index under third_party/
  • Official Drive package = dataset only (no .pth/.h5); trained proxy on DSIFN val β†’ test F1β‰ˆ0.485
  • Delhi 5 pairs (4 test + 1 val): AdaptFormer fills interiors; DSIFN does not
Model F1 Fill Hole
AdaptFormer (wed_retrain) 0.624 0.832 0.148
DSIFN proxy 0.090 0.356 0.644

Decision: KEEP_ADAPTFORMER β€” do not switch backbones.
Artifacts: data/delhi_cd/thursday_dsifn_compare/DECISION.md

wed_retrain operating point

Sweep F_Ξ² thr + DETECTION_TTA + DETECTION_MULTISCALE on held-out test:

Config Test F1 Notes
thr=0.446, TTA=off, MS=off 0.606 Wed sidecar baseline
thr=0.354 (F_Ξ²), TTA/MS variants ≀0.601 Drop (hurt F1)
thr=0.446, TTA=full, MS=off 0.626 KEEP (+0.020 F1, ~3Γ— flips)
thr=0.446, MS=0.75,1 0.587 Drop

Frozen OP (models/adaptformer_delhi/wed_retrain/):

  • threshold=0.446
  • DETECTION_TTA=full
  • DETECTION_MULTISCALE=off
  • Production remains v3_frozen until an explicit promote.
python scripts/sweep_wed_operating_point.py
python scripts/dsifn_proxy_train.py --epochs 6 --batch 2
python scripts/compare_dsifn_vs_adaptformer.py

Friday 2026-07-31 β€” integrate + regression (Priyanka, Task 1)

Labeling round (drone pairs, before splits)

Hand/assisted-labeled 9 drone before/after pairs (georeferenced but un-orthorectified, NCC 0.07–0.61 β€” well below the ~0.9 needed for clean pixel correspondence). Registration attempts (SIFT homography, ECC affine/euclidean/homography on the static region) could not raise NCC further β€” confirmed local parallax, not a fixable global misalignment; orthomosaic reprocessing from raw photos is the only real fix. See each pack's meta.json (orthomosaics_needed: true) for the caveat.

By explicit user directive (2026-07-31), these 9 are now flagged training_use: true (training_use_override recorded in each meta.json) and registered in docs/delhi_eval/manifest.json with pack-local before.png/after.png (not the original Downloads TIFFs, so the split is portable to Uday's machine):

pair_id change_frac split
dda_1_2_scene 0.0079 train
dda_after_testing 0.1604 train
dda_before1_after 0.2124 train
dda_before1_after2_testing 0.2342 test
dda_before2_after2 0.3396 val
dda_before3_after3 0.0332 val
dda_before4_after4 0.1932 train
dda_before5_after5 0.2232 train
dda_before6_after6 0.0471 train

An earlier aligned-source labeling attempt (2019β†’2026 satellite pair, 1 positive + 3 seasonal hard-negatives, dda_before2019_r0..r3) was created, then removed by user request before ingest β€” not present in the splits.

Splits rebuilt

python scripts/build_delhi_cd_splits.py --min-change-frac 0.001 --stratify

33 labeled pairs β†’ train=23 Β· val=5 Β· test=5 (was 24/16Β·4Β·4 before today).

Regression check

  • python scripts/validate_detection.py (unit checks) β€” all pass, no change from HEAD.
  • python scripts/validate_detection.py --benchmark --method "AI-Based Deep Learning" β€” synthetic-scene F1 is very low (0.00–0.06) vs the old runs/calibration/best_params.json numbers (0.86–1.0). Not a regression from this week's commits β€” confirmed via git diff that feature_based_method/fusion logic are byte-identical to before Tuesday. Root cause: those old numbers were measured under smart_union fusion (classical+DL) on a different model; current default is dl_only (app/detection_config.py:200 docstring: smart_union "collapsed mean F1 from ~0.58 to ~0.04" on the v3 Delhi model β€” a deliberate, documented tradeoff). The synthetic suite's expected baseline is stale for dl_only and should be recalibrated separately; it is not something Tue–Fri changes broke.
  • End-to-end wiring smoke (ROI crop β†’ detect β†’ export training pack β†’ ingest) β€” all 4 steps pass, artifacts cleaned up.

Shadow classification β€” removed (user decision)

Reverted Tuesday's Shadow DDA type / region classifier (change_type_map.py, detection_engine.py, detect_service.py). Shadow-only pixels are still suppressed (strip_shadow_only_from_mask, unchanged) β€” just no longer surfaced as a labeled class or split stat.

ROI β†’ training-pack export β€” shipped

POST /api/dda/training/pack (app/dda/training_pack.py, app/dda/training_routes.py) + scripts/ingest_dda_gt_label.py --pair-id generalization + "Export training pack" button in the result view (static/js/dda/result.js). Verified end-to-end (see regression check above).

🚩 Found + fixed: test-set drift risk (before handing off to Uday)

Rebuilding the splits (needed to fold in Friday's labels) reshuffled which pairs land in test even at the same seed β€” Wednesday/Thursday's frozen wed_retrain operating point (thr=0.446, TTA=full, test F1=0.6263) and Tuesday's v3_frozen baseline (F1=0.5867) were both measured on {delhi_0024, delhi_0001, delhi_0005, delhi_0016}. The first Friday rebuild produced a different test set ({delhi_0024, delhi_0011, delhi_0017, delhi_0022, dda_before1_after2_testing}, only 1 pair overlapping) β€” any F1 number from that set would not be comparable to Tue/Wed/Thu's, and a drop could look like "the model got worse" when it's really "the benchmark changed."

Fix implemented: scripts/build_delhi_cd_splits.py now supports --freeze-test-ids (default: data/delhi_cd/frozen_test_ids.json), which pins specific pair_ids to test on every run regardless of what's added/removed from the labeled pool. Created data/delhi_cd/frozen_test_ids.json = the original Tue/Wed/Thu set. Rebuilt splits β€” test is now exactly {delhi_0024, delhi_0001, delhi_0005, delhi_0016} again, so today's held-out eval is directly comparable to the whole week's numbers. All 9 drone pairs land in train (8) / val (1) β€” none in test β€” so the benchmark stays 100% clean satellite imagery regardless of the drone-data alignment-quality question (see below).

Residual minor risk: dda_before2019 removal + drone-pair addition also shifted val composition (dda_1_2_scene, a low-NCC pair, is now in val). Val doesn't affect the headline test-F1 comparison but does feed the val-threshold sweep (Thursday's sweep_wed_operating_point.py pattern) β€” if Uday re-runs that sweep, note val is not fully frozen, only test.

Handoff to Uday (Friday P0)

  • Fine-tune on the rebuilt 24/5/4 split (train includes 8 of the 9 new drone pairs β€” low-NCC caveat applies; test is unchanged/frozen, clean satellite only).
  • Held-out eval on the frozen test set β†’ before/after F1/P/R vs Tuesday's v3_frozen baseline (now a valid, apples-to-apples comparison) β†’ promotion decision (v3_frozen vs wed_retrain vs new fine-tune).
  • Recommended extra check: since low-NCC drone pairs are now in training, additionally report F1 on the frozen test set split by training-data recipe (with vs without the 9 drone pairs, if time allows an ablation) to isolate whether they helped or hurt real (clean satellite) performance β€” directly answers "did the drone data degrade F1."

Result: fri2_retrain (delhi + drone) β€” confirmed regression, root cause found

Checkpoint Frozen test F1 thr Val F1 Val P Val R
v3_frozen 0.587 0.2 0.677 0.686 0.700
wed_retrain 0.605 (0.626 TTA) 0.446 0.630 - -
fri2_retrain 0.487 0.5 0.436 0.345 0.641

fri2_retrain's threshold.json follows the same schema as v3/wed (real per-epoch val calibration, not a default) - so the drop is not a threshold-calibration bug. Precision collapsed 0.69->0.35 while recall stayed reasonable (0.64): the model became far less selective, consistent with training on the 9 un-orthorectified drone pairs (dda_before*, dda_after*, dda_1_2_scene - NCC 0.07-0.61) - large misaligned regions labeled "change" taught the model "big pixel differences = change" too broadly. Confirmed visually on DDA_Report_55_before1_tif_vs_after_tif.pdf: 16.63% flagged, mostly cars/vegetation/ground, buildings still not covered.

Fix in progress: ablation split prepared and pushed - data/delhi_cd_ablation_no_drone/ (same 24-pair pool that produced wed_retrain's 0.605/0.626, drone pairs excluded via the new --exclude-prefix flag on build_delhi_cd_splits.py, frozen test set intact). Requested from Uday:

python scripts/finetune_adaptformer.py --delhi-cd data/delhi_cd_ablation_no_drone --preset wed --out runs/finetune_fri3_no_drone

If F1 recovers to ~0.60+, confirms drone data as the cause -> exclude drone pairs from training going forward (keep eval-only, per the original plan); promote whichever of wed_retrain / fri3_no_drone scores higher on the frozen test set. Status: awaiting Uday's run (sent EOD Friday).