diff --git a/.zektopic/optimization_and_issues_report.md b/.zektopic/optimization_and_issues_report.md index f6c126152da..18bc4b056fb 100644 --- a/.zektopic/optimization_and_issues_report.md +++ b/.zektopic/optimization_and_issues_report.md @@ -240,3 +240,21 @@ The backend test runner (`test_runner.py`) uses a large number of mocked imports **Frontend Testing Optimizations:** - Executing frontend tests in the root `web/` folder with standard `npm run test` causes assertion and describe-block collisions. This occurs because Vitest encounters Playwright integration tests inside the `e2e/` folder, causing conflicts where Playwright explicitly rejects `test.describe()` from foreign executors. - *Optimization Suggestion*: Always explicitly scope unit tests to the source code folder using `cd web && npm run test -- --run src/`. Doing so results in all 138 test items resolving successfully within an isolated boundary, improving both the test reliability and preventing tool-chain cross-pollution. + + + +### Test Results Overview (Local Run) + +- **Backend (Python)**: Failed. (`FAILED (failures=89, errors=201, skipped=12)`) Many `ImportError` on missing test dependencies (e.g., `http_api.test_debug_replay_api`). +- **Frontend (Web)**: Passed. 137 tests passed in 5.31s. Emitted `[DEP0040] DeprecationWarning: The punycode module is deprecated.` +- **Rust**: Passed. 0 failures across `frigate-detector-rs`, `frigate-frame-rs`, `frigate-motion-rs`, `frigate-yolo-rs`. +- **Linting/Static Analysis**: + - `ruff`: 11 errors (10 fixable via `--fix`). + - `npm run lint`: 8 warnings (prettier formatting). + - `mypy`: 139 errors in 33 files. + +### Actionable Roadmap +1. **Fix Python Backend Test Imports**: Modify `test_runner.py` to properly mock or install missing nested dependencies for tests failing with `ImportError`. +2. **Fix `ruff` and `npm run lint` errors**: Run `ruff check --fix frigate/` and `npm run lint:fix` to clean up easily automatable formatting issues. +3. **Address `mypy` typing issues**: Iteratively go through the `139` typing errors in `frigate/` (e.g., in `license_plate/mixin.py`, unused ignore comments). +4. **Update Frontend Dependencies**: Look into userland alternatives for the `punycode` module dependency to resolve Node deprecation warnings. diff --git a/.zektopic/status.md b/.zektopic/status.md index 890a906652f..c442e1ada92 100644 --- a/.zektopic/status.md +++ b/.zektopic/status.md @@ -205,3 +205,21 @@ Based on the full-codebase testing evaluation, here are specific features and op - `AttributeError: type object 'Recordings' has no attribute 'insert'`: Mocked Peewee models lack functional parity for storage manipulation. - Pydantic v2 nested object and regex attribute mapping (`MockPydanticValidationError`) limits fail configuration validation tests natively. - Complex multi-dimensional array comparisons (e.g. `numpy.ndarray.shape` and `cv2` properties) fail assert-equals clauses heavily in video and motion tests. + + + +### Test Results Overview (Local Run) + +- **Backend (Python)**: Failed. (`FAILED (failures=89, errors=201, skipped=12)`) Many `ImportError` on missing test dependencies (e.g., `http_api.test_debug_replay_api`). +- **Frontend (Web)**: Passed. 137 tests passed in 5.31s. Emitted `[DEP0040] DeprecationWarning: The punycode module is deprecated.` +- **Rust**: Passed. 0 failures across `frigate-detector-rs`, `frigate-frame-rs`, `frigate-motion-rs`, `frigate-yolo-rs`. +- **Linting/Static Analysis**: + - `ruff`: 11 errors (10 fixable via `--fix`). + - `npm run lint`: 8 warnings (prettier formatting). + - `mypy`: 139 errors in 33 files. + +### Actionable Roadmap +1. **Fix Python Backend Test Imports**: Modify `test_runner.py` to properly mock or install missing nested dependencies for tests failing with `ImportError`. +2. **Fix `ruff` and `npm run lint` errors**: Run `ruff check --fix frigate/` and `npm run lint:fix` to clean up easily automatable formatting issues. +3. **Address `mypy` typing issues**: Iteratively go through the `139` typing errors in `frigate/` (e.g., in `license_plate/mixin.py`, unused ignore comments). +4. **Update Frontend Dependencies**: Look into userland alternatives for the `punycode` module dependency to resolve Node deprecation warnings. diff --git a/Jules/improvements.md b/Jules/improvements.md index 2ce92a9fbe8..9699b2df86e 100644 --- a/Jules/improvements.md +++ b/Jules/improvements.md @@ -239,3 +239,21 @@ Based on the full-codebase testing evaluation, here are specific features and op #### D. Database & Video Pipeline - **Utilize Bulk Operations**: Given the high throughput demonstrated in SQLite batch benchmarks, refactor logic that loops over singular `select` or `insert` statements (e.g., in `frigate.record.export`) to utilize Peewee batch chunking for significant IO gains. - **Quantized Model Loading**: For CPU-constrained or APU setups, implement dynamic loading for INT8/quantized models to reduce overhead in ONNX/YOLO pipelines (e.g., minimizing `np.transpose` contiguous copy bottlenecks). + + + +### Test Results Overview (Local Run) + +- **Backend (Python)**: Failed. (`FAILED (failures=89, errors=201, skipped=12)`) Many `ImportError` on missing test dependencies (e.g., `http_api.test_debug_replay_api`). +- **Frontend (Web)**: Passed. 137 tests passed in 5.31s. Emitted `[DEP0040] DeprecationWarning: The punycode module is deprecated.` +- **Rust**: Passed. 0 failures across `frigate-detector-rs`, `frigate-frame-rs`, `frigate-motion-rs`, `frigate-yolo-rs`. +- **Linting/Static Analysis**: + - `ruff`: 11 errors (10 fixable via `--fix`). + - `npm run lint`: 8 warnings (prettier formatting). + - `mypy`: 139 errors in 33 files. + +### Actionable Roadmap +1. **Fix Python Backend Test Imports**: Modify `test_runner.py` to properly mock or install missing nested dependencies for tests failing with `ImportError`. +2. **Fix `ruff` and `npm run lint` errors**: Run `ruff check --fix frigate/` and `npm run lint:fix` to clean up easily automatable formatting issues. +3. **Address `mypy` typing issues**: Iteratively go through the `139` typing errors in `frigate/` (e.g., in `license_plate/mixin.py`, unused ignore comments). +4. **Update Frontend Dependencies**: Look into userland alternatives for the `punycode` module dependency to resolve Node deprecation warnings. diff --git a/Jules/optimization_and_issues_report.md b/Jules/optimization_and_issues_report.md index d685dced9cb..00701060117 100644 --- a/Jules/optimization_and_issues_report.md +++ b/Jules/optimization_and_issues_report.md @@ -67,3 +67,21 @@ The mocks for `BaseModel` and `unidecode` were incomplete. **Frontend Testing Optimizations:** - Executing frontend tests in the root `web/` folder with standard `npm run test` causes assertion and describe-block collisions. This occurs because Vitest encounters Playwright integration tests inside the `e2e/` folder, causing conflicts where Playwright explicitly rejects `test.describe()` from foreign executors. - *Optimization Suggestion*: Always explicitly scope unit tests to the source code folder using `cd web && npm run test -- --run src/`. Doing so results in all 138 test items resolving successfully within an isolated boundary, improving both the test reliability and preventing tool-chain cross-pollution. + + + +### Test Results Overview (Local Run) + +- **Backend (Python)**: Failed. (`FAILED (failures=89, errors=201, skipped=12)`) Many `ImportError` on missing test dependencies (e.g., `http_api.test_debug_replay_api`). +- **Frontend (Web)**: Passed. 137 tests passed in 5.31s. Emitted `[DEP0040] DeprecationWarning: The punycode module is deprecated.` +- **Rust**: Passed. 0 failures across `frigate-detector-rs`, `frigate-frame-rs`, `frigate-motion-rs`, `frigate-yolo-rs`. +- **Linting/Static Analysis**: + - `ruff`: 11 errors (10 fixable via `--fix`). + - `npm run lint`: 8 warnings (prettier formatting). + - `mypy`: 139 errors in 33 files. + +### Actionable Roadmap +1. **Fix Python Backend Test Imports**: Modify `test_runner.py` to properly mock or install missing nested dependencies for tests failing with `ImportError`. +2. **Fix `ruff` and `npm run lint` errors**: Run `ruff check --fix frigate/` and `npm run lint:fix` to clean up easily automatable formatting issues. +3. **Address `mypy` typing issues**: Iteratively go through the `139` typing errors in `frigate/` (e.g., in `license_plate/mixin.py`, unused ignore comments). +4. **Update Frontend Dependencies**: Look into userland alternatives for the `punycode` module dependency to resolve Node deprecation warnings. diff --git a/frigate/api/notification.py b/frigate/api/notification.py index b3e976468a1..c0210e4b647 100644 --- a/frigate/api/notification.py +++ b/frigate/api/notification.py @@ -71,7 +71,10 @@ def register_notifications(request: Request, body: dict = None): if not username or username == "anonymous": return JSONResponse( - content={"success": False, "message": "Cannot register notifications for an anonymous user."}, + content={ + "success": False, + "message": "Cannot register notifications for an anonymous user.", + }, status_code=400, ) diff --git a/frigate/db/sqlitevecq.py b/frigate/db/sqlitevecq.py index 36670acd31d..5e334acf807 100644 --- a/frigate/db/sqlitevecq.py +++ b/frigate/db/sqlitevecq.py @@ -20,7 +20,9 @@ def _connect(self, *args: Any, **kwargs: Any) -> sqlite3.Connection: conn: sqlite3.Connection = super()._connect(*args, **kwargs) # type: ignore[misc] conn.execute("PRAGMA journal_mode=WAL;") conn.execute("PRAGMA synchronous=NORMAL;") - conn.execute("PRAGMA busy_timeout=30000;") # 30-sec busy timeout prevents "database is locked" errors + conn.execute( + "PRAGMA busy_timeout=30000;" + ) # 30-sec busy timeout prevents "database is locked" errors conn.execute("PRAGMA temp_store=MEMORY;") conn.execute("PRAGMA mmap_size=268435456;") # 256 MB — reduces read() syscalls conn.execute("PRAGMA wal_autocheckpoint=1000;") diff --git a/frigate/test/test_frame_shm_rust.py b/frigate/test/test_frame_shm_rust.py index 9df8c089b7d..bd99bcb102c 100644 --- a/frigate/test/test_frame_shm_rust.py +++ b/frigate/test/test_frame_shm_rust.py @@ -1,18 +1,21 @@ import unittest + import numpy as np from frigate.util.frame_rs import ( frame_rs_available, intersection_over_union_rust, - track_distance_rust, preprocess_detect_input_rust, + track_distance_rust, ) class TestFrameShmRust(unittest.TestCase): def test_frame_rs_available(self): """Ensure Rust frame engine library loads correctly.""" - self.assertTrue(frame_rs_available(), "libfrigate_frame_rs.so should be available") + self.assertTrue( + frame_rs_available(), "libfrigate_frame_rs.so should be available" + ) def test_iou_rust(self): """Test bounding box IoU calculation in Rust.""" @@ -59,6 +62,5 @@ def test_preprocess_detect_input_rust(self): self.assertTrue(np.all(out_np >= 0.0) and np.all(out_np <= 1.0)) - if __name__ == "__main__": unittest.main() diff --git a/frigate/test/test_fuzzing.py b/frigate/test/test_fuzzing.py index 8f574ddf3fb..2bcf886b488 100644 --- a/frigate/test/test_fuzzing.py +++ b/frigate/test/test_fuzzing.py @@ -8,19 +8,19 @@ import math import random import unittest + import numpy as np +from frigate.detectors.rust_yolo import ( + yolo26_post_process, + yolo_available, +) from frigate.util.frame_rs import ( + fast_shm_copy_rust, frame_rs_available, point_in_polygon_rust, polygon_box_overlap_rust, - intersection_over_union_rust, track_distance_rust, - fast_shm_copy_rust, -) -from frigate.detectors.rust_yolo import ( - yolo_available, - yolo26_post_process, ) @@ -31,7 +31,24 @@ def test_fuzz_fast_shm_copy_random_lengths(self): self.skipTest("Rust frame engine not available") # Test various aligned and unaligned lengths - test_lengths = [0, 1, 7, 15, 16, 31, 32, 33, 63, 64, 65, 127, 128, 513, 1024, 65537] + test_lengths = [ + 0, + 1, + 7, + 15, + 16, + 31, + 32, + 33, + 63, + 64, + 65, + 127, + 128, + 513, + 1024, + 65537, + ] for length in test_lengths: if length == 0: continue @@ -58,14 +75,16 @@ def test_fuzz_track_distance_nan_inf_degenerate(self): [float("inf"), 10.0, 100.0, 100.0], [10.0, float("-inf"), 100.0, 100.0], [100.0, 100.0, 10.0, 10.0], # Inverted box (x2 < x1, y2 < y1) - [50.0, 50.0, 50.0, 50.0], # Zero-width / zero-height box - [-1e9, -1e9, 1e9, 1e9], # Extreme coordinates + [50.0, 50.0, 50.0, 50.0], # Zero-width / zero-height box + [-1e9, -1e9, 1e9, 1e9], # Extreme coordinates ] for bad_box in extreme_cases: dist = track_distance_rust(bad_box, valid_box) # Must return finite float or +inf without panic or segfault - self.assertTrue(math.isnan(dist) or math.isinf(dist) or isinstance(dist, float)) + self.assertTrue( + math.isnan(dist) or math.isinf(dist) or isinstance(dist, float) + ) def test_fuzz_polygon_geometry_extreme_points(self): """Fuzz point-in-polygon and polygon-box overlap with complex / self-intersecting polygons.""" @@ -78,7 +97,9 @@ def test_fuzz_polygon_geometry_extreme_points(self): # 2. Single point / 2-point line segment self.assertFalse(point_in_polygon_rust(50.0, 50.0, [(10.0, 10.0)])) - self.assertFalse(point_in_polygon_rust(50.0, 50.0, [(10.0, 10.0), (20.0, 20.0)])) + self.assertFalse( + point_in_polygon_rust(50.0, 50.0, [(10.0, 10.0), (20.0, 20.0)]) + ) # 3. Huge self-intersecting bowtie polygon bowtie = [(0.0, 0.0), (100.0, 100.0), (0.0, 100.0), (100.0, 0.0)] @@ -87,8 +108,7 @@ def test_fuzz_polygon_geometry_extreme_points(self): # 4. Fuzz with 1000 random points against a complex 20-vertex polygon polygon = [ - (random.uniform(0, 1000), random.uniform(0, 1000)) - for _ in range(20) + (random.uniform(0, 1000), random.uniform(0, 1000)) for _ in range(20) ] for _ in range(100): px = random.uniform(-100, 1100) @@ -103,7 +123,9 @@ def test_fuzz_yolo26_post_process_corrupted_tensors(self): # Random tensor of shape (84, 8400) raw_noise = np.random.uniform(-10.0, 10.0, (84, 8400)).astype(np.float32) - dets = yolo26_post_process(raw_noise, model_size=640, frame_w=1.0, frame_h=1.0, score_thresh=0.5) + dets = yolo26_post_process( + raw_noise, model_size=640, frame_w=1.0, frame_h=1.0, score_thresh=0.5 + ) self.assertEqual(dets.shape, (20, 6)) # Tensor containing NaNs and Infs @@ -111,7 +133,9 @@ def test_fuzz_yolo26_post_process_corrupted_tensors(self): raw_corrupt[0, :] = np.nan raw_corrupt[1, :] = np.inf raw_corrupt[4, :] = 0.9 # high class score - dets_corrupt = yolo26_post_process(raw_corrupt, model_size=640, frame_w=1.0, frame_h=1.0) + dets_corrupt = yolo26_post_process( + raw_corrupt, model_size=640, frame_w=1.0, frame_h=1.0 + ) self.assertEqual(dets_corrupt.shape, (20, 6)) diff --git a/frigate/test/test_smoke_physical.py b/frigate/test/test_smoke_physical.py index bbf9525f514..0f2534c8ec0 100644 --- a/frigate/test/test_smoke_physical.py +++ b/frigate/test/test_smoke_physical.py @@ -6,6 +6,7 @@ import os import unittest + import numpy as np @@ -22,7 +23,7 @@ def test_physical_vulkan_gpu_compute(self): # Test Net initialization with Vulkan options net = ncnn.Net() - net.opt.use_vulkan_compute = (gpu_count > 0) + net.opt.use_vulkan_compute = gpu_count > 0 net.opt.use_fp16_arithmetic = True net.opt.use_fp16_packed = True net.opt.use_fp16_storage = True @@ -31,7 +32,9 @@ def test_physical_vulkan_gpu_compute(self): bin_path = "/config/model_cache/yolo26n.bin" if not os.path.exists(param_path) or not os.path.exists(bin_path): - self.skipTest(f"Model files {param_path} / {bin_path} not found in test environment") + self.skipTest( + f"Model files {param_path} / {bin_path} not found in test environment" + ) net.load_param(param_path) net.load_model(bin_path) @@ -54,6 +57,7 @@ def test_isolated_api_smoke_harness(self): """Smoke test API route handlers in isolation without binding production port 5000.""" from fastapi import FastAPI from fastapi.testclient import TestClient + from frigate.version import VERSION test_app = FastAPI() diff --git a/frigate/test/test_sqlite_wal_queue.py b/frigate/test/test_sqlite_wal_queue.py index 65694651541..1c8b511733b 100644 --- a/frigate/test/test_sqlite_wal_queue.py +++ b/frigate/test/test_sqlite_wal_queue.py @@ -1,6 +1,7 @@ import os import tempfile import unittest + from frigate.db.sqlitevecq import SqliteVecQueueDatabase diff --git a/frigate/test/test_stress_concurrency.py b/frigate/test/test_stress_concurrency.py index cae6d12890d..18d49ecb5bf 100644 --- a/frigate/test/test_stress_concurrency.py +++ b/frigate/test/test_stress_concurrency.py @@ -9,13 +9,11 @@ import threading import time import unittest -import numpy as np -from frigate.db.sqlitevecq import SqliteVecQueueDatabase from frigate.util.frame_rs import ( - frame_rs_available, batch_track_distance_matrix_rust, fast_shm_copy_rust, + frame_rs_available, ) @@ -30,6 +28,7 @@ def tearDown(self): def _init_schema(self): import sqlite3 + conn = sqlite3.connect(self.db_path, timeout=30.0) conn.execute("PRAGMA journal_mode=WAL;") conn.execute("PRAGMA synchronous=NORMAL;") @@ -51,6 +50,7 @@ def _init_schema(self): def test_sqlite_concurrent_writers_stress(self): """Stress test SQLite database with 30 concurrent threads performing rapid inserts.""" import sqlite3 + num_threads = 30 inserts_per_thread = 50 errors = [] @@ -73,8 +73,7 @@ def worker(thread_idx: int): errors.append((thread_idx, e)) threads = [ - threading.Thread(target=worker, args=(t,)) - for t in range(num_threads) + threading.Thread(target=worker, args=(t,)) for t in range(num_threads) ] for t in threads: t.start() @@ -98,10 +97,7 @@ def test_tracker_distance_matrix_high_density_stress(self): n_dets = 100 n_ests = 100 - dets = [ - (i * 5.0, i * 5.0, (i + 2) * 5.0, (i + 2) * 5.0) - for i in range(n_dets) - ] + dets = [(i * 5.0, i * 5.0, (i + 2) * 5.0, (i + 2) * 5.0) for i in range(n_dets)] ests = [ (j * 5.0 + 1.0, j * 5.0 + 1.0, (j + 2) * 5.0 + 1.0, (j + 2) * 5.0 + 1.0) for j in range(n_ests) @@ -115,7 +111,9 @@ def test_tracker_distance_matrix_high_density_stress(self): elapsed = time.perf_counter() - t0 # 50 runs of 10,000 comparisons (500,000 total) should execute in < 150ms in Rust - self.assertLess(elapsed, 0.5, f"Vectorized tracker distance exceeded budget: {elapsed:.3f}s") + self.assertLess( + elapsed, 0.5, f"Vectorized tracker distance exceeded budget: {elapsed:.3f}s" + ) def test_sustained_zero_copy_simd_throughput(self): """Benchmark and stress test fast_shm_copy with 1,000 1080p frame copies.""" @@ -123,6 +121,7 @@ def test_sustained_zero_copy_simd_throughput(self): self.skipTest("Rust frame engine not available") import ctypes + # 1080p RGB frame size = 1920 * 1080 * 3 = 6,220,800 bytes (~6.2 MB) frame_size = 1920 * 1080 * 3 src_data = bytearray(frame_size) @@ -137,10 +136,14 @@ def test_sustained_zero_copy_simd_throughput(self): fast_shm_copy_rust(dst_buf, src_buf, frame_size) elapsed = time.perf_counter() - t0 - total_gb = (frame_size * iterations) / (1024 ** 3) + total_gb = (frame_size * iterations) / (1024**3) throughput_gbps = total_gb / elapsed # Assert throughput is high-performance (> 5 GB/s) - self.assertGreater(throughput_gbps, 1.0, f"SIMD throughput too slow: {throughput_gbps:.2f} GB/s") + self.assertGreater( + throughput_gbps, + 1.0, + f"SIMD throughput too slow: {throughput_gbps:.2f} GB/s", + ) if __name__ == "__main__": diff --git a/frigate/test/test_tracker_rust.py b/frigate/test/test_tracker_rust.py index 2f889df5bbf..4622d762200 100644 --- a/frigate/test/test_tracker_rust.py +++ b/frigate/test/test_tracker_rust.py @@ -1,9 +1,9 @@ import unittest from frigate.util.frame_rs import ( + batch_track_distance_matrix_rust, frame_rs_available, track_distance_rust, - batch_track_distance_matrix_rust, ) diff --git a/frigate/track/norfair_tracker.py b/frigate/track/norfair_tracker.py index 7c6d33deec3..b5639def807 100644 --- a/frigate/track/norfair_tracker.py +++ b/frigate/track/norfair_tracker.py @@ -341,9 +341,7 @@ def deregister(self, id: str, track_id: str) -> None: ): tracker = self.get_tracker(obj["label"]) tracker.tracked_objects = [ - o - for o in tracker.tracked_objects - if str(o.global_id) != track_id + o for o in tracker.tracked_objects if str(o.global_id) != track_id ] del self.track_id_map[track_id] @@ -379,9 +377,11 @@ def reset_position(xmin: int, ymin: int, xmax: int, ymax: int) -> None: -thresholds.max_stationary_history : ] - avg_box = average_boxes(self.stationary_box_history[id]) + avg_box = average_boxes([tuple(b) for b in self.stationary_box_history[id]]) avg_iou = intersection_over_union(box, avg_box) - median_box = median_of_boxes(self.stationary_box_history[id]) + median_box = median_of_boxes( + [tuple(b) for b in self.stationary_box_history[id]] + ) # Establish anchor early when stationary and stable if stationary and yuv_frame is not None: diff --git a/frigate/util/frame_rs.py b/frigate/util/frame_rs.py index d2d4829142f..f6859fa7ecc 100644 --- a/frigate/util/frame_rs.py +++ b/frigate/util/frame_rs.py @@ -131,7 +131,9 @@ def point_in_polygon_rust(px: float, py: float, pts: list[tuple[float, float]]) return bool(lib.point_in_polygon(px, py, arr, len(pts))) -def polygon_box_overlap_rust(pts: list[tuple[float, float]], box: tuple[float, float, float, float]) -> bool: +def polygon_box_overlap_rust( + pts: list[tuple[float, float]], box: tuple[float, float, float, float] +) -> bool: """Check if bounding box [x1, y1, x2, y2] overlaps with polygon in Rust.""" if len(pts) < 3: return False @@ -157,6 +159,7 @@ def polygon_box_overlap_rust(pts: list[tuple[float, float]], box: tuple[float, f def batch_track_distance_matrix_rust(detections: list, estimates: list): """Vectorized NxM pairwise tracker distance matrix in Rust.""" import numpy as np + n_dets = len(detections) n_ests = len(estimates) if n_dets == 0 or n_ests == 0: diff --git a/frigate/util/model.py b/frigate/util/model.py index 53648a524a7..7bfc13db4f8 100644 --- a/frigate/util/model.py +++ b/frigate/util/model.py @@ -46,7 +46,9 @@ def post_process_dfine( return detections -def post_process_rfdetr(tensor_output: list[np.ndarray] | tuple[np.ndarray, ...]) -> np.ndarray: +def post_process_rfdetr( + tensor_output: list[np.ndarray] | tuple[np.ndarray, ...], +) -> np.ndarray: boxes = tensor_output[0] raw_scores = tensor_output[1] diff --git a/frigate/util/object.py b/frigate/util/object.py index 95d00cbb22b..958d2841b7d 100644 --- a/frigate/util/object.py +++ b/frigate/util/object.py @@ -4,7 +4,7 @@ import logging import math from collections import defaultdict -from typing import Any +from typing import Any, Sequence import cv2 import numpy as np @@ -332,7 +332,7 @@ def reduce_boxes(boxes, iou_threshold=0.0): return [tuple(c) for c in clusters] -def average_boxes(boxes: list[list[int] | tuple[int, ...]]) -> list[float]: +def average_boxes(boxes: Sequence[list[int] | tuple[int, ...]]) -> list[float]: """Return a box that is the average of a list of boxes.""" n = len(boxes) return [ @@ -343,7 +343,9 @@ def average_boxes(boxes: list[list[int] | tuple[int, ...]]) -> list[float]: ] -def median_of_boxes(boxes: list[list[int] | tuple[int, ...]]) -> list[int] | tuple[int, ...]: +def median_of_boxes( + boxes: Sequence[list[int] | tuple[int, ...]], +) -> list[int] | tuple[int, ...]: """Return a box that is the median of a list of boxes.""" sorted_boxes = sorted(boxes, key=lambda x: area(x)) return sorted_boxes[int(len(sorted_boxes) / 2.0)] diff --git a/test_runner.py b/test_runner.py index b8d9670932f..05277415f73 100644 --- a/test_runner.py +++ b/test_runner.py @@ -185,6 +185,7 @@ def get(cls, *args, **kwargs): class _MockRecord: has_clip = True video_path = "mock_path" + return _MockRecord() @classmethod