diff --git a/docs/design.md b/docs/design.md index ff2b038..3e691c1 100644 --- a/docs/design.md +++ b/docs/design.md @@ -346,12 +346,15 @@ switching the active class swaps pools instead of discarding them (REQ-172). The per class and accumulates (REQ-182): exemplar changes share **one 250 ms debounce** that re-runs the most recently touched class against its own pool and replaces only that class's shapes — rapid alternation between classes re-runs only the last one — while the other -exemplared classes' results stay on screen and the canvas renders the union of every class -with a non-empty pool. Undo is the same re-run with the shortened pool, because SAM3 can -only append geometric prompts. Clearing the last example sends no request and restores the -plain full-set preview (the session's last full-set run, blank if none ran yet); -Run Preview with examples runs the exemplared classes -sequentially and merges, without examples it is the unchanged full-set request. The +exemplared classes' results stay on screen and the canvas **merges** (amended REQ-182): +exemplared classes contribute their pool-conditioned results (replacing their own full-set +boxes), every other selected class keeps the detections of the last full-set run on that +frame. Undo is the same re-run with the shortened pool, because SAM3 can +only append geometric prompts. Clearing a class's last example sends no request and returns +that class to its full-set detections (blank if no full-set run covered it); clearing all +examples is then a plain full-set view again. Run Preview always refreshes the full-set for +every selected class first (with empty exemplars), then re-runs the exemplared classes +sequentially; drawing an example never triggers a full-set re-run. The per-class overrides table also carries a **Container** checkbox (REQ-184) that toggles `project_classes.container` through `PATCH /api/projects/{id} { containers: … }`. The same table carries a **Copy** button (REQ-186): one click puts every selected class's effective diff --git a/docs/requirements.md b/docs/requirements.md index d318365..ba78de8 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -129,12 +129,14 @@ changes. pool on the current frame: switching the active class shows that class's pool instead of discarding the others, and a new frame discards them all. - **REQ-182** — The auto-annotate preview is **per class and accumulates**. While at least one - class has example boxes on the previewed frame, the preview shows only the classes that have - examples, each conditioned on its own pool; re-drawing one class's example refreshes only - that class's predictions and leaves the other exemplared classes' results on screen. Classes - without examples contribute nothing while any example exists, and clearing the last example - restores the plain full-set preview. The preview and the batch job still use the same - thresholds and per-class overrides. + class has example boxes on the previewed frame, the preview **merges**: classes with examples + show only their pool-conditioned results, replacing their own full-set boxes, while classes + without examples keep the detections of the last full-set run on that frame. Re-drawing one + class's example refreshes only that class's predictions and leaves everything else on screen. + Run Preview always refreshes the full-set for every selected class first, then the exemplared + ones; drawing an example never triggers a full-set re-run. Clearing a class's last example + returns that class to its full-set detections, so clearing them all is a plain full-set view + again. The preview and the batch job still use the same thresholds and per-class overrides. - **REQ-186** — The auto-annotate parameters table has a **copy button** that puts every selected class's effective settings on the clipboard as short text, one class per line: `class conf iou minbox container `. Effective means the diff --git a/docs/tasks.md b/docs/tasks.md index d8cf2e7..a7dc222 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -1272,10 +1272,11 @@ failure modes to the counting path. The saving was always on the browser side. 1. Exemplar pools keyed by class and a per-class accumulating preview in the auto-annotate modal — switching the active class swaps pools and a frame change clears them all - (REQ-172); a redraw re-runs only the touched class against its own pool, the canvas unions - every exemplared class, classes without examples contribute nothing while any example - exists, clearing the last example falls back to the full-set preview, no examples at all → - the unchanged full-set request (REQ-182) → verify: **[DONE]** `cd frontend && npm run + (REQ-172); a redraw re-runs only the touched class against its own pool, the canvas merges + exemplared classes' conditioned results over the last full-set detections of the other + selected classes (REQ-182, as amended — superseded by the merged-preview entry below), + clearing the last example returns that class to its full-set detections, no examples at + all → the unchanged full-set request (REQ-182) → verify: **[DONE]** `cd frontend && npm run build` passes (✓ 536 ms, 69 modules); `wc -l` caps hold — `frontend/src/components/AutoAnnotateModal.jsx` (400) and `frontend/src/hooks/useExemplarPools.js` (100) both ≤ 400; rebuilt Docker frontend @@ -1348,6 +1349,18 @@ read it. invalid→global, float-noise case 0.30000000000000004 → `0.3`); browser clipboard click-test NOT automated — manual click-test pending. +## Task — Merged preview: exemplar draw keeps other classes (REQ-182 amended) `[DONE]` + +1. Non-exemplared classes keep last full-set detections when an exemplar is drawn; + exemplared class shows conditioned results replacing its own; Run Preview + refreshes full-set first (exemplars: []) then exemplared; draw never full-set + re-runs → verify: **[DONE]** `cd frontend && npm run build` passes (✓ 523 ms); + `wc -l frontend/src/components/AutoAnnotateModal.jsx` = 400 (at cap; blank lines + trimmed to fit, adjudicated by review); `git diff --stat` = 1 code file; grep + proof full-set call has `exemplars: []` (`AutoAnnotateModal.jsx:118`); 7-case + walkthrough in t11-report.md; browser test (draw example after Run Preview, other + classes persist) NOT automated — manual click-test pending. + ## Known open points - *Not closed by any task, by choice:* **any rebuild kills the running job.** Task 14's resume diff --git a/docs/ui-spec.md b/docs/ui-spec.md index 4b003a5..5b59fbd 100644 --- a/docs/ui-spec.md +++ b/docs/ui-spec.md @@ -485,11 +485,14 @@ them all. Adding, undoing or clearing an exemplar records the touched class and single revision counter — **one shared debounce** across all classes, 250 ms — which re-runs the most recently touched class against its own pool and replaces only its shapes, so rapid alternation between classes re-runs only the last one; the other exemplared classes' results -stay on screen and the canvas shows the union of every class with examples. Classes without -examples contribute nothing while any example exists; Run Preview with examples runs those -classes sequentially and merges, and with none it is the plain full-set preview. Clearing the -last example sends no request and restores the plain full-set preview (the session's last -full-set run, blank if none ran yet). Drawing a box *is* the question and the redrawn +stay on screen and the canvas **merges** (amended REQ-182): exemplared classes contribute +their pool-conditioned results (replacing their own full-set boxes), every other selected +class keeps the detections of the last full-set run on that frame. Classes without examples +keep those detections on screen; Run Preview always refreshes the full-set first (empty +exemplars), then re-runs the exemplared classes sequentially — drawing an example never +triggers a full-set re-run. Clearing a class's last example sends no request and returns +that class to its full-set detections (blank if no full-set run covered it); clearing them +all is a plain full-set view again. Drawing a box *is* the question and the redrawn preview is the answer, so no button press sits between them. Re-running is cheap because `set_image` is already cached for this frame — only the grounding head runs. diff --git a/frontend/src/components/AutoAnnotateModal.jsx b/frontend/src/components/AutoAnnotateModal.jsx index 996747b..dc70f8c 100644 --- a/frontend/src/components/AutoAnnotateModal.jsx +++ b/frontend/src/components/AutoAnnotateModal.jsx @@ -20,7 +20,6 @@ export default function AutoAnnotateModal({ const [minBoxFrac, setMinBoxFrac] = useState(0.0) const [classParams, setClassParams] = useState({}) - // Class selection state const [selectedClasses, setSelectedClasses] = useState(() => { if (engine === 'base_model') { @@ -85,20 +84,25 @@ export default function AutoAnnotateModal({ }) const activePool = activeClassName ? pools[activeClassName] || [] : [] - // While any selected class has examples, only those classes contribute. const exemplaredClasses = selectedClasses.filter(name => pools[name]?.length) + // merged: exemplared classes show conditioned results (replacing their full-set + // boxes); everyone else keeps the last full-set run (amended REQ-182) + const nameById = Object.fromEntries(project.classes.map(c => [c.class_id, c.name])) + const exemplaredSet = new Set(exemplaredClasses) + const selectedSet = new Set(selectedClasses) const renderShapes = exemplaredClasses.length > 0 - ? exemplaredClasses.flatMap(name => shapesByClass[name] || []) + ? [ + ...previewShapes.filter(s => { + const n = nameById[s.class_id] + return selectedSet.has(n) && !exemplaredSet.has(n) + }), + ...exemplaredClasses.flatMap(name => shapesByClass[name] || []), + ] : previewShapes const handlePreview = () => { - if (exemplaredClasses.length > 0) { - run(exemplaredClasses) - return - } const frame = currentFrame if (!frame) return - let isMounted = true setIsLoadingPreview(true) setPreviewError('') @@ -111,8 +115,8 @@ export default function AutoAnnotateModal({ target_class_names: selectedClasses, class_params: buildClassParams(classParams), custom_model_path: customModelStagedPath, - exemplars: engine === 'sam3' ? activePool : [], - exemplar_class_name: engine === 'sam3' ? activeClassName : null + exemplars: [], + exemplar_class_name: null }).then(res => { if (isMounted && res.shapes) { setPreviewShapes(res.shapes) @@ -123,7 +127,9 @@ export default function AutoAnnotateModal({ setPreviewError(err.message || 'Preview failed') setPreviewShapes([]) }).finally(() => { - setIsLoadingPreview(false) + if (!isMounted) return + if (exemplaredClasses.length > 0) run(exemplaredClasses) // manages busy itself + else setIsLoadingPreview(false) }) } @@ -212,11 +218,9 @@ export default function AutoAnnotateModal({ border: '1px solid rgba(168, 85, 247, 0.4)', background: '#18181b', overflowY: 'auto', borderRadius: 8 }}> - {/* Left Side: Preview */}

Preview

-
{currentFrame ? ( /* This wrapper must hug the image and nothing else: both overlays @@ -294,7 +298,6 @@ export default function AutoAnnotateModal({ )}
-
Preview Frame ({frameIndex + 1} / {frames.length}): @@ -380,7 +383,6 @@ export default function AutoAnnotateModal({ promptError={promptError} savedPrompt={activeClass ? classPrompts[activeClass.class_id] : ''} /> -
-
) } -