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:
```bash
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`
```bash
# 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):
```bash
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`
```bash
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.
```bash
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
```bash
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:
```bash
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).**