Internal · method record · Lear Body 2 pre-study · 26–27 Aug 2026

Body 2 Method Record

What was tried, what was measured, what was discarded and why — the audit trail behind the work-content findings.

Where it landed

yolo11n-pose on CPU · region = the hand-drawn box with its bottom extended to the frame edge · engaged = wrist depth below shoulder > 0.6 × torso · 15 s sampling for levels, 1 s for events. Engagement MAE 0.28, presence 0.39, within one operator on 97% of frames.

Decision log

chronological · every branch that changed the answer

01

Ran the supplied pipeline at its documented settings

SAM2 person count, --interval 5 --confidence 0.9, region boxes drawn by hand on 12 cameras.

✗ Frames scored 0 operators visibly contained 2–4. Confidence sweep on B2C12: mean 0.84 at conf 0.9, 1.49 at 0.7, 2.28 at 0.5 — a 2.7× swing on identical footage, and 43% of frames read zero at the shipped default.

02

Lowered confidence to 0.5

Recovered the missed operators — and started counting people across the aisle.

✗ Ground truth on 4 frames: at conf 0.5 a station with 3 real operators read 9. The two errors partially cancelled at conf 0.9, which is what made the original settings look plausible.

03

Trimmed 35% off the top of 6 aisle-contaminated cameras

Chosen by eye from the crops; validated on 2 cameras against manual counts.

✗ Applied to only half the cameras, which introduced a systematic bias: the six untrimmed read 3.13 mean against 1.91 for the six trimmed, on the same plan of 2.

04

Anchored every region to the yellow floor stripe

Auto-detected the stripe per camera, set the top edge 5% of frame height above it.

✗ 4–5× too tight on all twelve. Measured apparent person height per camera: 62–450 px, against a margin of only 24–97 px. B2C11 read 0.84 operators because its people were outside the box entirely.

05

Re-drew all twelve, then tuned the top edges by optimisation

Swept each top edge to maximise "capture" — the share of engaged detections falling inside the box — capped at ≤1 extra aisle person.

✗ Circular. Capture was scored against pose's own detections, which include people who are not that station's operators, so maximising it maximised admitting bodies. Six-way test: tuned tops scored worst of all variants (engaged MAE 0.61).

06

Reverted to the original hand-drawn boxes, bottom extended to the frame edge

Six-way comparison against 36 hand-scored frames settled it.

✓ Best of six: bodies 0.39, engaged 0.28, 97% within one. Extending down adds board and no people; raising the top adds people who should not count. An independent person-height calculation reproduced the original y1 to within 4 px on two cameras and 40 px on five.

07

Switched the model from SAM2 to pose

Triggered by the question "would hands be more robust than whole bodies?" — the answer turned out to be a model swap, not a rule change.

✓ A pose model must fit a whole skeleton, so it rejects the partial far-aisle bodies that inflated every detector. At B2C17: SAM2 11, yolo11n 7, pose 2, truth 2. This solved the region problem that steps 03–05 had been attacking from the wrong side.

08

Defined "working" as wrists below the board line

Board zone taken as everything below the yellow stripe.

✗ Returned 0 on frames with operators plainly working. Keypoints were found at 0.74–0.98 confidence but sat 56–187 px above the line. The stripe is painted on the floor; the board is an elevated bench, so hands appear above a floor marking in image space.

09

Redefined "working" as posture-relative reach

(wrist_y − shoulder_y) / torso_length > 0.6, computed per person.

✓ Self-normalising, so immune to camera height, resolution and the floor-versus-bench geometry that broke step 08. Needs no board polygon at all, which removes a calibration step. MAE 0.28 — better than any method's error on presence.

Model comparison

36 frames hand-scored across 9 stations · same regions, same frames

ModelRuns onMAEBiasExact±1Latency
SAM2 personsremote GPU2.03+1.9227%52%~12,400 msdropped
yolov8n personsCPU+0.2068.6 msnot pursued
yolo11n personsCPU1.28+0.7833%75%15.7 msdropped
yolo11n-pose personsCPU0.39+0.1169%94%53–246 msshipped
yolo11n-pose reach>0.6CPU0.28+0.0675%97%same callshipped
The expensive model bought nothing. On an earlier 10-frame set SAM2 scored MAE 1.00 against yolo11n's 0.90 — statistically indistinguishable at ~800× the latency. SAM2's value in this study was as a zero-shot labelling tool, not a runtime. Pose costs 3–15× plain detection and is still comfortably CPU-only at 1 Hz.

Region variants

six definitions · same 36 frames · same model

Region definitionBodies MAEBiasEngaged MAEBias±1
Original hand-drawn, bottom extended0.39+0.110.28+0.0697%shipped
Second redraw, as drawn0.42−0.140.42−0.2591%clips heads
Original hand-drawn, as drawn0.67+0.280.44+0.0094%
Second redraw, bottom extended0.64−0.310.58−0.2588%
Tuned tops, bottom extended0.61+0.220.61+0.1786%
Tuned tops, as drawn0.81+0.470.61+0.2283%worst

The two redraws sit either side of the original: the second redraw under-counts (bias −0.25, clipping heads to keep the aisle out), the tuned tops over-count (+0.22, admitting bench workers). Aisle discipline in the redraw was excellent on its own terms — 6 aisle admissions out of 230, 98% purity — but purity was bought with capture.

Sampling interval

not enforced by the service · chosen from the excursion-duration distribution

Excursions away from a station's own baseline, measured at 5 s and at 1 s. The distribution decides the interval, not the compute budget.

Excursion length5 s sampling1 s raw1 s median-31 s median-5
1 sample34%4,368933553
2 samples19%2,134796408
20–39 s314436434
40–79 s65163217
80 s+103049
5 s is a dead zone. A third of excursions there last exactly one sample — indistinguishable from detector noise at MAE ~1.0 — while the median-of-5 gate needed to suppress that noise spans 25 s and erases 80% of all excursions. Raw is untrustworthy, smoothed is blind.

At 1 s both work, and the smoothing behaves diagnostically: the 1–2 s population collapses 6,502 → 961 while the ≥20 s population grows 389 → 700. That growth is real events being reassembled from fragments a noisy detector had split. Chosen: 15 s for levels — unbiased at any interval, precision scales with √n — and 1 s for event structure.

Analyses withdrawn

reached a conclusion, then failed a check

ClaimWhat killed it
Extra operators stand
significantly upstream
The null pooled all normal-frame positions into one distribution. Stations have multiple standard work positions (B2C17 shows 3 modes, IQR 0.560) — a second person simply occupies the other one. Also an order-statistic artifact inflated the first estimate ~3×.
Entry edge identifies
work-ahead behaviour
Attributes to the wrong station. A person entering station N from downstream most likely belongs to N+1. Replaced by exit direction for people who first appear mid-frame — those were demonstrably working there.
Staffing is phase-locked
to the cycle (R² 45%)
Held at Station 1, does not generalise. Tested at each station's own period on 1 s data: median R² 4.2%, only 2 of 23 station-hours above 15%. The period generalises; the rhythm does not.
Chase rate as
"% of boards chased"
Exceeded 100% everywhere. ByteTrack fragments an occluded person into several tracks, inflating absolute counts by an unknown factor. Replaced by share of the station's own appearances, where fragmentation largely cancels — which reorders the ranking (rank correlation 0.62).
B2C17/B2C13 idle time
means operators waiting
They also have the highest chase rates. Much of that "idle" is likely operators walking downstream after boards, not standing. Unresolved — needs a walking-versus-standing classifier, and it inverts the recommended intervention.

What survived checking

each confirmed by an independent method

FindingValueIndependent confirmation
Pitch175 / 176 / 179 sBoard QR tracking · occupancy autocorrelation at 15 s · same at 1 s. Three methods within 4 s.
Cameras do not overlap0 collisionsORB matching (4–9 RANSAC inliers of 6,000 keypoints) · visual inspection · no board ID in two cameras at the same instant.
Exit direction is behaviourr = −0.01Against mean working position. Net drift agrees: all six chasing stations drift downstream, B2C12 drifts upstream.
No cascade between stations+0.14 / +0.02Lag 0 and lag +1 across 9 adjacent pairs. Spatial pattern is build-then-absorb, never accumulate.
Work content ≠ chase indexr = +0.30Weak by design — they measure work done versus work that did not fit. Both are needed to classify a station.
Line runs faster than takt179 s vs 211 sTakt supplied by Lear; pitch measured. Yields ~82 min/shift of downtime cushion.

Compute

measured on an M-series laptop, 10 cores

Datasets produced

all retained · ~/Downloads/Lear

DirectoryRowsConfiguration
results_pose_v25,381pose, present + engaged, 15 s, final regionsauthoritative
results_tracks14,023ByteTrack over yolo11n at 12 fps, entry/exit edgesauthoritative
results_local_1s_orig80,497yolo11n persons at 1 s — event structure, period detectionsupporting
results5,381SAM2 at 15 s, final regions — method comparisonsupporting
results_tracks_tin progressas above plus per-track timestamps, for the sub-block cascade testrunning
results_stripe_anchored16,101SAM2 5 s, stripe regions — superseded, kept for auditsuperseded
results_trimmed3516,101SAM2 5 s, 35% trim on 6 cameras — supersededsuperseded
results_preview1,362SAM2 60 s scoping pass at conf 0.9superseded

Eight region definitions are on disk as boxes_*.json, every one reproducible via draw_all_boxes.py --configs-only. All batch runners are resumable — row-level for counting, chunk-level for tracking — and resume was verified by SIGKILL rather than assumed.

Open

known limits of this record