pyzm 2.3.2__tar.gz → 2.3.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. {pyzm-2.3.2/pyzm.egg-info → pyzm-2.3.4}/PKG-INFO +3 -3
  2. {pyzm-2.3.2 → pyzm-2.3.4}/README.md +2 -2
  3. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/__init__.py +1 -1
  4. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/ml/detector.py +22 -2
  5. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/models/zm.py +2 -0
  6. {pyzm-2.3.2 → pyzm-2.3.4/pyzm.egg-info}/PKG-INFO +3 -3
  7. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml/test_detector.py +58 -0
  8. {pyzm-2.3.2 → pyzm-2.3.4}/LICENSE +0 -0
  9. {pyzm-2.3.2 → pyzm-2.3.4}/MANIFEST.in +0 -0
  10. {pyzm-2.3.2 → pyzm-2.3.4}/pyproject.toml +0 -0
  11. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/client.py +0 -0
  12. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/log.py +0 -0
  13. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/ml/__init__.py +0 -0
  14. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/ml/backends/__init__.py +0 -0
  15. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/ml/backends/alpr.py +0 -0
  16. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/ml/backends/base.py +0 -0
  17. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/ml/backends/birdnet.py +0 -0
  18. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/ml/backends/coral.py +0 -0
  19. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/ml/backends/face_dlib.py +0 -0
  20. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/ml/backends/face_tpu.py +0 -0
  21. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/ml/backends/rekognition.py +0 -0
  22. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/ml/backends/yolo.py +0 -0
  23. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/ml/backends/yolo_darknet.py +0 -0
  24. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/ml/backends/yolo_onnx.py +0 -0
  25. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/ml/face_train_dlib.py +0 -0
  26. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/ml/filters.py +0 -0
  27. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/ml/pipeline.py +0 -0
  28. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/models/__init__.py +0 -0
  29. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/models/config.py +0 -0
  30. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/models/detection.py +0 -0
  31. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/serve/__init__.py +0 -0
  32. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/serve/__main__.py +0 -0
  33. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/serve/app.py +0 -0
  34. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/serve/auth.py +0 -0
  35. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/train/__init__.py +0 -0
  36. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/train/__main__.py +0 -0
  37. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/train/_import_panel.py +0 -0
  38. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/train/_review_panel.py +0 -0
  39. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/train/_train_panel.py +0 -0
  40. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/train/app.py +0 -0
  41. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/train/assets/pyzm.png +0 -0
  42. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/train/auto_label.py +0 -0
  43. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/train/dataset.py +0 -0
  44. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/train/local_import.py +0 -0
  45. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/train/pipeline.py +0 -0
  46. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/train/trainer.py +0 -0
  47. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/train/verification.py +0 -0
  48. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/zm/__init__.py +0 -0
  49. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/zm/api.py +0 -0
  50. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/zm/auth.py +0 -0
  51. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/zm/db.py +0 -0
  52. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/zm/media.py +0 -0
  53. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm/zm/shm.py +0 -0
  54. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm.egg-info/SOURCES.txt +0 -0
  55. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm.egg-info/dependency_links.txt +0 -0
  56. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm.egg-info/requires.txt +0 -0
  57. {pyzm-2.3.2 → pyzm-2.3.4}/pyzm.egg-info/top_level.txt +0 -0
  58. {pyzm-2.3.2 → pyzm-2.3.4}/setup.cfg +0 -0
  59. {pyzm-2.3.2 → pyzm-2.3.4}/setup.py +0 -0
  60. {pyzm-2.3.2 → pyzm-2.3.4}/tests/__init__.py +0 -0
  61. {pyzm-2.3.2 → pyzm-2.3.4}/tests/conftest.py +0 -0
  62. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_config.py +0 -0
  63. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_config_variants.py +0 -0
  64. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_face_dlib_lock.py +0 -0
  65. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_filters.py +0 -0
  66. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_log.py +0 -0
  67. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml/__init__.py +0 -0
  68. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml/test_birdnet.py +0 -0
  69. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml/test_filters.py +0 -0
  70. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml/test_pipeline.py +0 -0
  71. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml/test_pipeline_lazy.py +0 -0
  72. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml/test_yolo_onnx.py +0 -0
  73. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/__init__.py +0 -0
  74. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/conftest.py +0 -0
  75. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_basic_detection.py +0 -0
  76. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_disabled_models.py +0 -0
  77. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_edge_cases.py +0 -0
  78. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_filter_combinations.py +0 -0
  79. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_frame_strategies.py +0 -0
  80. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_from_dict.py +0 -0
  81. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_match_strategies.py +0 -0
  82. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_min_confidence.py +0 -0
  83. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_model_dimensions.py +0 -0
  84. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_model_discovery.py +0 -0
  85. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_multi_model.py +0 -0
  86. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_past_detections.py +0 -0
  87. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_pattern_filtering.py +0 -0
  88. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_pipeline_loading.py +0 -0
  89. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_pre_existing_labels.py +0 -0
  90. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_remote_serve.py +0 -0
  91. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_server_config.py +0 -0
  92. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_size_filtering.py +0 -0
  93. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_stream_config.py +0 -0
  94. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_ml_e2e/test_zone_filtering.py +0 -0
  95. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_models/__init__.py +0 -0
  96. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_models/test_config.py +0 -0
  97. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_models/test_detection.py +0 -0
  98. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_models/test_zm.py +0 -0
  99. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_serve/__init__.py +0 -0
  100. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_serve/test_app.py +0 -0
  101. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_serve/test_auth.py +0 -0
  102. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_serve/test_models_all.py +0 -0
  103. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_train/__init__.py +0 -0
  104. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_train/test_app_review.py +0 -0
  105. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_train/test_auto_label.py +0 -0
  106. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_train/test_dataset.py +0 -0
  107. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_train/test_local_import.py +0 -0
  108. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_train/test_trainer.py +0 -0
  109. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_train/test_verification.py +0 -0
  110. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_train_pipeline.py +0 -0
  111. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm/__init__.py +0 -0
  112. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm/test_api.py +0 -0
  113. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm/test_auth.py +0 -0
  114. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm/test_client.py +0 -0
  115. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm_e2e/__init__.py +0 -0
  116. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm_e2e/conftest.py +0 -0
  117. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm_e2e/test_auth.py +0 -0
  118. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm_e2e/test_configs.py +0 -0
  119. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm_e2e/test_db.py +0 -0
  120. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm_e2e/test_detection.py +0 -0
  121. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm_e2e/test_events.py +0 -0
  122. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm_e2e/test_frames.py +0 -0
  123. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm_e2e/test_monitors.py +0 -0
  124. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm_e2e/test_states.py +0 -0
  125. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm_e2e/test_system.py +0 -0
  126. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm_e2e/test_write_events.py +0 -0
  127. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm_e2e/test_write_monitors.py +0 -0
  128. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm_e2e/test_write_notes.py +0 -0
  129. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm_e2e/test_write_state.py +0 -0
  130. {pyzm-2.3.2 → pyzm-2.3.4}/tests/test_zm_e2e/test_zones.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyzm
3
- Version: 2.3.2
3
+ Version: 2.3.4
4
4
  Summary: ZoneMinder API, Logger and other base utilities for python programmers
5
5
  Home-page: https://github.com/pliablepixels/pyzm
6
6
  Author: Pliable Pixels
@@ -65,11 +65,11 @@ Requires-Dist: st-clickable-images>=0.0.3; extra == "full"
65
65
 
66
66
  What
67
67
  =====
68
- pyzmv2 is a rewrite of pyzm.
68
+ pyzmNg is a rewrite of pyzm.
69
69
 
70
70
  It's a pythonic wrapper that integrates with ZM and also operates as a standalone ML library. Key features:
71
71
  - ZM API
72
- - ZM Event Server
72
+ - ZM EventServerNg
73
73
  - ZM Logger
74
74
  - ZM Memory
75
75
  - Machine Learning Modules (with our without ZM)
@@ -2,11 +2,11 @@
2
2
 
3
3
  What
4
4
  =====
5
- pyzmv2 is a rewrite of pyzm.
5
+ pyzmNg is a rewrite of pyzm.
6
6
 
7
7
  It's a pythonic wrapper that integrates with ZM and also operates as a standalone ML library. Key features:
8
8
  - ZM API
9
- - ZM Event Server
9
+ - ZM EventServerNg
10
10
  - ZM Logger
11
11
  - ZM Memory
12
12
  - Machine Learning Modules (with our without ZM)
@@ -3,7 +3,7 @@
3
3
  v2: Typed configuration, clean APIs, proper result objects.
4
4
  """
5
5
 
6
- __version__ = "2.3.2"
6
+ __version__ = "2.3.4"
7
7
  VERSION = __version__
8
8
 
9
9
  from pyzm.client import ZMClient
@@ -291,14 +291,31 @@ class Detector:
291
291
  detections: list,
292
292
  zones: list["Zone"] | None,
293
293
  image_shape: tuple[int, int],
294
+ original_shape: tuple[int, int] | None = None,
294
295
  ) -> tuple[list, list]:
295
296
  """Apply client-side filters: pattern, size, zones, past detections.
296
297
 
298
+ Parameters
299
+ ----------
300
+ original_shape:
301
+ ``(height, width)`` of the image before resizing. When provided
302
+ and different from *image_shape*, zone polygons are rescaled from
303
+ original coordinates to match the (resized) detection space.
304
+
297
305
  Returns (kept_detections, error_boxes).
298
306
  """
299
307
  h, w = image_shape
300
308
  zone_dicts = [z.as_dict() for z in zones] if zones else []
301
309
 
310
+ # Rescale zone polygons when the image was resized
311
+ if original_shape and zone_dicts and (original_shape[0] != h or original_shape[1] != w):
312
+ orig_h, orig_w = original_shape
313
+ xfactor = w / orig_w
314
+ yfactor = h / orig_h
315
+ for zd in zone_dicts:
316
+ pts = zd.get("value") or zd.get("points", [])
317
+ zd["value"] = [(int(x * xfactor), int(y * yfactor)) for x, y in pts]
318
+
302
319
  detections = filter_by_pattern(detections, self._config.pattern)
303
320
  detections = filter_by_size(detections, self._config.max_detection_size, (h, w))
304
321
  detections, error_boxes = filter_by_zone(detections, zone_dicts, (h, w))
@@ -332,6 +349,7 @@ class Detector:
332
349
  self,
333
350
  image: "np.ndarray",
334
351
  zones: list["Zone"] | None = None,
352
+ original_shape: tuple[int, int] | None = None,
335
353
  ) -> DetectionResult:
336
354
  """Send an image to the remote gateway for detection, then filter locally."""
337
355
  import cv2
@@ -355,7 +373,9 @@ class Detector:
355
373
 
356
374
  # Apply client-side filters
357
375
  h, w = image.shape[:2]
358
- filtered, error_boxes = self._apply_filters(result.detections, zones, (h, w))
376
+ filtered, error_boxes = self._apply_filters(
377
+ result.detections, zones, (h, w), original_shape=original_shape,
378
+ )
359
379
 
360
380
  return DetectionResult(
361
381
  detections=filtered,
@@ -845,7 +865,7 @@ class Detector:
845
865
 
846
866
  for frame_id, image in frames:
847
867
  try:
848
- result = self._remote_detect(image, zones)
868
+ result = self._remote_detect(image, zones, original_shape=original_shape)
849
869
  result.frame_id = frame_id
850
870
  if original_shape:
851
871
  result.image_dimensions["original"] = original_shape
@@ -231,6 +231,7 @@ class Notification:
231
231
  interval: int = 0
232
232
  push_state: str = "enabled"
233
233
  app_version: str | None = None
234
+ profile: str | None = None
234
235
  badge_count: int = 0
235
236
  last_notified_at: datetime | None = None
236
237
 
@@ -291,6 +292,7 @@ class Notification:
291
292
  interval=int(n.get("Interval", 0)),
292
293
  push_state=n.get("PushState", "enabled"),
293
294
  app_version=n.get("AppVersion"),
295
+ profile=n.get("Profile"),
294
296
  badge_count=int(n.get("BadgeCount", 0)),
295
297
  last_notified_at=_parse_dt(n.get("LastNotifiedAt")),
296
298
  _raw=data,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyzm
3
- Version: 2.3.2
3
+ Version: 2.3.4
4
4
  Summary: ZoneMinder API, Logger and other base utilities for python programmers
5
5
  Home-page: https://github.com/pliablepixels/pyzm
6
6
  Author: Pliable Pixels
@@ -65,11 +65,11 @@ Requires-Dist: st-clickable-images>=0.0.3; extra == "full"
65
65
 
66
66
  What
67
67
  =====
68
- pyzmv2 is a rewrite of pyzm.
68
+ pyzmNg is a rewrite of pyzm.
69
69
 
70
70
  It's a pythonic wrapper that integrates with ZM and also operates as a standalone ML library. Key features:
71
71
  - ZM API
72
- - ZM Event Server
72
+ - ZM EventServerNg
73
73
  - ZM Logger
74
74
  - ZM Memory
75
75
  - Machine Learning Modules (with our without ZM)
@@ -1557,6 +1557,64 @@ class TestApplyFilters:
1557
1557
  filtered, errors = det._apply_filters(dets, zones=None, image_shape=(480, 640))
1558
1558
  assert len(filtered) == 2
1559
1559
 
1560
+ def test_zone_rescaled_with_original_shape(self):
1561
+ """Zone polygons should be rescaled when original_shape differs from image_shape."""
1562
+ from pyzm.ml.detector import Detector
1563
+ from pyzm.models.zm import Zone
1564
+ det = Detector.__new__(Detector)
1565
+ det._config = DetectorConfig(pattern=".*")
1566
+ det._pipeline = None
1567
+ # Zone covers a small region in 800x480 (original) coordinates
1568
+ # After 0.5x resize to 400x240, detection bbox at (10,10)-(90,90) in resized space
1569
+ # Zone must be rescaled to (50,100)-(200,200) in resized space to contain bbox
1570
+ zones = [Zone(name="z", points=[(0, 0), (200, 0), (200, 200), (0, 200)])]
1571
+ dets = [
1572
+ Detection(label="person", confidence=0.9, bbox=BBox(10, 10, 90, 90), model_name="test"),
1573
+ ]
1574
+ # With original_shape: zones get rescaled from 800x480 to 400x240
1575
+ filtered, errors = det._apply_filters(
1576
+ dets, zones=zones, image_shape=(240, 400), original_shape=(480, 800),
1577
+ )
1578
+ assert len(filtered) == 1
1579
+ assert filtered[0].label == "person"
1580
+
1581
+ def test_zone_not_rescaled_without_original_shape(self):
1582
+ """Without original_shape, zone polygons pass through unchanged."""
1583
+ from pyzm.ml.detector import Detector
1584
+ from pyzm.models.zm import Zone
1585
+ det = Detector.__new__(Detector)
1586
+ det._config = DetectorConfig(pattern=".*")
1587
+ det._pipeline = None
1588
+ zones = [Zone(name="z", points=[(0, 0), (50, 0), (50, 50), (0, 50)])]
1589
+ dets = [
1590
+ Detection(label="person", confidence=0.9, bbox=BBox(10, 10, 40, 40), model_name="test"),
1591
+ ]
1592
+ filtered, errors = det._apply_filters(dets, zones=zones, image_shape=(480, 640))
1593
+ assert len(filtered) == 1
1594
+
1595
+ def test_zone_mismatch_without_rescaling(self):
1596
+ """Without original_shape, large original-coord zones won't match small-image detections."""
1597
+ from pyzm.ml.detector import Detector
1598
+ from pyzm.models.zm import Zone
1599
+ det = Detector.__new__(Detector)
1600
+ det._config = DetectorConfig(pattern=".*")
1601
+ det._pipeline = None
1602
+ # Zone is in original coords (600-800 range), but image is only 400 wide
1603
+ # Detection at (10,10)-(90,90) in resized space won't intersect
1604
+ zones = [Zone(name="z", points=[(600, 300), (800, 300), (800, 480), (600, 480)])]
1605
+ dets = [
1606
+ Detection(label="person", confidence=0.9, bbox=BBox(10, 10, 90, 90), model_name="test"),
1607
+ ]
1608
+ # Without original_shape: zone coords stay in 800x480 space, won't intersect detection
1609
+ filtered, errors = det._apply_filters(dets, zones=zones, image_shape=(240, 400))
1610
+ assert len(filtered) == 0
1611
+
1612
+ # With original_shape: zone rescaled to (300,150)-(400,240), still won't intersect (10,10)-(90,90)
1613
+ filtered2, _ = det._apply_filters(
1614
+ dets, zones=zones, image_shape=(240, 400), original_shape=(480, 800),
1615
+ )
1616
+ assert len(filtered2) == 0
1617
+
1560
1618
 
1561
1619
  class TestRemoteDetectFiltering:
1562
1620
  """Verify that _remote_detect applies client-side filters."""
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes