pyronear / pyronear/pyro-api

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

Open
#662 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

endpoint: detections type: fix
Dominant language
Python
Stars
29
Forks
14
Avg merge
9d 1h
Merged PRs (30d)
5

Description

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

  • Should build on top of #624: matching must compare against the last real bbox (get_latest_with_bbox), ignoring continuity rows.
  • The production data above (sequences 56126 and 56161, detections around 14:46:51) can be replayed as a regression test scenario.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/app/api/api_v1/endpoints/detections.py, especially create_detection and _bboxes_overlap, then inspect how get_latest_with_bbox from #624 is used. Replay the production scenario around detection 2596275 and add a regression test covering competing sequences, candidate match quality, and the expected sequence assignment.

Written by the indexing model from the issue text.

Assessment

Tech stack
fastapi, postgresql, python
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.