Files
frigate/frigate/test/test_obects.py
T
ec3fb00494
CI / AMD64 Build (push) Has been cancelled
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled
perf(track): use sum()/len() instead of np.mean in average_boxes (#23521)
* perf(track): avoid numpy reductions on tiny box lists in position smoothing

update_position runs per tracked object per frame. While a position has
fewer than 10 samples it calls np.percentile four times, and average_boxes
(per stationary object per frame) calls np.mean four times - all on lists of
at most 10 ints, where numpy's per-call dispatch/validation overhead
dominates the actual work.

Replace them with pure-Python equivalents:
- average_boxes: sum()/len() instead of np.mean (bit-identical output)
- interpolated_percentile(): linear-interpolated percentile matching
  numpy.percentile (including its lerp branch at frac>=0.5) for the small
  lists used here, in place of np.percentile

Measured in the release image (numpy 1.26.4) on a 10-element list:
np.percentile 18735 ns -> 191 ns/call (98x); np.mean-based average_boxes
7480 ns -> 591 ns (12.7x); ~74 us saved per object-frame in update_position.
A live py-spy --gil profile of a camera process_frames worker showed
np.percentile (update_position) and np.mean (average_boxes) among the top
Frigate-owned on-CPU frames.

Output is unchanged: added tests assert both helpers are bit-identical to
numpy over randomized small inputs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Drop interpolated_percentile, keep only average_boxes

Per review: reimplementing np.percentile hurts readability and risks
divergence from numpy (e.g. numpy 2.x). Revert update_position to
np.percentile and remove the helper; keep only the average_boxes change
(sum()/len() instead of np.mean), which stays bit-identical.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 15:47:04 -06:00

67 lines
2.3 KiB
Python

import random
import unittest
import numpy as np
from frigate.track.tracked_object import TrackedObjectAttribute
from frigate.util.object import average_boxes
class TestBoxStatistics(unittest.TestCase):
def test_average_boxes_matches_numpy(self) -> None:
rng = random.Random(0)
for _ in range(5000):
boxes = [
[rng.randint(0, 4000) for _ in range(4)]
for _ in range(rng.randint(1, 10))
]
expected = [float(np.mean([b[i] for b in boxes])) for i in range(4)]
self.assertEqual(average_boxes(boxes), expected)
class TestAttribute(unittest.TestCase):
def test_overlapping_object_selection(self) -> None:
attribute = TrackedObjectAttribute(
(
"amazon",
0.80078125,
(847, 242, 883, 255),
468,
2.769230769230769,
(702, 134, 1050, 482),
)
)
objects = [
{
"label": "car",
"score": 0.98828125,
"box": (728, 223, 1266, 719),
"area": 266848,
"ratio": 1.0846774193548387,
"region": (349, 0, 1397, 1048),
"frame_time": 1727785394.498972,
"centroid": (997, 471),
"id": "1727785349.150633-408hal",
"start_time": 1727785349.150633,
"motionless_count": 362,
"position_changes": 0,
"score_history": [0.98828125, 0.95703125, 0.98828125, 0.98828125],
},
{
"label": "person",
"score": 0.76953125,
"box": (826, 172, 939, 417),
"area": 27685,
"ratio": 0.46122448979591835,
"region": (702, 134, 1050, 482),
"frame_time": 1727785394.498972,
"centroid": (882, 294),
"id": "1727785390.499768-9fbhem",
"start_time": 1727785390.499768,
"motionless_count": 2,
"position_changes": 1,
"score_history": [0.8828125, 0.83984375, 0.91796875, 0.94140625],
},
]
assert attribute.find_best_object(objects) == "1727785390.499768-9fbhem"