From 6fd5b136b6ce79ae62ac841f2c8ea045d746513d Mon Sep 17 00:00:00 2001 From: patrykorwat Date: Fri, 21 Aug 2026 06:08:17 +0000 Subject: [PATCH] fix(axelera): stop keep-alive frames after pipeline init Misattribution reproduced at night: when Frigate stops sending frames during quiet periods the queue empties and keep-alive frames are emitted; a pending real-frame result then races the keep-alive's (empty) result, which steals the real id (frame/detections shown under the WRONG camera). Fix: synthesize keep-alive frames ONLY until the first stream result (_primed), then never again - no unregistered result can exist in the pipeline afterwards. Queue-empty after init just waits; under Frigate's continuous feed this branch is only hit during true quiet periods, where there is no pending request to misattribute anyway. --- frigate/detectors/plugins/axelera.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/frigate/detectors/plugins/axelera.py b/frigate/detectors/plugins/axelera.py index e3ea5436e7..cf990f8dfa 100644 --- a/frigate/detectors/plugins/axelera.py +++ b/frigate/detectors/plugins/axelera.py @@ -153,6 +153,11 @@ class _AxeleraInference: # flight the FIFO pop can misattribute a result to the wrong camera. # With at most one frame in flight the result can only belong to it. self._run_lock = threading.Lock() + # Set on the FIRST stream result. Until then the feed may synthesize + # keep-alive frames so the SDK pipeline finishes initializing; after + # this NO synthetic frames are ever emitted, so an unregistered result + # can never arrive while a real request id is pending. + self._primed = threading.Event() if stream_factory is None: # lazy import: never import the SDK unless the detector is used @@ -187,9 +192,16 @@ class _AxeleraInference: try: frame_id, frame = self._frame_q.get(timeout=1.0) except queue.Empty: - # keep-alive: no request pending, keep the SDK stream fed so the - # inference timeout never kills it during quiet periods. The - # None id means this frame's result is dropped by the pump. + if self._primed.is_set(): + # After init NEVER synthesize frames: a keep-alive result + # can arrive while a real id is pending and steal it + # (misattribution across cameras, observed at night when + # Frigate stops sending frames during quiet periods). + time.sleep(0.2) + continue + # During pipeline init the SDK needs a sustained source; these + # zero frames prime it and are drained before the first real + # request is registered. frame_id, frame = None, np.zeros( (self._height, self._width, 3), np.uint8 ) @@ -255,8 +267,11 @@ class _AxeleraInference: logger.info("axelera: inference stream started") try: for frame_result in self._stream: + if not self._primed.is_set(): + self._primed.set() + logger.info("axelera: pipeline initialized (first result)") out = _pad_rows(self._detection_rows(frame_result)) - if self._pending_ids: + if self._primed.is_set() and self._pending_ids: # FIFO: the stream returns results in submission order, so # the oldest pending id belongs to this result. frame_id = self._pending_ids.popleft()