diff --git a/frigate/test/test_stationary_classifier.py b/frigate/test/test_stationary_classifier.py new file mode 100644 index 0000000000..bd5e2c41e2 --- /dev/null +++ b/frigate/test/test_stationary_classifier.py @@ -0,0 +1,39 @@ +"""Tests for stationary object classification thresholds.""" + +import unittest + +from frigate.track.stationary_classifier import ( + DEFAULT_OBJECT_THRESHOLDS, + DYNAMIC_OBJECT_THRESHOLDS, + NON_STATIONARY_OBJECT_THRESHOLDS, + STATIONARY_OBJECT_THRESHOLDS, + StationaryThresholds, + get_stationary_threshold, +) + + +class TestStationaryThresholds(unittest.TestCase): + def test_known_labels_return_expected_singletons(self) -> None: + self.assertIs(get_stationary_threshold("package"), STATIONARY_OBJECT_THRESHOLDS) + self.assertIs(get_stationary_threshold("car"), DYNAMIC_OBJECT_THRESHOLDS) + self.assertIs( + get_stationary_threshold("license_plate"), + NON_STATIONARY_OBJECT_THRESHOLDS, + ) + + def test_unknown_label_returns_shared_default(self) -> None: + # an unknown label must reuse the shared default instance, not allocate + # a fresh one on every call (this runs per object per frame) + first = get_stationary_threshold("person") + second = get_stationary_threshold("dog") + self.assertIs(first, DEFAULT_OBJECT_THRESHOLDS) + self.assertIs(second, DEFAULT_OBJECT_THRESHOLDS) + + def test_default_matches_a_fresh_instance(self) -> None: + # the shared default must be value-equivalent to the previous + # per-call StationaryThresholds() + self.assertEqual(DEFAULT_OBJECT_THRESHOLDS, StationaryThresholds()) + + +if __name__ == "__main__": + unittest.main() diff --git a/frigate/track/norfair_tracker.py b/frigate/track/norfair_tracker.py index 3f11823572..7c695a7a7c 100644 --- a/frigate/track/norfair_tracker.py +++ b/frigate/track/norfair_tracker.py @@ -641,9 +641,11 @@ class NorfairTracker(ObjectTracker): self.deregister(self.track_id_map[e_id], e_id) # update list of object boxes that don't have a tracked object yet - tracked_object_boxes = [obj["box"] for obj in self.tracked_objects.values()] + tracked_object_boxes = { + tuple(obj["box"]) for obj in self.tracked_objects.values() + } self.untracked_object_boxes = [ - o[2] for o in detections if o[2] not in tracked_object_boxes + o[2] for o in detections if tuple(o[2]) not in tracked_object_boxes ] def print_objects_as_table(self, tracked_objects: Sequence) -> None: diff --git a/frigate/track/stationary_classifier.py b/frigate/track/stationary_classifier.py index bea37f641b..1e22ec6f6d 100644 --- a/frigate/track/stationary_classifier.py +++ b/frigate/track/stationary_classifier.py @@ -63,6 +63,9 @@ NON_STATIONARY_OBJECT_THRESHOLDS = StationaryThresholds( max_stationary_history=4, ) +# Default thresholds for any other object label +DEFAULT_OBJECT_THRESHOLDS = StationaryThresholds() + def get_stationary_threshold(label: str) -> StationaryThresholds: """Get the stationary thresholds for a given object label.""" @@ -76,7 +79,7 @@ def get_stationary_threshold(label: str) -> StationaryThresholds: if label in NON_STATIONARY_OBJECT_THRESHOLDS.objects: return NON_STATIONARY_OBJECT_THRESHOLDS - return StationaryThresholds() + return DEFAULT_OBJECT_THRESHOLDS class StationaryMotionClassifier: