This commit includes major additions and updates to the frontend and backend architectures, introducing new dataset management, live counting features, batch processing, and triage logic. Includes new UI pages, components, and API routes.
73 lines
3.4 KiB
Markdown
73 lines
3.4 KiB
Markdown
# Annotation policy — sack counting
|
||
|
||
Why this document exists: the model is not the product, **the count** is. Every rule below
|
||
is derived from how `algoritma-batch/src/counting.py` turns boxes into counts. Change the
|
||
counter and this policy has to change with it.
|
||
|
||
## The one fact that drives everything
|
||
|
||
`LineCrossCounter` counts on **`y1` — the top edge of the box** (`counting.py`, "Uses y1
|
||
(top edge) of the stabilized sack bounding box"). A track is counted when it was seen ABOVE
|
||
the zone and later BELOW it.
|
||
|
||
So the top edge of every box you draw is a **measurement instrument**. Anything that moves
|
||
`y1` for a reason other than the sack moving is a counting error you baked into the dataset.
|
||
|
||
## The rule
|
||
|
||
| Situation | Annotate? | Box |
|
||
| --- | --- | --- |
|
||
| Fully visible | Yes | Visible extent |
|
||
| Partly occluded, **top edge visible** | Yes | Visible extent |
|
||
| **Top edge hidden** (head, arm, another sack over the top) | **No** | — |
|
||
| Under ~40% visible | No | — |
|
||
| Fully hidden | **Never** | — |
|
||
|
||
**If you cannot see where the sack's top edge is, do not annotate it.**
|
||
|
||
Reasoning: a head over the top of a sack drags `y1` down 50–100 px. The sack reads as lower
|
||
than it is and can trip `above → below` early — a phantom count caused by annotation, not by
|
||
the model. Leaving it unannotated costs nothing, because `cfg/tracker.yaml` sets
|
||
`track_buffer: 60` (~2.4 s at 25 fps) specifically to "survive worker occlusion". The tracker
|
||
coasts through the gap and keeps the ID.
|
||
|
||
Never annotate a sack you cannot see. Labeling invisible objects teaches the model to
|
||
hallucinate, which produces ghost tracks and overcounting.
|
||
|
||
## Consistency beats correctness
|
||
|
||
Either occlusion convention can work. **Mixing them cannot.** A model trained on "sometimes
|
||
we box occluded sacks, sometimes not" learns to fire at unstable confidence on ambiguous
|
||
evidence — which is how you get ID switches (overcount) and dropped tracks (undercount) at
|
||
the same time. Pick the table above and apply it identically, every session.
|
||
|
||
## Don't let triage delete the hard examples
|
||
|
||
SAM3's confidence correlates with occlusion: low score usually means *partly hidden*, not
|
||
*wrong*. A blanket "ignore the weakest 10%" rule therefore deletes exactly the occluded
|
||
examples and trains a model that only knows easy, fully-visible sacks. It will then fail
|
||
where workers stand — which is where the counting line is.
|
||
|
||
Use triage to remove boxes that are **wrong**:
|
||
|
||
- slivers and long thin masks (low aspect ratio)
|
||
- duplicates of the same sack
|
||
- background objects that are not sacks
|
||
- boxes so tiny they cannot be a sack at this camera distance
|
||
|
||
Do **not** use it to remove boxes that are merely **hard**. Check the crop grid before
|
||
saving any `ignore` rule — if the crops show real sacks, the threshold is too aggressive.
|
||
|
||
## "Half sack" is usually "occluded sack"
|
||
|
||
A sack that looks half-sized on screen is most often a *full* sack that is partly hidden, or
|
||
one further from the camera. Both are normal sacks. Only split a second class if the crop
|
||
grid shows a genuinely different object, and remember that box area tracks distance from the
|
||
camera at least as much as it tracks object size.
|
||
|
||
## Before trusting any of this
|
||
|
||
There is no ground truth yet. Hand-count 2–3 videos per camera and store the numbers, so a
|
||
retrained model can be measured against something real. Until then, mAP moves are not
|
||
evidence that the *count* improved — and the count is the product.
|