# 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.