fix: exemplar draw keeps other classes' full-set preview (REQ-182 amended)
- AutoAnnotateModal: render merges last full-set detections for non-exemplared classes with conditioned results for exemplared ones (selected-not-exemplared filter); drawing an example no longer clears the other classes' detections - Run Preview now always refreshes full-set first (exemplars: []) then re-runs exemplared classes - docs: REQ-182 amended (merge semantics), design/ui-spec/tasks synced
This commit is contained in:
1 parent
8fb65d2de9
commit
8f41c6c85a
5 files changed
+59
-38
No files matched your search
+9
-6
@@ -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
|
||||
|
||||
@@ -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 <name> conf <v> iou <v> minbox <v> container <true|false>`. Effective means the
|
||||
|
||||
+17
-4
@@ -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
|
||||
|
||||
+8
-5
@@ -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.
|
||||
|
||||
|
||||
@@ -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 */}
|
||||
<div style={{ flex: '1 1 450px', display: 'flex', flexDirection: 'column', minWidth: 280 }}>
|
||||
<h3 style={{ margin: '0 0 8px 0', color: '#e4e4e7', fontSize: '1rem' }}>Preview</h3>
|
||||
|
||||
<div style={{ position: 'relative', width: '100%', background: '#000', borderRadius: 6, overflow: 'hidden', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||
{currentFrame ? (
|
||||
/* This wrapper must hug the image and nothing else: both overlays
|
||||
@@ -294,7 +298,6 @@ export default function AutoAnnotateModal({
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<div className="row" style={{ justifyContent: 'space-between', marginBottom: 4 }}>
|
||||
<span className="hint" style={{ fontSize: '0.8rem' }}>Preview Frame ({frameIndex + 1} / {frames.length}):</span>
|
||||
@@ -380,7 +383,6 @@ export default function AutoAnnotateModal({
|
||||
promptError={promptError}
|
||||
savedPrompt={activeClass ? classPrompts[activeClass.class_id] : ''}
|
||||
/>
|
||||
|
||||
<div className="row" style={{ justifyContent: 'flex-end', gap: 10, marginTop: 16, paddingTop: 10, borderTop: '1px solid rgba(255,255,255,0.08)' }}>
|
||||
<button className="btn btn-ghost" onClick={onClose} disabled={isSubmitting}>Cancel</button>
|
||||
<button
|
||||
@@ -392,9 +394,7 @@ export default function AutoAnnotateModal({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user