Skip to content

Sequence matching can steal a fire's detections after a merged giant bbox (first-match-wins) #662

Description

@MateoLostanlen

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):

  1. 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.
  2. 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.
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions