Skip to content

feat: interactive labeler Advanced panel — 5-fold CV + pre-event swipe view#91

Open
calebrob6 wants to merge 6 commits into
mainfrom
feat/interactive-labeler-advanced-panel
Open

feat: interactive labeler Advanced panel — 5-fold CV + pre-event swipe view#91
calebrob6 wants to merge 6 commits into
mainfrom
feat/interactive-labeler-advanced-panel

Conversation

@calebrob6

Copy link
Copy Markdown
Member

Summary

Adds an expandable "Advanced" section to the Interactive Labeler right panel with two features.

1. Run 5-fold CV

Stratified k-fold cross-validation of the in-browser model over your current labels, reporting per-class precision, recall, and one-vs-rest AUC as mean ± stdev across folds, in a compact table (Intact / Damaged / Cloudy).

  • New pure, dependency-free crossValidateMetrics(entries, { k = 5 }) in interactiveModel.js:
    • Stratified fold assignment (every fold gets every class); effective folds clamped to the smallest class count (a 5-fold request on a class with 3 samples runs 3-fold and says so).
    • Per-class precision/recall from argmax predictions (skipping folds where the metric is undefined so one degenerate fold doesn't drag the mean to 0).
    • Per-class one-vs-rest AUC from the raw per-class sigmoid via a new rank-based binaryAUC helper (Mann–Whitney, average ranks for ties; null when a fold is single-class for that label).
    • Aggregates mean + sample (n-1) stdev; returns { ok:false, reason } for degenerate inputs (<2 classes / too few per class).
  • UI: "Run 5-fold CV" button (disabled with no labels), a spinner while the CPU crunch runs (yields to paint first), and the results table with a k-fold=<effK> caption.

2. Swipe (pre-event) toggle

A FluentUI toggle that overlays an atlas.SwipeMap whose secondary map reveals the layer's pre-event imagery, falling back to a satellite basemap when the layer has no pre-event imagery — mirroring the Visualizer's swipe pattern (Visualizer.jsx:140-155, 315-345) and reusing the existing loadImagery helper (which itself falls back to the Azure microsoft.imagery satellite tiles for an empty URL). The SwipeMap is lazily constructed on toggle-on and fully torn down on toggle-off (dispose swipe + secondary map, reset container) so the primary labeler map and its footprint interactions are untouched across repeated toggling.

Files

  • ui/src/Components/InteractiveLabeler/interactiveModel.js (+189): crossValidateMetrics, binaryAUC, meanStd/shuffle helpers.
  • ui/src/Components/InteractiveLabeler/InteractiveLabeler.jsx (+340/−4): Advanced expandable, CV button/table, swipe toggle + lifecycle, map-area overlay container.

Validation

  • CV math (independently verified with Node against the real module): AUC edge cases (perfect=1, reversed=0, all-ties=0.5, single-class=null); separable 3-class data → per-class AUC≈1.0 with 0 fold-stdev; overlapping clusters → non-zero stdev; degenerate inputs → {ok:false}; effK clamps to the smallest class (3 when a class has 3 members). Output pasted in the PR discussion.
  • Build: npm run build passes.
  • Lint: no new problems — the 5 eslint items on the file are pre-existing (verified identical on the pristine HEAD copy) and outside the added code.
  • Local docker: ui image rebuilt + recreated; page serves (/ → 302 auth redirect); the vite-transformed module and the azure-maps-swipe-map.min.js global both load without error.

Known limitation (please verify on a token-enabled deployment)

The visual swipe behavior (dragging the divider, the pre-event/satellite reveal, repeated on/off restore) could not be exercised in the local stack: the SWA emulator gates the app behind MSAL login and local docker uses the Azure Maps placeholder token, so atlas.microsoft.com basemap tiles don't render without a real subscription key. The swipe code mirrors the already-working Visualizer SwipeMap pattern and builds/lints clean, but the live interaction should be confirmed against a deployment with a real Azure Maps token (e.g. via the UI Validation Agent / Playwright). The CV feature is fully verified and has no such dependency.

Deploy note

UI-only change. Rebuild/redeploy the ui app. No backend/API/hastegeo changes.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

…ipe)

Add an expandable "Advanced" section to the Interactive Labeler right panel:

- Run 5-fold CV: stratified k-fold cross-validation of the in-browser model
  over the current labels, reporting per-class precision, recall, and one-vs-
  rest AUC as mean ± stdev across folds. Implemented as a pure, dependency-free
  crossValidateMetrics(entries, {k}) in interactiveModel.js with a rank-based
  binaryAUC helper (Mann-Whitney, average ranks for ties). Effective folds are
  clamped to the smallest class count; degenerate inputs return {ok:false}.

- Swipe (pre-event) toggle: overlays an atlas.SwipeMap whose secondary map
  reveals the layer's pre-event imagery, falling back to a satellite basemap
  when the layer has no pre-event imagery — mirroring the Visualizer's SwipeMap
  usage and reusing the loadImagery helper. Lazily constructed on toggle-on and
  torn down (dispose swipe + secondary map, reset container) on toggle-off.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@calebrob6 calebrob6 requested a review from a team as a code owner July 14, 2026 18:02
calebrob6 and others added 5 commits July 14, 2026 22:15
Two fixes to the Advanced swipe view:

- Panning: removed the redundant primary 'moveend' -> secondary setCamera
  handler. atlas.SwipeMap already synchronizes both cameras on every 'move';
  our extra handler double-updated them, making panning jump/stutter.

- Layout: the primary labeler map div was flexGrow/static while the swipe
  secondary was absolute, so the swipe clip/divider were sized against a
  mismatched box. Make BOTH map divs position:absolute inset:0 overlapping
  inside a relative wrapper — the exact layout the Visualizer's working swipe
  uses (visualizer.css .map) — so the secondary (pre-event, on top) clips
  cleanly to reveal the primary (post-event) underneath.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ty view

Swipe refinements:
- Put PRE imagery on the LEFT and POST on the RIGHT (make the labeler map the
  SwipeMap secondary and a new pre-event map the primary, since the module
  always reveals the secondary on the right).
- Add 'Pre imagery' / 'Post imagery' corner labels (top-left / top-right).
- Draw the building footprints on the pre pane too (same PMTiles source), so
  outlines span both panes (per-building label colors still render on the
  interactive/post pane only — feature-state mirroring is a follow-up).

Right-panel layout:
- Divider (Separator) between the CV and Swipe blocks in Advanced.
- Pin the keyboard-hotkeys hint to the bottom of the panel: the panel is now a
  flex column with a scrollable content region and a fixed footer.

Uncertainty view (Advanced):
- New toggle that recolors every scored footprint by the model's predictive
  uncertainty (normalized Shannon entropy of the OvR class probabilities),
  driven by a new 'unc' feature-state and a blue-to-red interpolate ramp.
  Computed for the viewport on each settle (reuses the trained model), with a
  legend shown while the view is on.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Legends on the map: add a bottom-right legend overlay (inside the map
  wrapper so it tracks the map, not the panel). Shows the class colors
  (Intact/Damaged/Cloudy, titled Labels or Predicted per view mode), or the
  uncertainty ramp when the uncertainty view is on. Removed the in-panel
  uncertainty legend.

- Fix map blanking during 'Run 5-fold CV': crossValidateMetrics is now async
  and yields to the event loop between folds, so the main thread stays free
  and the map keeps rendering instead of disappearing until results return.
  handleRunCV awaits it. Verified the async version returns identical results.

- Swipe divider hotkeys: with swipe on, A snaps the divider full-left, S to
  center, D full-right (via SwipeMap setOptions sliderPosition). Hotkey hint
  updated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e Back

- Bound the labeler root to the header-offset viewport height
  (calc(100vh - 40px - footer)), so the right panel scrolls in its own
  scrollbar when the Advanced tab is expanded instead of growing the page and
  pushing the on-map legends below the fold. Mirrors the Visualizer container.
- With the swipe view on, the 'Pre imagery' label now sits at the top-left and
  the Back button drops just below it (top:46), so Pre imagery is above the
  Back button rather than below it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…bility

- Predicted view and Uncertainty view are now mutually exclusive: turning one
  on (via toggle or the P hotkey) turns the other off.
- Both the View toggle and the Uncertainty toggle are disabled until there are
  enough labels to train (MIN_PER_CLASS in 2+ classes), with a hint shown; the
  P hotkey is likewise ignored until then. If labels later drop below the
  threshold, the map falls back to the Labeled view so it can't get stuck in a
  disabled Predicted/Uncertainty state.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant