Summary
When two fires are visible on the same camera pose, a single oversized bbox (the model merging a plume into one giant box) can get attached to the wrong sequence. From that point on, the wrong sequence tracks the other fire's detections and the correct sequence starves. Both alerts then end up showing the same fire, while their triangulated positions still point to two different locations.
Observed in production on 2026-07-29, camera croix-augas-02 (id 41), pose 79, alerts 51205 and 51185.
Production timeline
Two sequences ran correctly in parallel on the same pose:
| Sequence |
Alert |
Fire |
Started |
bbox center x |
Azimuth |
| 56126 |
51205 |
A |
11:27 |
~0.31 |
224.6° |
| 56161 |
51185 |
B |
14:03 |
~0.53 |
238.1° |
- 14:42 fire A fades (conf drops to 0.21 then 0.00). Last detection of 56126 at 14:42:52, last detection of 56161 at 14:42:51.
- 14:42:52 to 14:46:51 no detections at all (4 min gap).
- 14:46:51 detection id 2596275 arrives with a giant bbox
(0.348, 0.070, 0.684, 0.524) covering fire B's whole plume. It is attached to 56126 (fire A's sequence).
- 14:46 to 17:21 every fire B bbox (center x ≈ 0.55) lands in 56126. Sequence 56161 never receives anything again (
last_seen_at stuck at 14:42:51).
Result: alert 51205, triangulated with fire A's azimuth (224.6°), displays fire B's images for 2.5 hours.
Root cause
Three weaknesses in the spatial matching combine (create_detection in src/app/api/api_v1/endpoints/detections.py):
- Giant boxes match everything.
_bboxes_overlap measures an edge gap with a 0.05 tolerance per axis, and edge contact counts as a match. The giant box had an x-gap of 0.030 with fire A's last box (0.308, 0.524, 0.318, 0.538) and touched it in y at exactly 0.524. The bigger the box, the more sequences it matches.
- First match wins, tie-broken by seconds. Candidates are ordered by
last_seen_at DESC and the loop takes the first overlap. 56126 had been seen at 14:42:52 vs 14:42:51 for 56161: one second decided the attachment. The giant box overlapped 56161 at ~100% IoU versus a mere edge contact with 56126, but match quality is never compared.
- A sequence's identity is its last bbox only. Once the giant box was in 56126, its anchor moved onto fire B. Every subsequent fire B box then matched 56126 first (always the freshest), so the steal is irreversible and 56161 starved.
Proposed fix
- Best match instead of first match: among candidate sequences within tolerance, pick the highest IoU (or the closest bbox center). This alone would have attached the giant box to 56161.
- Optionally, guard against anchor jumps: reject a match when the new box area is N times the sequence's last real box area, or require actual overlap (not tolerated edge contact) when several sequences are candidates.
- Optionally, add inertia: compare against the median center of the last K real bboxes instead of the single last one, so one aberrant box cannot re-anchor a sequence.
Notes
Summary
When two fires are visible on the same camera pose, a single oversized bbox (the model merging a plume into one giant box) can get attached to the wrong sequence. From that point on, the wrong sequence tracks the other fire's detections and the correct sequence starves. Both alerts then end up showing the same fire, while their triangulated positions still point to two different locations.
Observed in production on 2026-07-29, camera
croix-augas-02(id 41), pose 79, alerts 51205 and 51185.Production timeline
Two sequences ran correctly in parallel on the same pose:
(0.348, 0.070, 0.684, 0.524)covering fire B's whole plume. It is attached to 56126 (fire A's sequence).last_seen_atstuck at 14:42:51).Result: alert 51205, triangulated with fire A's azimuth (224.6°), displays fire B's images for 2.5 hours.
Root cause
Three weaknesses in the spatial matching combine (
create_detectioninsrc/app/api/api_v1/endpoints/detections.py):_bboxes_overlapmeasures an edge gap with a 0.05 tolerance per axis, and edge contact counts as a match. The giant box had an x-gap of 0.030 with fire A's last box(0.308, 0.524, 0.318, 0.538)and touched it in y at exactly 0.524. The bigger the box, the more sequences it matches.last_seen_at DESCand the loop takes the first overlap. 56126 had been seen at 14:42:52 vs 14:42:51 for 56161: one second decided the attachment. The giant box overlapped 56161 at ~100% IoU versus a mere edge contact with 56126, but match quality is never compared.Proposed fix
Notes
get_latest_with_bbox), ignoring continuity rows.