viso-sdk-python 1.1.21__tar.gz → 1.1.22__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 (145) hide show
  1. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/CHANGELOG.md +5 -0
  2. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/PKG-INFO +1 -1
  3. viso_sdk_python-1.1.22/tests/unit/test_log_id.py +184 -0
  4. viso_sdk_python-1.1.22/tests/unit/test_polygon_roi_manager.py +314 -0
  5. viso_sdk_python-1.1.22/tests/unit/test_rollingq.py +280 -0
  6. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/_version.c +9 -9
  7. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/_version.py +1 -1
  8. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/roi/polygon_roi_manager.c +139 -203
  9. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/roi/polygon_roi_manager.py +1 -3
  10. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/structures/rollingq.c +322 -2348
  11. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/structures/rollingq.py +6 -3
  12. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk_python.egg-info/PKG-INFO +1 -1
  13. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk_python.egg-info/SOURCES.txt +3 -0
  14. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/AUTHORS +0 -0
  15. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/COPYING +0 -0
  16. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/MANIFEST.in +0 -0
  17. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/README.md +0 -0
  18. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/Makefile +0 -0
  19. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/__init__.py +0 -0
  20. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/conf.py +0 -0
  21. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/ext/__init__.py +0 -0
  22. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/ext/docstrings.py +0 -0
  23. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/ext/manager_tmpl.j2 +0 -0
  24. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/index.rst +0 -0
  25. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/make.bat +0 -0
  26. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/modules.rst +0 -0
  27. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk._version.rst +0 -0
  28. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.constants.constants.rst +0 -0
  29. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.constants.modules.rst +0 -0
  30. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.constants.rst +0 -0
  31. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.constants.variables.rst +0 -0
  32. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.edge.common.rst +0 -0
  33. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.edge.rst +0 -0
  34. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.logging.logger.rst +0 -0
  35. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.logging.rst +0 -0
  36. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.model.download.rst +0 -0
  37. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.model.rst +0 -0
  38. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.mqtt.mqtt_wrapper.rst +0 -0
  39. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.mqtt.rst +0 -0
  40. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.mqtt.utils.rst +0 -0
  41. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.nodered.flow.rst +0 -0
  42. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.nodered.rst +0 -0
  43. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.redis.redis_wrapper.rst +0 -0
  44. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.redis.rst +0 -0
  45. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.rst +0 -0
  46. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.status.rst +0 -0
  47. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/docs/source/viso_sdk.status.status_logger.rst +0 -0
  48. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/pyproject.toml +0 -0
  49. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/requirements-dev.txt +0 -0
  50. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/requirements-docker.txt +0 -0
  51. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/requirements-docs.txt +0 -0
  52. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/requirements-lint.txt +0 -0
  53. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/requirements-test.txt +0 -0
  54. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/requirements.txt +0 -0
  55. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/setup.cfg +0 -0
  56. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/setup.py +0 -0
  57. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/__init__.py +0 -0
  58. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/conftest.py +0 -0
  59. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/fixtures/flow.json +0 -0
  60. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/integration/__init__.py +0 -0
  61. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/integration/test_status.py +0 -0
  62. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/meta/__init__.py +0 -0
  63. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/meta/test_ensure_type_hints.py +0 -0
  64. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/smoke/__init__.py +0 -0
  65. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/smoke/test_dists.py +0 -0
  66. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/unit/__init__.py +0 -0
  67. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/unit/test_flow.py +0 -0
  68. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/unit/test_logger.py +0 -0
  69. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/unit/test_model_fetch.py +0 -0
  70. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/unit/test_nms.py +0 -0
  71. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/unit/test_object.py +0 -0
  72. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/unit/test_redis_utils.py +0 -0
  73. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/unit/test_redis_wrapper.py +0 -0
  74. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/unit/test_stream_cleanup.py +0 -0
  75. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/unit/test_trace_malloc.py +0 -0
  76. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tests/unit/test_viso_data.py +0 -0
  77. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/tox.ini +0 -0
  78. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/__init__.c +0 -0
  79. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/__init__.py +0 -0
  80. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/constants/__init__.c +0 -0
  81. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/constants/__init__.py +0 -0
  82. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/constants/constants.c +0 -0
  83. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/constants/constants.py +0 -0
  84. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/constants/modules.c +0 -0
  85. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/constants/modules.py +0 -0
  86. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/constants/variables.c +0 -0
  87. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/constants/variables.py +0 -0
  88. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/debug/__init__.c +0 -0
  89. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/debug/__init__.py +0 -0
  90. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/debug/trace_malloc.c +0 -0
  91. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/debug/trace_malloc.py +0 -0
  92. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/detection/__init__.c +0 -0
  93. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/detection/__init__.py +0 -0
  94. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/detection/model_utils.c +0 -0
  95. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/detection/model_utils.py +0 -0
  96. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/detection/nms.c +0 -0
  97. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/detection/nms.py +0 -0
  98. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/detection/object.c +0 -0
  99. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/detection/object.py +0 -0
  100. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/edge/__init__.c +0 -0
  101. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/edge/__init__.py +0 -0
  102. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/edge/common.c +0 -0
  103. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/edge/common.py +0 -0
  104. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/logging/__init__.c +0 -0
  105. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/logging/__init__.py +0 -0
  106. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/logging/log_id.c +0 -0
  107. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/logging/log_id.py +0 -0
  108. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/logging/logger.c +0 -0
  109. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/logging/logger.py +0 -0
  110. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/model/__init__.c +0 -0
  111. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/model/__init__.py +0 -0
  112. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/model/fetch.c +0 -0
  113. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/model/fetch.py +0 -0
  114. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/mqtt/__init__.c +0 -0
  115. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/mqtt/__init__.py +0 -0
  116. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/mqtt/mqtt_wrapper.c +0 -0
  117. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/mqtt/mqtt_wrapper.py +0 -0
  118. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/mqtt/utils.c +0 -0
  119. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/mqtt/utils.py +0 -0
  120. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/nodered/__init__.c +0 -0
  121. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/nodered/__init__.py +0 -0
  122. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/nodered/flow.c +0 -0
  123. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/nodered/flow.py +0 -0
  124. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/py.typed +0 -0
  125. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/redis/__init__.c +0 -0
  126. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/redis/__init__.py +0 -0
  127. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/redis/redis_wrapper.c +0 -0
  128. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/redis/redis_wrapper.py +0 -0
  129. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/redis/stream_cleanup.c +0 -0
  130. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/redis/stream_cleanup.py +0 -0
  131. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/redis/utils.c +0 -0
  132. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/redis/utils.py +0 -0
  133. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/redis/viso_data.c +0 -0
  134. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/redis/viso_data.py +0 -0
  135. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/roi/__init__.c +0 -0
  136. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/roi/__init__.py +0 -0
  137. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/status/__init__.c +0 -0
  138. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/status/__init__.py +0 -0
  139. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/status/status_logger.c +0 -0
  140. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/status/status_logger.py +0 -0
  141. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/structures/__init__.c +0 -0
  142. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk/structures/__init__.py +0 -0
  143. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk_python.egg-info/dependency_links.txt +0 -0
  144. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk_python.egg-info/requires.txt +0 -0
  145. {viso_sdk_python-1.1.21 → viso_sdk_python-1.1.22}/viso_sdk_python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,11 @@
1
1
  # Changelog
2
2
 
3
3
  <!--next-version-placeholder-->
4
+ ## v1.1.22
5
+ ### Fix
6
+ - fix rolling queue issue and ROI manager issue
7
+ - add test scripts
8
+
4
9
  ## v1.1.21
5
10
  ### Fix
6
11
  - redownload model issue
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: viso-sdk-python
3
- Version: 1.1.21
3
+ Version: 1.1.22
4
4
  Summary: VisoSDK: A Python SDK for use in Viso containers
5
5
  Home-page: https://gitlab.com/TopKamera/03_edge/Base/viso-sdk-python-public.git
6
6
  Author: support@viso.ai
@@ -0,0 +1,184 @@
1
+ """Unit tests for LogIDGenerator."""
2
+ import re
3
+ import threading
4
+
5
+ import pytest
6
+
7
+ from viso_sdk.logging.log_id import ErrorType, EventType, LogIDGenerator
8
+
9
+
10
+ NODE_ID = "abcdef1234567890"
11
+ SOURCE_IDX = 2
12
+
13
+
14
+ @pytest.fixture
15
+ def gen():
16
+ return LogIDGenerator(node_id=NODE_ID, source_idx=SOURCE_IDX)
17
+
18
+
19
+ # ---------------------------------------------------------------------------
20
+ # Session ID
21
+ # ---------------------------------------------------------------------------
22
+
23
+ class TestSessionId:
24
+ def test_format(self, gen):
25
+ sid = gen.generate_session_id()
26
+ # SES-<node_id[:8]>-<source_idx>-<8-char uuid>
27
+ assert re.fullmatch(r"SES-[a-z0-9]{8}-\d+-[a-f0-9\-]{8}", sid), sid
28
+
29
+ def test_node_id_truncated_to_8(self, gen):
30
+ sid = gen.generate_session_id()
31
+ parts = sid.split("-")
32
+ assert parts[1] == NODE_ID[:8]
33
+
34
+ def test_source_idx_embedded(self, gen):
35
+ sid = gen.generate_session_id()
36
+ assert f"-{SOURCE_IDX}-" in sid
37
+
38
+ def test_cached_after_first_call(self, gen):
39
+ sid1 = gen.generate_session_id()
40
+ sid2 = gen.generate_session_id()
41
+ assert sid1 == sid2
42
+
43
+ def test_get_session_id_none_before_generate(self, gen):
44
+ assert gen.get_session_id() is None
45
+
46
+ def test_get_session_id_after_generate(self, gen):
47
+ sid = gen.generate_session_id()
48
+ assert gen.get_session_id() == sid
49
+
50
+ def test_force_regenerates(self, gen):
51
+ sid1 = gen.generate_session_id()
52
+ sid2 = gen.generate_session_id(force=True)
53
+ assert sid1 != sid2
54
+
55
+ def test_force_updates_cached_value(self, gen):
56
+ gen.generate_session_id()
57
+ new_sid = gen.generate_session_id(force=True)
58
+ assert gen.get_session_id() == new_sid
59
+
60
+
61
+ # ---------------------------------------------------------------------------
62
+ # Error ID
63
+ # ---------------------------------------------------------------------------
64
+
65
+ class TestErrorId:
66
+ def test_format_default_type(self, gen):
67
+ eid = gen.generate_error_id()
68
+ assert re.fullmatch(r"ERR-GEN-[a-z0-9]{8}-\d+-\d+", eid), eid
69
+
70
+ def test_format_custom_type(self, gen):
71
+ eid = gen.generate_error_id(ErrorType.FRAME_CAPTURE)
72
+ assert eid.startswith("ERR-FRAME-")
73
+
74
+ def test_counter_increments(self, gen):
75
+ ids = [gen.generate_error_id() for _ in range(5)]
76
+ counters = [int(i.rsplit("-", 1)[-1]) for i in ids]
77
+ assert counters == list(range(1, 6))
78
+
79
+ def test_counter_independent_from_event_counter(self, gen):
80
+ gen.generate_event_id()
81
+ gen.generate_event_id()
82
+ eid = gen.generate_error_id()
83
+ assert eid.endswith("-1")
84
+
85
+ def test_thread_safety(self, gen):
86
+ results = []
87
+ lock = threading.Lock()
88
+
89
+ def worker():
90
+ eid = gen.generate_error_id()
91
+ with lock:
92
+ results.append(eid)
93
+
94
+ threads = [threading.Thread(target=worker) for _ in range(50)]
95
+ for t in threads:
96
+ t.start()
97
+ for t in threads:
98
+ t.join()
99
+
100
+ counters = sorted(int(i.rsplit("-", 1)[-1]) for i in results)
101
+ assert counters == list(range(1, 51)), "Duplicate or missing counter values"
102
+
103
+
104
+ # ---------------------------------------------------------------------------
105
+ # Event ID
106
+ # ---------------------------------------------------------------------------
107
+
108
+ class TestEventId:
109
+ def test_format_default_type(self, gen):
110
+ eid = gen.generate_event_id()
111
+ assert re.fullmatch(r"EVT-EVT-[a-z0-9]{8}-\d+-\d+", eid), eid
112
+
113
+ def test_format_custom_type(self, gen):
114
+ eid = gen.generate_event_id(EventType.START)
115
+ assert eid.startswith("EVT-START-")
116
+
117
+ def test_counter_increments(self, gen):
118
+ ids = [gen.generate_event_id() for _ in range(5)]
119
+ counters = [int(i.rsplit("-", 1)[-1]) for i in ids]
120
+ assert counters == list(range(1, 6))
121
+
122
+ def test_counter_independent_from_error_counter(self, gen):
123
+ gen.generate_error_id()
124
+ gen.generate_error_id()
125
+ evid = gen.generate_event_id()
126
+ assert evid.endswith("-1")
127
+
128
+ def test_thread_safety(self, gen):
129
+ results = []
130
+ lock = threading.Lock()
131
+
132
+ def worker():
133
+ evid = gen.generate_event_id()
134
+ with lock:
135
+ results.append(evid)
136
+
137
+ threads = [threading.Thread(target=worker) for _ in range(50)]
138
+ for t in threads:
139
+ t.start()
140
+ for t in threads:
141
+ t.join()
142
+
143
+ counters = sorted(int(i.rsplit("-", 1)[-1]) for i in results)
144
+ assert counters == list(range(1, 51)), "Duplicate or missing counter values"
145
+
146
+
147
+ # ---------------------------------------------------------------------------
148
+ # Message formatting
149
+ # ---------------------------------------------------------------------------
150
+
151
+ class TestFormatting:
152
+ def test_format_log_with_id(self):
153
+ msg = LogIDGenerator.format_log_with_id("hello", "ERR-GEN-abc-0-1")
154
+ assert msg == "[ERR-GEN-abc-0-1] hello"
155
+
156
+ def test_format_log_with_session_before_generate(self, gen):
157
+ assert gen.format_log_with_session("hello") == "hello"
158
+
159
+ def test_format_log_with_session_after_generate(self, gen):
160
+ sid = gen.generate_session_id()
161
+ msg = gen.format_log_with_session("hello")
162
+ assert msg == f"[{sid}] hello"
163
+
164
+ def test_format_log_with_session_updates_after_force(self, gen):
165
+ gen.generate_session_id()
166
+ new_sid = gen.generate_session_id(force=True)
167
+ msg = gen.format_log_with_session("hello")
168
+ assert msg == f"[{new_sid}] hello"
169
+
170
+
171
+ # ---------------------------------------------------------------------------
172
+ # Short node_id (< 8 chars)
173
+ # ---------------------------------------------------------------------------
174
+
175
+ class TestShortNodeId:
176
+ def test_short_node_id_does_not_raise(self):
177
+ gen = LogIDGenerator(node_id="abc", source_idx=0)
178
+ sid = gen.generate_session_id()
179
+ assert "abc" in sid
180
+
181
+ def test_error_id_with_short_node_id(self):
182
+ gen = LogIDGenerator(node_id="xyz", source_idx=1)
183
+ eid = gen.generate_error_id()
184
+ assert "xyz" in eid
@@ -0,0 +1,314 @@
1
+ """Unit tests for PolygonROIManager."""
2
+ from viso_sdk.roi.polygon_roi_manager import PolygonROIManager, RoiData
3
+
4
+
5
+ # ---------------------------------------------------------------------------
6
+ # Helpers
7
+ # ---------------------------------------------------------------------------
8
+
9
+ class MockDet:
10
+ """Detection-like item (no .info attribute)."""
11
+ def __init__(self, tlwh):
12
+ self.tlwh = tlwh
13
+
14
+
15
+ class MockTrack:
16
+ """Track-like item (has .info attribute)."""
17
+ def __init__(self, tlwh, rect=None):
18
+ self.tlwh = tlwh
19
+ self.info = {'rect': rect if rect is not None else tlwh}
20
+
21
+
22
+ def _make_roi(label, roi_id, include, points):
23
+ return {
24
+ RoiData.ROI_LABEL_KEY: label,
25
+ RoiData.ROI_ID_KEY: roi_id,
26
+ RoiData.INCLUDE_KEY: include,
27
+ RoiData.POINTS_KEY: points,
28
+ }
29
+
30
+
31
+ # Unit square ROI: (0.2,0.2)→(0.8,0.8) in normalised coords
32
+ SQUARE = [[0.2, 0.2], [0.8, 0.2], [0.8, 0.8], [0.2, 0.8]]
33
+ # Inner exclusion zone: (0.4,0.4)→(0.6,0.6)
34
+ INNER_EXCL = [[0.4, 0.4], [0.6, 0.4], [0.6, 0.6], [0.4, 0.6]]
35
+ # Second non-overlapping ROI: (0.85,0.85)→(0.99,0.99)
36
+ SQUARE2 = [[0.85, 0.85], [0.99, 0.85], [0.99, 0.99], [0.85, 0.99]]
37
+
38
+ # Reference anchor = (0.5, 0.5) → ref point = centre of tlwh
39
+ # tlwh = (x, y, w, h); centre = (x + w/2, y + h/2)
40
+ INSIDE = MockDet((0.45, 0.45, 0.10, 0.10)) # centre (0.50, 0.50) – inside SQUARE
41
+ OUTSIDE = MockDet((0.00, 0.00, 0.10, 0.10)) # centre (0.05, 0.05) – outside SQUARE
42
+ IN_EXCL = MockDet((0.45, 0.45, 0.06, 0.06)) # centre (0.48, 0.48) – in SQUARE, inside INNER_EXCL
43
+ IN_SQ2 = MockDet((0.88, 0.88, 0.06, 0.06)) # centre (0.91, 0.91) – inside SQUARE2
44
+
45
+
46
+ # ---------------------------------------------------------------------------
47
+ # No ROI configured
48
+ # ---------------------------------------------------------------------------
49
+
50
+ class TestNoRoi:
51
+ def test_none_polygons_returns_input_unchanged(self):
52
+ """When no ROI is configured, all items pass through as-is."""
53
+ mgr = PolygonROIManager(polygons=None)
54
+ items = [INSIDE, OUTSIDE]
55
+ assert mgr.filter_in_roi(items) is items
56
+
57
+ def test_empty_detections_no_roi(self):
58
+ mgr = PolygonROIManager(polygons=None)
59
+ assert mgr.filter_in_roi([]) == []
60
+
61
+
62
+ # ---------------------------------------------------------------------------
63
+ # All ROIs have empty points (the bug case)
64
+ # ---------------------------------------------------------------------------
65
+
66
+ class TestAllEmptyPoints:
67
+ def test_no_roi_name_when_all_points_empty(self):
68
+ """Detections must NOT receive roi_id/roi_name when no polygon is drawn."""
69
+ polygons = [
70
+ _make_roi("roi1", "id1", True, []),
71
+ _make_roi("roi2", "id2", True, []),
72
+ ]
73
+ mgr = PolygonROIManager(polygons=polygons)
74
+ result = mgr.filter_in_roi([INSIDE, OUTSIDE])
75
+
76
+ assert len(result) == 2
77
+ for item in result:
78
+ assert not hasattr(item, 'roi_id'), "roi_id must not be set"
79
+ assert not hasattr(item, 'roi_name'), "roi_name must not be set"
80
+
81
+ def test_all_items_pass_through_when_all_points_empty(self):
82
+ """All items are returned when polygon is empty (pass-through)."""
83
+ polygons = [_make_roi("roi1", "id1", True, [])]
84
+ mgr = PolygonROIManager(polygons=polygons)
85
+ result = mgr.filter_in_roi([INSIDE, OUTSIDE])
86
+ assert len(result) == 2
87
+
88
+
89
+ # ---------------------------------------------------------------------------
90
+ # Single include polygon
91
+ # ---------------------------------------------------------------------------
92
+
93
+ class TestSingleIncludePolygon:
94
+ def setup_method(self):
95
+ self.mgr = PolygonROIManager(
96
+ polygons=[_make_roi("zone1", "abc123", True, SQUARE)]
97
+ )
98
+
99
+ def test_item_inside_gets_roi_info(self):
100
+ result = self.mgr.filter_in_roi([INSIDE])
101
+ assert len(result) == 1
102
+ assert result[0].roi_id == ["abc123"]
103
+ assert result[0].roi_name == ["zone1"]
104
+
105
+ def test_item_outside_is_filtered_out(self):
106
+ result = self.mgr.filter_in_roi([OUTSIDE])
107
+ assert result == []
108
+
109
+ def test_mixed_items(self):
110
+ result = self.mgr.filter_in_roi([INSIDE, OUTSIDE])
111
+ assert len(result) == 1
112
+ assert result[0].roi_id == ["abc123"]
113
+
114
+ def test_empty_detection_list(self):
115
+ assert self.mgr.filter_in_roi([]) == []
116
+
117
+
118
+ # ---------------------------------------------------------------------------
119
+ # Exclude polygon
120
+ # ---------------------------------------------------------------------------
121
+
122
+ class TestExcludePolygon:
123
+ def setup_method(self):
124
+ self.mgr = PolygonROIManager(polygons=[
125
+ _make_roi("zone1", "abc123", True, SQUARE),
126
+ _make_roi("excl1", "excl", False, INNER_EXCL),
127
+ ])
128
+
129
+ def test_item_inside_include_outside_exclude_passes(self):
130
+ """Point inside SQUARE but outside INNER_EXCL → kept."""
131
+ # centre (0.30, 0.30): inside SQUARE (0.2–0.8), outside INNER_EXCL (0.4–0.6)
132
+ item = MockDet((0.25, 0.25, 0.10, 0.10))
133
+ result = self.mgr.filter_in_roi([item])
134
+ assert len(result) == 1
135
+ assert result[0].roi_id == ["abc123"]
136
+
137
+ def test_item_inside_exclude_is_filtered(self):
138
+ """Point inside both SQUARE and INNER_EXCL → excluded."""
139
+ result = self.mgr.filter_in_roi([IN_EXCL])
140
+ assert result == []
141
+
142
+ def test_item_outside_both_is_filtered(self):
143
+ result = self.mgr.filter_in_roi([OUTSIDE])
144
+ assert result == []
145
+
146
+
147
+ # ---------------------------------------------------------------------------
148
+ # Multiple include polygons
149
+ # ---------------------------------------------------------------------------
150
+
151
+ class TestMultipleIncludePolygons:
152
+ def setup_method(self):
153
+ self.mgr = PolygonROIManager(polygons=[
154
+ _make_roi("zone1", "id1", True, SQUARE),
155
+ _make_roi("zone2", "id2", True, SQUARE2),
156
+ ])
157
+
158
+ def test_item_in_first_polygon_only(self):
159
+ result = self.mgr.filter_in_roi([INSIDE])
160
+ assert len(result) == 1
161
+ assert result[0].roi_id == ["id1"]
162
+ assert result[0].roi_name == ["zone1"]
163
+
164
+ def test_item_in_second_polygon_only(self):
165
+ result = self.mgr.filter_in_roi([IN_SQ2])
166
+ assert len(result) == 1
167
+ assert result[0].roi_id == ["id2"]
168
+ assert result[0].roi_name == ["zone2"]
169
+
170
+ def test_item_in_neither_polygon_filtered(self):
171
+ result = self.mgr.filter_in_roi([OUTSIDE])
172
+ assert result == []
173
+
174
+ def test_item_matched_to_both_polygons(self):
175
+ """A point inside an overlapping region of two included polygons gets both ids."""
176
+ # Create two overlapping ROIs so a single point is in both
177
+ overlap_sq1 = [[0.1, 0.1], [0.6, 0.1], [0.6, 0.6], [0.1, 0.6]]
178
+ overlap_sq2 = [[0.4, 0.4], [0.9, 0.4], [0.9, 0.9], [0.4, 0.9]]
179
+ mgr = PolygonROIManager(polygons=[
180
+ _make_roi("z1", "r1", True, overlap_sq1),
181
+ _make_roi("z2", "r2", True, overlap_sq2),
182
+ ])
183
+ # centre (0.5, 0.5) is in both
184
+ item = MockDet((0.45, 0.45, 0.10, 0.10))
185
+ result = mgr.filter_in_roi([item])
186
+ assert len(result) == 1
187
+ assert set(result[0].roi_id) == {"r1", "r2"}
188
+ assert set(result[0].roi_name) == {"z1", "z2"}
189
+
190
+
191
+ # ---------------------------------------------------------------------------
192
+ # Empty polygon acting as pass-through alongside real polygon
193
+ # ---------------------------------------------------------------------------
194
+
195
+ class TestEmptyPolygonWithRealPolygon:
196
+ def setup_method(self):
197
+ self.mgr = PolygonROIManager(polygons=[
198
+ _make_roi("zone1", "id1", True, SQUARE),
199
+ _make_roi("everywhere", "idE", True, []),
200
+ ])
201
+
202
+ def test_item_inside_real_polygon_gets_roi_info(self):
203
+ result = self.mgr.filter_in_roi([INSIDE])
204
+ assert len(result) == 1
205
+ assert result[0].roi_id == ["id1"]
206
+
207
+ def test_item_outside_real_polygon_passes_without_roi_info(self):
208
+ """Item not in any real polygon falls through to empty_polygon → no roi info."""
209
+ result = self.mgr.filter_in_roi([OUTSIDE])
210
+ assert len(result) == 1
211
+ assert not hasattr(result[0], 'roi_id')
212
+ assert not hasattr(result[0], 'roi_name')
213
+
214
+
215
+ # ---------------------------------------------------------------------------
216
+ # Track items (have .info attribute)
217
+ # ---------------------------------------------------------------------------
218
+
219
+ class TestTrackItems:
220
+ def setup_method(self):
221
+ self.mgr = PolygonROIManager(
222
+ polygons=[_make_roi("zone1", "abc123", True, SQUARE)]
223
+ )
224
+
225
+ def test_track_inside_gets_roi_info_in_info_dict(self):
226
+ track = MockTrack(tlwh=(0.45, 0.45, 0.10, 0.10))
227
+ result = self.mgr.filter_in_roi([track])
228
+ assert len(result) == 1
229
+ assert result[0].info['roi_id'] == ["abc123"]
230
+ assert result[0].info['roi_name'] == ["zone1"]
231
+
232
+ def test_track_outside_is_filtered(self):
233
+ track = MockTrack(tlwh=(0.0, 0.0, 0.1, 0.1))
234
+ result = self.mgr.filter_in_roi([track])
235
+ assert result == []
236
+
237
+ def test_track_uses_rect_from_info_when_present(self):
238
+ """Track uses info['rect'] for the bounding box if available."""
239
+ track = MockTrack(
240
+ tlwh=(0.0, 0.0, 0.1, 0.1), # would be outside
241
+ rect=(0.45, 0.45, 0.10, 0.10), # centre (0.5, 0.5) – inside
242
+ )
243
+ result = self.mgr.filter_in_roi([track])
244
+ assert len(result) == 1
245
+
246
+
247
+ # ---------------------------------------------------------------------------
248
+ # Pixel coordinates (width > 1.0 triggers normalisation)
249
+ # ---------------------------------------------------------------------------
250
+
251
+ class TestPixelCoordinates:
252
+ def test_pixel_coords_normalised_correctly(self):
253
+ """Items with pixel-space tlwh are divided by frame size before ROI check."""
254
+ frame_sz = [100, 100]
255
+ # SQUARE covers normalised (0.2,0.2)→(0.8,0.8)
256
+ mgr = PolygonROIManager(
257
+ polygons=[_make_roi("zone1", "id1", True, SQUARE)],
258
+ frame_sz=frame_sz,
259
+ )
260
+ # Pixel rect (45, 45, 10, 10) → centre (50, 50) → normalised (0.5, 0.5) = inside
261
+ item_in = MockDet((45.0, 45.0, 10.0, 10.0))
262
+ # Pixel rect (0, 0, 10, 10) → centre (5, 5) → normalised (0.05, 0.05) = outside
263
+ item_out = MockDet((0.0, 0.0, 10.0, 10.0))
264
+
265
+ result = mgr.filter_in_roi([item_in, item_out], frame_sz=frame_sz)
266
+ assert len(result) == 1
267
+ assert result[0].roi_id == ["id1"]
268
+
269
+
270
+ # ---------------------------------------------------------------------------
271
+ # Custom anchor point
272
+ # ---------------------------------------------------------------------------
273
+
274
+ class TestCustomAnchorPoint:
275
+ def setup_method(self):
276
+ self.mgr = PolygonROIManager(
277
+ polygons=[_make_roi("zone1", "id1", True, SQUARE)]
278
+ )
279
+
280
+ def test_top_left_anchor(self):
281
+ """With anchor (0, 0) the top-left corner is used as the reference point."""
282
+ # tlwh (0.25, 0.25, 0.1, 0.1) → top-left = (0.25, 0.25) → inside SQUARE
283
+ item = MockDet((0.25, 0.25, 0.10, 0.10))
284
+ result = self.mgr.filter_in_roi([item], anchor_pt=(0.0, 0.0))
285
+ assert len(result) == 1
286
+
287
+ def test_bottom_right_anchor(self):
288
+ """With anchor (1, 1) the bottom-right corner drives the check."""
289
+ # tlwh (0.7, 0.7, 0.15, 0.15) → bottom-right = (0.85, 0.85) → outside SQUARE
290
+ item = MockDet((0.7, 0.7, 0.15, 0.15))
291
+ result = self.mgr.filter_in_roi([item], anchor_pt=(1.0, 1.0))
292
+ assert result == []
293
+
294
+ def test_bottom_right_anchor_inside(self):
295
+ # tlwh (0.1, 0.1, 0.5, 0.5) → bottom-right = (0.6, 0.6) → inside SQUARE
296
+ item = MockDet((0.1, 0.1, 0.50, 0.50))
297
+ result = self.mgr.filter_in_roi([item], anchor_pt=(1.0, 1.0))
298
+ assert len(result) == 1
299
+
300
+
301
+ # ---------------------------------------------------------------------------
302
+ # Original item not mutated
303
+ # ---------------------------------------------------------------------------
304
+
305
+ class TestImmutability:
306
+ def test_original_item_not_mutated(self):
307
+ """filter_in_roi must not modify the original item object."""
308
+ mgr = PolygonROIManager(
309
+ polygons=[_make_roi("zone1", "id1", True, SQUARE)]
310
+ )
311
+ item = MockDet((0.45, 0.45, 0.10, 0.10))
312
+ mgr.filter_in_roi([item])
313
+ assert not hasattr(item, 'roi_id')
314
+ assert not hasattr(item, 'roi_name')