viso-sdk-python 1.1.17__tar.gz → 1.1.19__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 (146) hide show
  1. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/CHANGELOG.md +8 -0
  2. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/PKG-INFO +1 -1
  3. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tests/unit/test_model_fetch.py +0 -1
  4. viso_sdk_python-1.1.19/tests/unit/test_nms.py +231 -0
  5. viso_sdk_python-1.1.19/tests/unit/test_object.py +189 -0
  6. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tests/unit/test_stream_cleanup.py +1 -3
  7. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tests/unit/test_trace_malloc.py +0 -1
  8. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tests/unit/test_viso_data.py +23 -6
  9. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/_version.c +7 -7
  10. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/_version.py +1 -1
  11. viso_sdk_python-1.1.19/viso_sdk/detection/nms.c +12493 -0
  12. viso_sdk_python-1.1.19/viso_sdk/detection/nms.py +114 -0
  13. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/detection/object.c +40 -40
  14. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/detection/object.py +1 -1
  15. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/redis/viso_data.c +72 -46
  16. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/redis/viso_data.py +1 -1
  17. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk_python.egg-info/PKG-INFO +1 -1
  18. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk_python.egg-info/SOURCES.txt +4 -4
  19. viso_sdk_python-1.1.17/viso_sdk/detection/utils/__init__.c +0 -4589
  20. viso_sdk_python-1.1.17/viso_sdk/detection/utils/nms.c +0 -13329
  21. viso_sdk_python-1.1.17/viso_sdk/detection/utils/nms.py +0 -272
  22. viso_sdk_python-1.1.17/viso_sdk/model/__init__.py +0 -0
  23. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/AUTHORS +0 -0
  24. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/COPYING +0 -0
  25. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/MANIFEST.in +0 -0
  26. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/README.md +0 -0
  27. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/Makefile +0 -0
  28. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/__init__.py +0 -0
  29. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/conf.py +0 -0
  30. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/ext/__init__.py +0 -0
  31. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/ext/docstrings.py +0 -0
  32. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/ext/manager_tmpl.j2 +0 -0
  33. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/index.rst +0 -0
  34. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/make.bat +0 -0
  35. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/modules.rst +0 -0
  36. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk._version.rst +0 -0
  37. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.constants.constants.rst +0 -0
  38. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.constants.modules.rst +0 -0
  39. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.constants.rst +0 -0
  40. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.constants.variables.rst +0 -0
  41. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.edge.common.rst +0 -0
  42. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.edge.rst +0 -0
  43. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.logging.logger.rst +0 -0
  44. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.logging.rst +0 -0
  45. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.model.download.rst +0 -0
  46. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.model.rst +0 -0
  47. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.mqtt.mqtt_wrapper.rst +0 -0
  48. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.mqtt.rst +0 -0
  49. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.mqtt.utils.rst +0 -0
  50. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.nodered.flow.rst +0 -0
  51. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.nodered.rst +0 -0
  52. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.redis.redis_wrapper.rst +0 -0
  53. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.redis.rst +0 -0
  54. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.rst +0 -0
  55. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.status.rst +0 -0
  56. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/docs/source/viso_sdk.status.status_logger.rst +0 -0
  57. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/pyproject.toml +0 -0
  58. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/requirements-dev.txt +0 -0
  59. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/requirements-docker.txt +0 -0
  60. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/requirements-docs.txt +0 -0
  61. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/requirements-lint.txt +0 -0
  62. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/requirements-test.txt +0 -0
  63. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/requirements.txt +0 -0
  64. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/setup.cfg +0 -0
  65. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/setup.py +0 -0
  66. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tests/__init__.py +0 -0
  67. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tests/conftest.py +0 -0
  68. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tests/fixtures/flow.json +0 -0
  69. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tests/integration/__init__.py +0 -0
  70. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tests/integration/test_status.py +0 -0
  71. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tests/meta/__init__.py +0 -0
  72. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tests/meta/test_ensure_type_hints.py +0 -0
  73. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tests/smoke/__init__.py +0 -0
  74. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tests/smoke/test_dists.py +0 -0
  75. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tests/unit/__init__.py +0 -0
  76. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tests/unit/test_flow.py +0 -0
  77. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tests/unit/test_logger.py +0 -0
  78. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tests/unit/test_redis_utils.py +0 -0
  79. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tests/unit/test_redis_wrapper.py +0 -0
  80. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/tox.ini +0 -0
  81. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/__init__.c +0 -0
  82. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/__init__.py +0 -0
  83. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/constants/__init__.c +0 -0
  84. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/constants/__init__.py +0 -0
  85. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/constants/constants.c +0 -0
  86. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/constants/constants.py +0 -0
  87. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/constants/modules.c +0 -0
  88. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/constants/modules.py +0 -0
  89. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/constants/variables.c +0 -0
  90. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/constants/variables.py +0 -0
  91. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/debug/__init__.c +0 -0
  92. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/debug/__init__.py +0 -0
  93. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/debug/trace_malloc.c +0 -0
  94. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/debug/trace_malloc.py +0 -0
  95. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/detection/__init__.c +0 -0
  96. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/detection/__init__.py +0 -0
  97. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/detection/model_utils.c +0 -0
  98. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/detection/model_utils.py +0 -0
  99. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/edge/__init__.c +0 -0
  100. {viso_sdk_python-1.1.17/viso_sdk/detection/utils → viso_sdk_python-1.1.19/viso_sdk/edge}/__init__.py +0 -0
  101. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/edge/common.c +0 -0
  102. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/edge/common.py +0 -0
  103. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/logging/__init__.c +0 -0
  104. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/logging/__init__.py +0 -0
  105. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/logging/log_id.c +0 -0
  106. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/logging/log_id.py +0 -0
  107. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/logging/logger.c +0 -0
  108. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/logging/logger.py +0 -0
  109. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/model/__init__.c +0 -0
  110. {viso_sdk_python-1.1.17/viso_sdk/edge → viso_sdk_python-1.1.19/viso_sdk/model}/__init__.py +0 -0
  111. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/model/fetch.c +0 -0
  112. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/model/fetch.py +0 -0
  113. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/mqtt/__init__.c +0 -0
  114. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/mqtt/__init__.py +0 -0
  115. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/mqtt/mqtt_wrapper.c +0 -0
  116. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/mqtt/mqtt_wrapper.py +0 -0
  117. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/mqtt/utils.c +0 -0
  118. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/mqtt/utils.py +0 -0
  119. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/nodered/__init__.c +0 -0
  120. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/nodered/__init__.py +0 -0
  121. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/nodered/flow.c +0 -0
  122. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/nodered/flow.py +0 -0
  123. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/py.typed +0 -0
  124. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/redis/__init__.c +0 -0
  125. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/redis/__init__.py +0 -0
  126. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/redis/redis_wrapper.c +0 -0
  127. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/redis/redis_wrapper.py +0 -0
  128. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/redis/stream_cleanup.c +0 -0
  129. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/redis/stream_cleanup.py +0 -0
  130. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/redis/utils.c +0 -0
  131. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/redis/utils.py +0 -0
  132. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/roi/__init__.c +0 -0
  133. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/roi/__init__.py +0 -0
  134. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/roi/polygon_roi_manager.c +0 -0
  135. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/roi/polygon_roi_manager.py +0 -0
  136. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/status/__init__.c +0 -0
  137. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/status/__init__.py +0 -0
  138. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/status/status_logger.c +0 -0
  139. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/status/status_logger.py +0 -0
  140. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/structures/__init__.c +0 -0
  141. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/structures/__init__.py +0 -0
  142. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/structures/rollingq.c +0 -0
  143. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk/structures/rollingq.py +0 -0
  144. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk_python.egg-info/dependency_links.txt +0 -0
  145. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk_python.egg-info/requires.txt +0 -0
  146. {viso_sdk_python-1.1.17 → viso_sdk_python-1.1.19}/viso_sdk_python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,14 @@
1
1
  # Changelog
2
2
 
3
3
  <!--next-version-placeholder-->
4
+ ## v1.1.19
5
+ ### Fix
6
+ - Fix timestamp error
7
+ - Add missing test files
8
+
9
+ ## v1.1.18
10
+ ### Fix
11
+ missing nms script
4
12
 
5
13
  ## v1.1.17
6
14
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: viso-sdk-python
3
- Version: 1.1.17
3
+ Version: 1.1.19
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
@@ -2,7 +2,6 @@
2
2
  """Tests for viso_sdk.model.fetch"""
3
3
  import io
4
4
  import tarfile
5
- import tempfile
6
5
  from unittest.mock import patch
7
6
 
8
7
  import pytest
@@ -0,0 +1,231 @@
1
+ """Unit tests for NMS (Non-Maximum Suppression) utilities."""
2
+ from viso_sdk.detection.nms import non_max_suppression_cv, non_max_suppression_cv_per_class
3
+
4
+
5
+ class TestNonMaxSuppressionCV:
6
+ """Tests for standard NMS."""
7
+
8
+ def test_empty_input_returns_empty(self):
9
+ """Test that empty boxes list returns empty result."""
10
+ result = non_max_suppression_cv([], [], score_threshold=0.5, iou_threshold=0.5)
11
+ assert result == []
12
+
13
+ def test_single_box_is_kept(self):
14
+ """Test that a single box is always kept."""
15
+ boxes = [[10, 10, 50, 50]]
16
+ scores = [0.9]
17
+ result = non_max_suppression_cv(boxes, scores, score_threshold=0.5, iou_threshold=0.5)
18
+ assert result == [0]
19
+
20
+ def test_non_overlapping_boxes_all_kept(self):
21
+ """Test that non-overlapping boxes are all kept."""
22
+ boxes = [
23
+ [10, 10, 30, 30], # Box 0
24
+ [100, 100, 30, 30], # Box 1 - far away
25
+ [200, 200, 30, 30], # Box 2 - far away
26
+ ]
27
+ scores = [0.9, 0.85, 0.8]
28
+ result = non_max_suppression_cv(boxes, scores, score_threshold=0.5, iou_threshold=0.5)
29
+ assert len(result) == 3
30
+ assert set(result) == {0, 1, 2}
31
+
32
+ def test_overlapping_boxes_highest_score_kept(self):
33
+ """Test that overlapping boxes keep only the highest score."""
34
+ boxes = [
35
+ [10, 10, 50, 50], # Box 0 - score 0.9
36
+ [15, 15, 50, 50], # Box 1 - score 0.8 (overlaps with 0)
37
+ ]
38
+ scores = [0.9, 0.8]
39
+ result = non_max_suppression_cv(boxes, scores, score_threshold=0.5, iou_threshold=0.3)
40
+ assert result == [0] # Box 0 kept, box 1 suppressed
41
+
42
+ def test_score_threshold_filters_low_scores(self):
43
+ """Test that boxes below score_threshold are filtered out."""
44
+ boxes = [
45
+ [10, 10, 50, 50], # High score
46
+ [100, 100, 30, 30], # Low score
47
+ ]
48
+ scores = [0.9, 0.3]
49
+ result = non_max_suppression_cv(boxes, scores, score_threshold=0.5, iou_threshold=0.5)
50
+ assert result == [0]
51
+
52
+ def test_high_iou_threshold_keeps_more_boxes(self):
53
+ """Test that higher IoU threshold allows more overlapping boxes."""
54
+ boxes = [
55
+ [10, 10, 50, 50],
56
+ [20, 20, 50, 50], # Moderate overlap
57
+ ]
58
+ scores = [0.9, 0.85]
59
+
60
+ # With low IoU threshold, should suppress
61
+ result_low = non_max_suppression_cv(boxes, scores, score_threshold=0.5, iou_threshold=0.1)
62
+ assert len(result_low) == 1
63
+
64
+ # With high IoU threshold, should keep both
65
+ result_high = non_max_suppression_cv(boxes, scores, score_threshold=0.5, iou_threshold=0.9)
66
+ assert len(result_high) == 2
67
+
68
+ def test_three_overlapping_boxes_keeps_best(self):
69
+ """Test NMS with three overlapping boxes."""
70
+ boxes = [
71
+ [10, 10, 50, 50], # score 0.95
72
+ [12, 12, 50, 50], # score 0.9 (high overlap with 0)
73
+ [14, 14, 50, 50], # score 0.85 (high overlap with 0 and 1)
74
+ ]
75
+ scores = [0.95, 0.9, 0.85]
76
+ result = non_max_suppression_cv(boxes, scores, score_threshold=0.5, iou_threshold=0.3)
77
+ assert result == [0] # Only highest score kept
78
+
79
+ def test_float_boxes_and_scores(self):
80
+ """Test that float boxes and scores are handled correctly."""
81
+ boxes = [[10.5, 10.5, 50.3, 50.7]]
82
+ scores = [0.934]
83
+ result = non_max_suppression_cv(boxes, scores, score_threshold=0.5, iou_threshold=0.5)
84
+ assert result == [0]
85
+
86
+ def test_mixed_overlapping_and_non_overlapping(self):
87
+ """Test mix of overlapping and non-overlapping boxes."""
88
+ boxes = [
89
+ [10, 10, 50, 50], # Group 1 - score 0.9
90
+ [15, 15, 50, 50], # Group 1 - score 0.8 (overlaps with 0)
91
+ [200, 200, 30, 30], # Group 2 - score 0.95 (isolated)
92
+ ]
93
+ scores = [0.9, 0.8, 0.95]
94
+ result = non_max_suppression_cv(boxes, scores, score_threshold=0.5, iou_threshold=0.3)
95
+ assert len(result) == 2
96
+ assert 2 in result # Isolated box kept
97
+ assert 0 in result # Highest score from group 1 kept
98
+ assert 1 not in result # Suppressed by box 0
99
+
100
+
101
+ class TestNonMaxSuppressionCVPerClass:
102
+ """Tests for per-class NMS."""
103
+
104
+ def test_empty_input_returns_empty(self):
105
+ """Test that empty inputs return empty result."""
106
+ result = non_max_suppression_cv_per_class(
107
+ [], [], [], score_threshold=0.5, iou_threshold=0.5
108
+ )
109
+ assert result == []
110
+
111
+ def test_single_box_is_kept(self):
112
+ """Test that a single box is always kept."""
113
+ boxes = [[10, 10, 50, 50]]
114
+ scores = [0.9]
115
+ class_ids = [1]
116
+ result = non_max_suppression_cv_per_class(
117
+ boxes, scores, class_ids, score_threshold=0.5, iou_threshold=0.5
118
+ )
119
+ assert result == [0]
120
+
121
+ def test_different_classes_overlap_both_kept(self):
122
+ """Test that overlapping boxes of different classes are both kept."""
123
+ boxes = [
124
+ [10, 10, 50, 50], # Class 1
125
+ [15, 15, 50, 50], # Class 2 (overlaps with class 1)
126
+ ]
127
+ scores = [0.9, 0.85]
128
+ class_ids = [1, 2]
129
+ result = non_max_suppression_cv_per_class(
130
+ boxes, scores, class_ids, score_threshold=0.5, iou_threshold=0.3
131
+ )
132
+ assert len(result) == 2
133
+ assert set(result) == {0, 1}
134
+
135
+ def test_same_class_overlap_highest_kept(self):
136
+ """Test that overlapping boxes of same class keep highest score."""
137
+ boxes = [
138
+ [10, 10, 50, 50], # Class 1 - score 0.9
139
+ [15, 15, 50, 50], # Class 1 - score 0.8 (overlaps)
140
+ ]
141
+ scores = [0.9, 0.8]
142
+ class_ids = [1, 1]
143
+ result = non_max_suppression_cv_per_class(
144
+ boxes, scores, class_ids, score_threshold=0.5, iou_threshold=0.3
145
+ )
146
+ assert result == [0]
147
+
148
+ def test_multiple_classes_with_overlap(self):
149
+ """Test complex scenario with multiple classes and overlaps."""
150
+ boxes = [
151
+ [10, 10, 50, 50], # Class 1 - score 0.9
152
+ [15, 15, 50, 50], # Class 1 - score 0.8 (overlaps with 0)
153
+ [12, 12, 48, 48], # Class 2 - score 0.85 (overlaps with 0 and 1)
154
+ [200, 200, 30, 30], # Class 1 - score 0.95 (isolated)
155
+ ]
156
+ scores = [0.9, 0.8, 0.85, 0.95]
157
+ class_ids = [1, 1, 2, 1]
158
+ result = non_max_suppression_cv_per_class(
159
+ boxes, scores, class_ids, score_threshold=0.5, iou_threshold=0.3
160
+ )
161
+
162
+ assert len(result) == 3
163
+ assert 0 in result # Class 1, highest in overlapping group
164
+ assert 2 in result # Class 2, only box
165
+ assert 3 in result # Class 1, isolated
166
+ assert 1 not in result # Class 1, suppressed by box 0
167
+
168
+ def test_string_class_ids(self):
169
+ """Test that string class IDs work correctly."""
170
+ boxes = [
171
+ [10, 10, 50, 50], # Class 'person'
172
+ [15, 15, 50, 50], # Class 'car' (overlaps)
173
+ ]
174
+ scores = [0.9, 0.85]
175
+ class_ids = ['person', 'car']
176
+ result = non_max_suppression_cv_per_class(
177
+ boxes, scores, class_ids, score_threshold=0.5, iou_threshold=0.3
178
+ )
179
+ assert len(result) == 2
180
+ assert set(result) == {0, 1}
181
+
182
+ def test_three_classes_no_suppression(self):
183
+ """Test that different classes at same location are all kept."""
184
+ boxes = [
185
+ [10, 10, 50, 50], # Class 1
186
+ [10, 10, 50, 50], # Class 2 (exact same location)
187
+ [10, 10, 50, 50], # Class 3 (exact same location)
188
+ ]
189
+ scores = [0.9, 0.85, 0.8]
190
+ class_ids = [1, 2, 3]
191
+ result = non_max_suppression_cv_per_class(
192
+ boxes, scores, class_ids, score_threshold=0.5, iou_threshold=0.3
193
+ )
194
+ assert len(result) == 3
195
+ assert set(result) == {0, 1, 2}
196
+
197
+ def test_score_threshold_filters_per_class(self):
198
+ """Test that score threshold is applied per class."""
199
+ boxes = [
200
+ [10, 10, 50, 50], # Class 1 - high score
201
+ [100, 100, 30, 30], # Class 1 - low score
202
+ [200, 200, 30, 30], # Class 2 - low score
203
+ ]
204
+ scores = [0.9, 0.3, 0.4]
205
+ class_ids = [1, 1, 2]
206
+ result = non_max_suppression_cv_per_class(
207
+ boxes, scores, class_ids, score_threshold=0.5, iou_threshold=0.5
208
+ )
209
+ assert result == [0] # Only box 0 passes score threshold
210
+
211
+ def test_per_class_nms_order_independence(self):
212
+ """Test that result is independent of input order."""
213
+ boxes = [
214
+ [10, 10, 50, 50], # Class 1 - score 0.8
215
+ [15, 15, 50, 50], # Class 2 - score 0.9
216
+ [12, 12, 48, 48], # Class 1 - score 0.95 (overlaps with box 0)
217
+ ]
218
+ scores = [0.8, 0.9, 0.95]
219
+ class_ids = [1, 2, 1]
220
+
221
+ result = non_max_suppression_cv_per_class(
222
+ boxes, scores, class_ids, score_threshold=0.5, iou_threshold=0.3
223
+ )
224
+
225
+ # Box 2 (class 1, score 0.95) should be kept
226
+ # Box 0 (class 1, score 0.8) should be suppressed
227
+ # Box 1 (class 2, score 0.9) should be kept
228
+ assert len(result) == 2
229
+ assert 1 in result # Class 2
230
+ assert 2 in result # Class 1 with higher score
231
+ assert 0 not in result # Class 1 with lower score suppressed
@@ -0,0 +1,189 @@
1
+ """Unit tests for Detection class and object detection utilities."""
2
+ import numpy as np
3
+ import pytest
4
+
5
+ from viso_sdk.detection.object import Detection, filter_iou_threshold, filter_labels
6
+ from viso_sdk.constants import KEY
7
+
8
+
9
+ class TestDetectionInit:
10
+ def test_init_with_direct_params(self):
11
+ det = Detection(class_id=1, label="car", score=0.95, tlwh=[0.1, 0.2, 0.3, 0.4])
12
+ assert det.class_id == 1
13
+ assert det.label == "car"
14
+ assert det.score == 0.95
15
+ assert np.allclose(det.tlwh, [0.1, 0.2, 0.3, 0.4])
16
+
17
+ def test_init_with_dict(self):
18
+ det_dict = {
19
+ KEY.CLASS_ID: 2,
20
+ KEY.LABEL: "person",
21
+ KEY.SCORE: 0.88,
22
+ KEY.TLWH: [0.2, 0.3, 0.4, 0.5],
23
+ }
24
+ det = Detection(det=det_dict)
25
+ assert det.class_id == 2
26
+ assert det.label == "person"
27
+ assert det.score == 0.88
28
+ assert np.allclose(det.tlwh, [0.2, 0.3, 0.4, 0.5])
29
+
30
+ def test_init_with_roi_id_and_name(self):
31
+ det = Detection(class_id=1, label="car", score=0.9, tlwh=[0.1, 0.2, 0.3, 0.4],
32
+ roi_id=[1, 2], roi_name=["zone_a", "zone_b"])
33
+ assert det.roi_id == [1, 2]
34
+ assert det.roi_name == ["zone_a", "zone_b"]
35
+
36
+ def test_init_converts_single_roi_to_list(self):
37
+ det = Detection(class_id=1, label="car", score=0.9, tlwh=[0.1, 0.2, 0.3, 0.4],
38
+ roi_id=5, roi_name="parking")
39
+ assert det.roi_id == [5]
40
+ assert det.roi_name == ["parking"]
41
+
42
+ def test_init_defaults_none_roi_to_empty_list(self):
43
+ det = Detection(class_id=1, label="car", score=0.9, tlwh=[0.1, 0.2, 0.3, 0.4])
44
+ assert det.roi_id == []
45
+ assert det.roi_name == []
46
+
47
+ def test_init_with_default_values(self):
48
+ det = Detection(tlwh=[0.1, 0.2, 0.3, 0.4])
49
+ assert det.class_id == -1
50
+ assert det.label == ""
51
+ assert det.score == 0.0
52
+
53
+
54
+ class TestDetectionCoordinateConversion:
55
+ def test_normalized_coordinates_stay_normalized(self):
56
+ # When tlwh has values < 1.0, treat as normalized
57
+ det = Detection(class_id=1, label="car", score=0.9, tlwh=[0.1, 0.2, 0.3, 0.4])
58
+ assert np.allclose(det.tlwh, [0.1, 0.2, 0.3, 0.4])
59
+
60
+ def test_pixel_coordinates_converted_to_normalized(self):
61
+ # When tlwh has values >= 1.0, treat as pixels and normalize
62
+ det = Detection(class_id=1, label="car", score=0.9,
63
+ tlwh=[100, 200, 300, 400], frame_sz=(1000, 1000))
64
+ expected = [0.1, 0.2, 0.3, 0.4]
65
+ assert np.allclose(det.tlwh, expected)
66
+
67
+ def test_pixel_coords_clamped_to_frame_bounds(self):
68
+ # Coordinates exceeding frame bounds should be clamped
69
+ det = Detection(class_id=1, label="car", score=0.9,
70
+ tlwh=[900, 900, 200, 200], frame_sz=(1000, 1000))
71
+ # x + w = 1100 > 1000, so xmax should be clamped to 1.0
72
+ # Final normalized: x=0.9, y=0.9, w=0.1, h=0.1
73
+ assert det.tlwh[0] == 0.9
74
+ assert det.tlwh[1] == 0.9
75
+ assert det.tlwh[2] <= 0.1
76
+ assert det.tlwh[3] <= 0.1
77
+
78
+ def test_negative_coordinates_clamped_to_zero(self):
79
+ det = Detection(class_id=1, label="car", score=0.9,
80
+ tlwh=[-10, -20, 100, 100], frame_sz=(1000, 1000))
81
+ # Negative coords should be clamped to 0
82
+ assert det.tlwh[0] >= 0.0
83
+ assert det.tlwh[1] >= 0.0
84
+
85
+
86
+ class TestDetectionConversionMethods:
87
+ def test_to_tlbr(self):
88
+ det = Detection(class_id=1, label="car", score=0.9, tlwh=[0.1, 0.2, 0.3, 0.4])
89
+ tlbr = det.to_tlbr()
90
+ # tlbr = [xmin, ymin, xmax, ymax] = [0.1, 0.2, 0.4, 0.6]
91
+ assert np.allclose(tlbr, [0.1, 0.2, 0.4, 0.6])
92
+
93
+ def test_to_xyah(self):
94
+ det = Detection(class_id=1, label="car", score=0.9, tlwh=[0.1, 0.2, 0.4, 0.2])
95
+ xyah = det.to_xyah()
96
+ # center_x = 0.1 + 0.4/2 = 0.3
97
+ # center_y = 0.2 + 0.2/2 = 0.3
98
+ # aspect = 0.4 / 0.2 = 2.0
99
+ # height = 0.2
100
+ assert np.allclose(xyah, [0.3, 0.3, 2.0, 0.2])
101
+
102
+ def test_to_dict(self):
103
+ det = Detection(class_id=3, label="truck", score=0.876,
104
+ tlwh=[0.123, 0.456, 0.789, 0.234],
105
+ roi_id=[1], roi_name=["zone1"])
106
+ d = det.to_dict()
107
+ assert d[KEY.CLASS_ID] == 3
108
+ assert d[KEY.LABEL] == "truck"
109
+ assert d[KEY.SCORE] == 0.88 # Rounded to 2 decimals
110
+ assert d[KEY.ROI_ID] == [1]
111
+ assert d[KEY.ROI_NAME] == ["zone1"]
112
+ # TLWH rounded to 3 decimals
113
+ assert len(d[KEY.TLWH]) == 4
114
+ assert isinstance(d[KEY.TLWH], list)
115
+
116
+
117
+ class TestFilterIouThreshold:
118
+ def test_filters_overlapping_detections(self):
119
+ # Create overlapping detections with different scores
120
+ det1 = Detection(class_id=1, label="car", score=0.9, tlwh=[0.1, 0.1, 0.3, 0.3])
121
+ det2 = Detection(class_id=1, label="car", score=0.7, tlwh=[0.15, 0.15, 0.3, 0.3]) # Overlaps with det1
122
+ det3 = Detection(class_id=1, label="car", score=0.95, tlwh=[0.6, 0.6, 0.2, 0.2]) # Separate
123
+
124
+ detections = [det1, det2, det3]
125
+ filtered = filter_iou_threshold(detections, score_threshold=0.5, iou_threshold=0.3, img_sz=(1000, 1000))
126
+
127
+ # Should keep det1 (higher score than det2) and det3 (non-overlapping)
128
+ assert len(filtered) >= 1
129
+ assert det3 in filtered # Non-overlapping detection should remain
130
+
131
+ def test_filters_by_score_threshold(self):
132
+ det1 = Detection(class_id=1, label="car", score=0.9, tlwh=[0.1, 0.1, 0.3, 0.3])
133
+ det2 = Detection(class_id=1, label="car", score=0.4, tlwh=[0.6, 0.6, 0.2, 0.2]) # Below threshold
134
+
135
+ detections = [det1, det2]
136
+ filtered = filter_iou_threshold(detections, score_threshold=0.5, iou_threshold=0.5, img_sz=(1000, 1000))
137
+
138
+ # Only det1 should pass score threshold
139
+ assert len(filtered) == 1
140
+ assert filtered[0].score == 0.9
141
+
142
+ def test_empty_detections_returns_empty(self):
143
+ filtered = filter_iou_threshold([], score_threshold=0.5, iou_threshold=0.5, img_sz=(1000, 1000))
144
+ assert filtered == []
145
+
146
+
147
+ class TestFilterLabels:
148
+ def test_filters_by_label_list(self):
149
+ det1 = Detection(class_id=1, label="car", score=0.9, tlwh=[0.1, 0.1, 0.3, 0.3])
150
+ det2 = Detection(class_id=2, label="person", score=0.8, tlwh=[0.5, 0.5, 0.2, 0.2])
151
+ det3 = Detection(class_id=3, label="truck", score=0.85, tlwh=[0.7, 0.7, 0.2, 0.2])
152
+
153
+ detections = [det1, det2, det3]
154
+ filtered = filter_labels(detections, labels_to_detect=["car", "truck"])
155
+
156
+ assert len(filtered) == 2
157
+ labels = [d.label for d in filtered]
158
+ assert "car" in labels
159
+ assert "truck" in labels
160
+ assert "person" not in labels
161
+
162
+ def test_none_labels_returns_all(self):
163
+ det1 = Detection(class_id=1, label="car", score=0.9, tlwh=[0.1, 0.1, 0.3, 0.3])
164
+ det2 = Detection(class_id=2, label="person", score=0.8, tlwh=[0.5, 0.5, 0.2, 0.2])
165
+
166
+ detections = [det1, det2]
167
+ filtered = filter_labels(detections, labels_to_detect=None)
168
+
169
+ assert len(filtered) == 2
170
+ assert filtered == detections
171
+
172
+ def test_empty_labels_returns_all(self):
173
+ det1 = Detection(class_id=1, label="car", score=0.9, tlwh=[0.1, 0.1, 0.3, 0.3])
174
+ det2 = Detection(class_id=2, label="person", score=0.8, tlwh=[0.5, 0.5, 0.2, 0.2])
175
+
176
+ detections = [det1, det2]
177
+ filtered = filter_labels(detections, labels_to_detect=[])
178
+
179
+ assert len(filtered) == 2
180
+ assert filtered == detections
181
+
182
+ def test_no_matching_labels_returns_empty(self):
183
+ det1 = Detection(class_id=1, label="car", score=0.9, tlwh=[0.1, 0.1, 0.3, 0.3])
184
+ det2 = Detection(class_id=2, label="person", score=0.8, tlwh=[0.5, 0.5, 0.2, 0.2])
185
+
186
+ detections = [det1, det2]
187
+ filtered = filter_labels(detections, labels_to_detect=["truck", "bike"])
188
+
189
+ assert len(filtered) == 0
@@ -1,8 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Tests for viso_sdk.redis.stream_cleanup"""
3
- from unittest.mock import MagicMock, patch
4
-
5
- import pytest
3
+ from unittest.mock import MagicMock
6
4
 
7
5
  from viso_sdk.redis.stream_cleanup import StreamCleanupManager, clear_stream_redis_keys
8
6
 
@@ -1,5 +1,4 @@
1
1
  """Unit tests for TraceMallocManager."""
2
- import pytest
3
2
  from viso_sdk.debug import TraceMallocManager
4
3
 
5
4
 
@@ -2,8 +2,6 @@
2
2
  """Tests for viso_sdk.redis.viso_data"""
3
3
  import time
4
4
 
5
- import pytest
6
-
7
5
  from viso_sdk.redis.viso_data import Meta, MetaFrameFormat, MetaModule, MetaSource, VisoData
8
6
 
9
7
  NODE = {"id": "node-1", "type": "object-detection", "name": "OD Node"}
@@ -60,12 +58,15 @@ class TestMetaModule:
60
58
 
61
59
 
62
60
  class TestMeta:
63
- def test_to_dict_uses_stored_ts_not_fresh_time(self):
64
- # Regression: to_dict() was calling time.time() instead of using self.ts
61
+ def test_to_dict_uses_current_time(self):
62
+ # to_dict() should use time.time() to get fresh timestamp
65
63
  meta = Meta(cur_node=NODE, feed_idx=0, src_node=NODE, src_port=0, src_name=None)
66
- stored_ts = meta.ts
64
+ time.sleep(0.01) # Small delay to ensure timestamps differ
65
+ t_before = time.time()
67
66
  d = meta.to_dict()
68
- assert d["ts"] == stored_ts
67
+ t_after = time.time()
68
+ assert t_before <= d["ts"] <= t_after
69
+ assert d["ts"] != meta.ts # Should be different from stored ts
69
70
 
70
71
  def test_to_dict_has_required_keys(self):
71
72
  meta = Meta(cur_node=NODE, feed_idx=0, src_node=NODE, src_port=0, src_name=None)
@@ -79,6 +80,22 @@ class TestMeta:
79
80
  t_after = time.time()
80
81
  assert t_before <= meta.ts <= t_after
81
82
 
83
+ def test_update_source(self):
84
+ meta = Meta(cur_node=NODE, feed_idx=0, src_node=NODE, src_port=0, src_name=None)
85
+ meta.update_source({"frame_id": 100})
86
+ assert meta.source.frame_id == 100
87
+
88
+ def test_update_module(self):
89
+ meta = Meta(cur_node=NODE, feed_idx=0, src_node=NODE, src_port=0, src_name=None)
90
+ meta.update_module({"custom_field": "test"})
91
+ assert meta.module.custom_field == "test"
92
+
93
+ def test_update_frame_format(self):
94
+ meta = Meta(cur_node=NODE, feed_idx=0, src_node=NODE, src_port=0, src_name=None)
95
+ meta.update_frame_format({"encoder": ".jpg", "with_base64": True})
96
+ assert meta.frame_format.encoder == ".jpg"
97
+ assert meta.frame_format.with_base64 is True
98
+
82
99
 
83
100
  class TestVisoData:
84
101
  def test_create_without_frame_returns_none_frame(self):
@@ -1994,7 +1994,7 @@ static __pyx_mstatetype * const __pyx_mstate_global = &__pyx_mstate_global_stati
1994
1994
  #endif
1995
1995
  /* #### Code section: constant_name_defines ### */
1996
1996
  #define __pyx_kp_u_ __pyx_string_tab[0]
1997
- #define __pyx_kp_u_1_1_17 __pyx_string_tab[1]
1997
+ #define __pyx_kp_u_1_1_19 __pyx_string_tab[1]
1998
1998
  #define __pyx_kp_u_Copyright_2020 __pyx_string_tab[2]
1999
1999
  #define __pyx_kp_u_Viso_ai_team __pyx_string_tab[3]
2000
2000
  #define __pyx_kp_u__2 __pyx_string_tab[4]
@@ -2605,16 +2605,16 @@ __Pyx_RefNannySetupContext("PyInit__version", 0);
2605
2605
  * __license__ = "LGPL3"
2606
2606
  * __title__ = "viso-sdk-python" # <<<<<<<<<<<<<<
2607
2607
  *
2608
- * __version__ = "1.1.17"
2608
+ * __version__ = "1.1.19"
2609
2609
  */
2610
2610
  if (PyDict_SetItem(__pyx_mstate_global->__pyx_d, __pyx_mstate_global->__pyx_n_u_title, __pyx_mstate_global->__pyx_kp_u_viso_sdk_python) < (0)) __PYX_ERR(0, 15, __pyx_L1_error)
2611
2611
 
2612
2612
  /* "viso_sdk/_version.py":17
2613
2613
  * __title__ = "viso-sdk-python"
2614
2614
  *
2615
- * __version__ = "1.1.17" # <<<<<<<<<<<<<<
2615
+ * __version__ = "1.1.19" # <<<<<<<<<<<<<<
2616
2616
  */
2617
- if (PyDict_SetItem(__pyx_mstate_global->__pyx_d, __pyx_mstate_global->__pyx_n_u_version, __pyx_mstate_global->__pyx_kp_u_1_1_17) < (0)) __PYX_ERR(0, 17, __pyx_L1_error)
2617
+ if (PyDict_SetItem(__pyx_mstate_global->__pyx_d, __pyx_mstate_global->__pyx_n_u_version, __pyx_mstate_global->__pyx_kp_u_1_1_19) < (0)) __PYX_ERR(0, 17, __pyx_L1_error)
2618
2618
 
2619
2619
  /* "viso_sdk/_version.py":1
2620
2620
  * """ # <<<<<<<<<<<<<<
@@ -2689,7 +2689,7 @@ static int __Pyx_InitConstants(__pyx_mstatetype *__pyx_mstate) {
2689
2689
  {
2690
2690
  const struct { const unsigned int length: 5; } index[] = {{1},{6},{15},{12},{1},{18},{26},{15},{5},{7},{10},{18},{13},{8},{8},{7},{9},{8},{11},{8},{10},{8},{3},{2},{4},{12},{10},{8},{9},{11},{4}};
2691
2691
  #if (CYTHON_COMPRESS_STRINGS) == 2 /* compression: bz2 (242 bytes) */
2692
- const char* const cstring = "BZh91AY&SYI\035\357\304\000\000\007\337\200@\000@\003x\205\350\204A\000\277\357\377\2400\000\315[\014\246\251\261\032\217\005<\240\r\007\244\323@DM6\223LM2\000\000\001\240\324\3656\251\246\241\232\020\304\310\3204\321\241B}\317\023\177kg\260 \035\340 Z\376\rG[']N7\363d\253B\010\313\310Z\267(\347m\323\314\021\232\010\010\024@c\311w\270\016\003\344b\354\037\236\203J\027\241\211a\270\276(\315:U\024$b\337\332]'j\372\314\025\256\316\316e`Tm\0023)E\244\027\351\362\025\rH\213YH\275X\250Gz{\240\367\332\304\326\244\302\251\344\341_\034a\254\230\376\276!0f\271\314\347\255\367\004GrU*\004\201\212\020\326\026\203r\226\221\215=K\030Y%%\037\225\001,<\037\342\356H\247\n\022\t#\275\370\200";
2692
+ const char* const cstring = "BZh91AY&SYo\247\030\321\000\000\007\337\200@\000@\003x%\350\204A\000\277\357\377\2400\000\315[\014\246\251\261\032\217\005<\240\r\007\244\323@DM6\223LM2\000\000\001\240\324\3656\251\246\241\232\020\304\310\3204\321\241B}\317\023\177kg\260 \035\340 Z\376\rG[']N7\363d\253B\010\313\310Z\267(\347m\323\314\021\232\010\010\024@c\311w\270\016\003\344b\354\037\236\203J\027\241\211a\270\276(\315:U\024$b\337\332]'j\372\314\025\256\316\316e`Tm\0023)E\244\027\351\362\025\rH\213YH\275X\250Gz{\240\367\332\304\326\244\302\251\344\341_\034a\254\230\376\276!0f\271\314\347\255\367\004GrU*\004\201\212\020\326\026\203r\226\221\215=K\030Y%%\037\225\001,<\037\342\356H\247\n\022\r\364\343\032 ";
2693
2693
  PyObject *data = __Pyx_DecompressString(cstring, 242, 2);
2694
2694
  if (unlikely(!data)) __PYX_ERR(0, 1, __pyx_L1_error)
2695
2695
  const char* const bytes = __Pyx_PyBytes_AsString(data);
@@ -2697,7 +2697,7 @@ const char* const cstring = "BZh91AY&SYI\035\357\304\000\000\007\337\200@\000@\0
2697
2697
  if (likely(bytes)); else { Py_DECREF(data); __PYX_ERR(0, 1, __pyx_L1_error) }
2698
2698
  #endif
2699
2699
  #elif (CYTHON_COMPRESS_STRINGS) != 0 /* compression: zlib (196 bytes) */
2700
- const char* const cstring = "x\3325\216\275n\3030\014\204_\305/`#M\207.A\177\320!K\206N]\tF\242c\"2\351JT\002\277}\3518\006\207;\334\007\362\330\275t>o\337:\315\231/\2035\373\335~\327\376r\321\016\2711\302\361\343\322\005\315\016Q>o\317|\323\257cs`\351u\003\357\213\266%^\333i\266A\345t\3749\275\342\271Lh\003\000V\3172@H,\004,`\031\003\2351\\=\333\036\000\3675C\344\034\321\310x$\267\202#\001\320\210\234\234C\317\211\026M\034H\312\303:\222\207j\254+\\wD\357\372\254\377\253\230\326\260\220E\352\261&o3*K'\030\333\272w\243\\X\375\330L\230\377\0019@m\355";
2700
+ const char* const cstring = "x\3325\216\275n\3030\014\204_\305/`#M\247\002A\177\320!K\206N]\tF\242c\"2\351JT\002\277}\3518\006\207;\334\007\362\330\275t>o\337:\315\231/\2035\373\335~\327\376r\321\016\2711\302\361\343\322\005\315\016Q>o\317|\323\257cs`\351u\003\357\213\266%^\333i\266A\345t\3749\275\342\271Lh\003\000V\3172@H,\004,`\031\003\2351\\=\333\036\000\3675C\344\034\321\310x$\267\202#\001\320\210\234\234C\317\211\026M\034H\312\303:\222\207j\254+\\wD\357\372\254\377\253\230\326\260\220E\352\261&o3*K'\030\333\272w\243\\X\375\330L\230\377\001;rm\357";
2701
2701
  PyObject *data = __Pyx_DecompressString(cstring, 196, 1);
2702
2702
  if (unlikely(!data)) __PYX_ERR(0, 1, __pyx_L1_error)
2703
2703
  const char* const bytes = __Pyx_PyBytes_AsString(data);
@@ -2705,7 +2705,7 @@ const char* const cstring = "x\3325\216\275n\3030\014\204_\305/`#M\207.A\177\320
2705
2705
  if (likely(bytes)); else { Py_DECREF(data); __PYX_ERR(0, 1, __pyx_L1_error) }
2706
2706
  #endif
2707
2707
  #else /* compression: none (287 bytes) */
2708
- const char* const bytes = ".1.1.17Copyright 2020-Viso.ai team?g.corrigan@viso.ai viso.ai AG <info@viso.ai>viso-sdk-pythonLGPL3abspath__author__cline_in_traceback__copyright___cur_dirdatetimedirname__email____file____license____main____module____name__nowospath__qualname__setdefault__test____title____version__year";
2708
+ const char* const bytes = ".1.1.19Copyright 2020-Viso.ai team?g.corrigan@viso.ai viso.ai AG <info@viso.ai>viso-sdk-pythonLGPL3abspath__author__cline_in_traceback__copyright___cur_dirdatetimedirname__email____file____license____main____module____name__nowospath__qualname__setdefault__test____title____version__year";
2709
2709
  PyObject *data = NULL;
2710
2710
  CYTHON_UNUSED_VAR(__Pyx_DecompressString);
2711
2711
  #endif
@@ -14,4 +14,4 @@ __email__ = "g.corrigan@viso.ai"
14
14
  __license__ = "LGPL3"
15
15
  __title__ = "viso-sdk-python"
16
16
 
17
- __version__ = "1.1.17"
17
+ __version__ = "1.1.19"