feat: skip track forward on result overlap, not class presence (REQ-189)
Frame holding the tracked class is now annotated like any other; the run's result is dropped only when it intersects a same-class shape already there.
This commit is contained in:
1 parent
3eeb529e3d
commit
f5be7880b0
8 files changed
+86
-30
No files matched your search
@@ -200,9 +200,12 @@ changes.
|
||||
shape supplies its own box, a polygon is reduced to its bounding box. Each frame is an
|
||||
**independent** run: a frame SAM3 refuses (nothing inside the box) is reported and the rest
|
||||
still run; nothing selected, an unusable shape, or the last frame says so instead of doing
|
||||
nothing. A frame that **already holds a shape of the tracked class is skipped** — hidden or
|
||||
dimmed shapes count as present — and the skip is reported in the result banner and marked
|
||||
with a dot on that frame's filmstrip thumbnail, never silently passed over. A long run can be
|
||||
nothing. Every frame ahead **runs** — a frame that already holds a shape of the tracked class
|
||||
is still annotated when SAM3 finds a *different* object there. What is dropped is a
|
||||
**duplicate**: a result that intersects an existing shape of the tracked class (any shared
|
||||
area counts, hidden or dimmed shapes count) is not written, and that skip is reported in the
|
||||
result banner and marked with a dot on that frame's filmstrip thumbnail, never silently passed
|
||||
over. A long run can be
|
||||
**cancelled** mid-flight: cancelling stops the remaining frames, but a request already in
|
||||
flight is not aborted server-side and may still save its shape. The box is never grown and never reseeded from a result, so there is
|
||||
no size or position readjustment between frames: a fast-moving object can still leave the
|
||||
|
||||
+32
-1
@@ -1511,7 +1511,8 @@ read it.
|
||||
slice bound is computed from `Number(trackFrames)` so a string can never concatenate into the
|
||||
slice end; `slice(index+1, index+1+N)` and `slice(index+1)` both bounded; cancel lands as
|
||||
`cancelled — frame n may still be saved by the server` and keeps the shapes already written;
|
||||
skip check reads the `class_ids` array, not `annotation_count`; marks cleared at the start of
|
||||
skip check reads the `class_ids` array, not `annotation_count` — *that rule was later replaced
|
||||
by the post-run overlap test in the next task*; marks cleared at the start of
|
||||
every run; `Shift+T` deliberately **not** bound (a fat-fingered 500-frame GPU run is not worth
|
||||
a keystroke). Reviewer pass done — 4 findings fixed. **Still owed, user browser + GPU**: set
|
||||
N=3 → 3 shapes, reload → N remembered; to-end over the already-reviewed part of batch 19 →
|
||||
@@ -1534,6 +1535,36 @@ read it.
|
||||
snapshot left as history); **SAM3 actually finding the object in the next 5 frames needs
|
||||
the user's GPU run** — select a shape, press `T`, expect a shape on each of the next 5.
|
||||
|
||||
## Task — Track Forward skips on overlap, not on class presence (REQ-189 amended) `[DONE]`
|
||||
|
||||
- The old rule skipped a frame *before* SAM3 ran when it already held a shape of the tracked
|
||||
class, which contradicted REQ-189's own "each frame is an independent run": a second truck in
|
||||
the same frame could never be annotated. Amended to a duplicate test **after** the run.
|
||||
- `backend/review.py:intersects()` — any intersection between two normalized boxes (shared area
|
||||
> 0; edge touch is *not* an overlap), deliberately not IoU. `assist()` gained
|
||||
`dedupe: bool = False`: once the GPU lock is released and right before `add()`, an existing
|
||||
shape of the same class on that frame whose box intersects the result makes it answer
|
||||
`{"skipped": True}` and write nothing — no create-then-delete, no transient row.
|
||||
`backend/api/review.py:AssistRequest.dedupe` carries the flag; `False` keeps the single-shot
|
||||
box-assist path byte-identical.
|
||||
- `frontend/src/pages/ReviewPage.jsx:trackForward` — pre-skip on `class_ids` and the local
|
||||
`class_ids` union are gone, the call sends `dedupe: true`, and `{skipped: true}` lands in the
|
||||
banner as `skipped K overlapping <class>` plus the filmstrip dot `overlaps an existing <class>`.
|
||||
- `backend/batches.py:frames()` no longer ships `class_ids` (it was added for the pre-skip and
|
||||
died with it); `annotation_count` stays.
|
||||
- Docs: `requirements.md` REQ-189 skip sentence rewritten, `ui-spec.md` banner + thumb tooltip.
|
||||
- Cost accepted: a long run now pays a GPU call on **every** frame ahead, including frames that
|
||||
end up skipped, where the old pre-skip made those free. Only worth layering a cheap seed-box
|
||||
pre-skip back if that hurts.
|
||||
→ verify: **[DONE]** `uv run python` harness (mocked engine + temp PNG) — 6 `intersects`
|
||||
cases (edge-touch false, partial/contained/corner true, disjoint false) and 5 `assist()` paths:
|
||||
dedupe+overlap → `{"skipped": True}` with `add()` never called; other-class box → annotated;
|
||||
empty frame → annotated; `dedupe=False` → annotated; polygon annotation → skipped.
|
||||
`batches.frames()` run against the live DB (3737 rows, `class_ids` gone, `annotation_count`
|
||||
kept); `npm run build` green; `git diff --check` clean. **Still worth a GPU run:** track into a
|
||||
frame already holding an overlapping truck → banner `skipped K overlapping truck`, dot, count
|
||||
unchanged, while a frame holding a truck elsewhere gets its own second box.
|
||||
|
||||
## Known open points
|
||||
|
||||
- *Not closed by any task, by choice:* **any rebuild kills the running job.** Task 14's resume
|
||||
|
||||
+7
-4
@@ -937,7 +937,7 @@ worked". The jump happens once per load, and again after an auto-label job finis
|
||||
### 5.2 Data model in play
|
||||
|
||||
```
|
||||
frame { id, idx, filename, width, height, review_status, annotation_count, class_ids }
|
||||
frame { id, idx, filename, width, height, review_status, annotation_count }
|
||||
annotation { id, class_id, geometry, score, source: "auto"|"manual" }
|
||||
batch { …, review: { pending, approved, rejected }, annotation_count, status }
|
||||
```
|
||||
@@ -1036,9 +1036,12 @@ an "asking SAM3…" indicator ·
|
||||
selected, an unusable shape, or the last frame
|
||||
says so in the error slot instead of silently doing nothing; a frame SAM3 refuses (nothing
|
||||
inside the box) does not stop the rest — the banner then **starts** with `Tracked N of M`
|
||||
(M = frames actually ahead) followed by `skipped K already had <class>` and the per-frame
|
||||
failures. Skipped frames are those that already hold a shape of the tracked class (hidden or
|
||||
dimmed counts); they get a dot on their filmstrip thumb and the thumb's tooltip says why. The
|
||||
(M = frames actually ahead) followed by `skipped K overlapping <class>` and the per-frame
|
||||
failures. Every frame ahead runs SAM3; a frame is skipped only when its **result** lands on a
|
||||
shape of the tracked class already there (any intersection — hidden or dimmed counts), which
|
||||
means a frame holding the tracked class elsewhere still gets its own second box. Skipped frames
|
||||
get a dot on their filmstrip thumb and the thumb's tooltip says `overlaps an existing <class>`.
|
||||
The
|
||||
"asking SAM3…" indicator becomes a **Cancel** button while a run is in flight (Cancel stops the
|
||||
remaining frames; the frame already in flight can still be saved by the server, and the banner
|
||||
says so). A second
|
||||
|
||||
Reference in new issue
Block a user