learning-loop-node 0.13.7__tar.gz → 0.14.0__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.

Potentially problematic release.


This version of learning-loop-node might be problematic. Click here for more details.

Files changed (104) hide show
  1. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/PKG-INFO +33 -38
  2. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/README.md +32 -37
  3. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/detector/detector_logic.py +2 -4
  4. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/detector/detector_node.py +44 -30
  5. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/detector/inbox_filter/relevance_filter.py +11 -9
  6. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/detector/outbox.py +134 -44
  7. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/detector/rest/detect.py +3 -3
  8. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/detector/rest/upload.py +4 -3
  9. learning_loop_node-0.14.0/learning_loop_node/helpers/background_tasks.py +78 -0
  10. learning_loop_node-0.14.0/learning_loop_node/helpers/run.py +21 -0
  11. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/node.py +11 -4
  12. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/annotator/conftest.py +9 -4
  13. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/annotator/test_annotator_node.py +10 -2
  14. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/inbox_filter/test_unexpected_observations_count.py +4 -3
  15. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/test_client_communication.py +1 -23
  16. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/test_outbox.py +7 -16
  17. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/general/conftest.py +8 -2
  18. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/pyproject.toml +1 -1
  19. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/__init__.py +0 -0
  20. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/annotation/__init__.py +0 -0
  21. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/annotation/annotator_logic.py +0 -0
  22. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/annotation/annotator_node.py +0 -0
  23. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/data_classes/__init__.py +0 -0
  24. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/data_classes/annotations.py +0 -0
  25. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/data_classes/detections.py +0 -0
  26. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/data_classes/general.py +0 -0
  27. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/data_classes/image_metadata.py +0 -0
  28. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/data_classes/socket_response.py +0 -0
  29. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/data_classes/training.py +0 -0
  30. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/data_exchanger.py +0 -0
  31. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/detector/__init__.py +0 -0
  32. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/detector/exceptions.py +0 -0
  33. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/detector/inbox_filter/__init__.py +0 -0
  34. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/detector/inbox_filter/cam_observation_history.py +0 -0
  35. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/detector/rest/__init__.py +0 -0
  36. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/detector/rest/about.py +0 -0
  37. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/detector/rest/backdoor_controls.py +0 -0
  38. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/detector/rest/model_version_control.py +0 -0
  39. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/detector/rest/operation_mode.py +0 -0
  40. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/detector/rest/outbox_mode.py +0 -0
  41. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/enums/__init__.py +0 -0
  42. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/enums/annotator.py +0 -0
  43. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/enums/detector.py +0 -0
  44. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/enums/general.py +0 -0
  45. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/enums/trainer.py +0 -0
  46. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/examples/novelty_score_updater.py +0 -0
  47. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/globals.py +0 -0
  48. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/helpers/__init__.py +0 -0
  49. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/helpers/environment_reader.py +0 -0
  50. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/helpers/gdrive_downloader.py +0 -0
  51. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/helpers/log_conf.py +0 -0
  52. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/helpers/misc.py +0 -0
  53. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/loop_communication.py +0 -0
  54. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/py.typed +0 -0
  55. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/rest.py +0 -0
  56. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/__init__.py +0 -0
  57. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/annotator/__init__.py +0 -0
  58. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/annotator/pytest.ini +0 -0
  59. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/__init__.py +0 -0
  60. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/conftest.py +0 -0
  61. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/inbox_filter/__init__.py +0 -0
  62. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/inbox_filter/test_observation.py +0 -0
  63. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/inbox_filter/test_relevance_group.py +0 -0
  64. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/pytest.ini +0 -0
  65. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/test.jpg +0 -0
  66. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/test_detector_node.py +0 -0
  67. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/test_relevance_filter.py +0 -0
  68. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/testing_detector.py +0 -0
  69. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/general/__init__.py +0 -0
  70. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/general/pytest.ini +0 -0
  71. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/general/test_data/file_1.txt +0 -0
  72. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/general/test_data/file_2.txt +0 -0
  73. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/general/test_data/model.json +0 -0
  74. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/general/test_data_classes.py +0 -0
  75. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/general/test_downloader.py +0 -0
  76. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/general/test_learning_loop_node.py +0 -0
  77. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/test_helper.py +0 -0
  78. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/__init__.py +0 -0
  79. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/conftest.py +0 -0
  80. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/pytest.ini +0 -0
  81. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/state_helper.py +0 -0
  82. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/states/__init__.py +0 -0
  83. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/states/test_state_cleanup.py +0 -0
  84. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/states/test_state_detecting.py +0 -0
  85. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/states/test_state_download_train_model.py +0 -0
  86. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/states/test_state_prepare.py +0 -0
  87. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/states/test_state_sync_confusion_matrix.py +0 -0
  88. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/states/test_state_train.py +0 -0
  89. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/states/test_state_upload_detections.py +0 -0
  90. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/states/test_state_upload_model.py +0 -0
  91. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/test_errors.py +0 -0
  92. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/test_trainer_states.py +0 -0
  93. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/testing_trainer_logic.py +0 -0
  94. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/trainer/__init__.py +0 -0
  95. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/trainer/downloader.py +0 -0
  96. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/trainer/exceptions.py +0 -0
  97. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/trainer/executor.py +0 -0
  98. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/trainer/io_helpers.py +0 -0
  99. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/trainer/rest/__init__.py +0 -0
  100. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/trainer/rest/backdoor_controls.py +0 -0
  101. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/trainer/test_executor.py +0 -0
  102. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/trainer/trainer_logic.py +0 -0
  103. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/trainer/trainer_logic_generic.py +0 -0
  104. {learning_loop_node-0.13.7 → learning_loop_node-0.14.0}/learning_loop_node/trainer/trainer_node.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: learning-loop-node
3
- Version: 0.13.7
3
+ Version: 0.14.0
4
4
  Summary: Python Library for Nodes which connect to the Zauberzeug Learning Loop
5
5
  Home-page: https://github.com/zauberzeug/learning_loop_node
6
6
  License: MIT
@@ -105,6 +105,9 @@ The detector also has a sio **upload endpoint** that can be used to upload image
105
105
  - `image`: the image data in jpg format
106
106
  - `tags`: a list of strings. If not provided the tag is `picked_by_system`
107
107
  - `detections`: a dictionary representing the detections. UUIDs for the classes are automatically determined based on the category names. This field is optional. If not provided, no detections are uploaded.
108
+ - `source`: optional source identifier for the image
109
+ - `creation_date`: optional creation date for the image
110
+ - `upload_priority`: boolean flag to prioritize the upload (defaults to False)
108
111
 
109
112
  The endpoint returns None if the upload was successful and an error message otherwise.
110
113
 
@@ -187,58 +190,52 @@ Upload a model with
187
190
  The model should now be available for the format 'format_a'
188
191
  `curl "https://learning-loop.ai/api/zauberzeug/projects/demo/models?format=format_a"`
189
192
 
190
- ````
191
-
192
- {
193
- "models": [
193
+ ```json
194
194
  {
195
- "id": "3c20d807-f71c-40dc-a996-8a8968aa5431",
196
- "version": "4.0",
197
- "formats": [
198
- "format_a"
199
- ],
200
- "created": "2021-06-01T06:28:21.289092",
201
- "comment": "uploaded at 2021-06-01 06:28:21.288442",
202
- ...
203
- }
204
- ]
195
+ "models": [
196
+ {
197
+ "id": "3c20d807-f71c-40dc-a996-8a8968aa5431",
198
+ "version": "4.0",
199
+ "formats": [
200
+ "format_a"
201
+ ],
202
+ "created": "2021-06-01T06:28:21.289092",
203
+ "comment": "uploaded at 2021-06-01 06:28:21.288442",
204
+ ...
205
+ }
206
+ ]
205
207
  }
206
-
207
208
  ```
208
209
 
209
210
  but not in the format_b
210
211
  `curl "https://learning-loop.ai/api/zauberzeug/projects/demo/models?format=format_b"`
211
212
 
212
- ```
213
-
213
+ ```json
214
214
  {
215
- "models": []
215
+ "models": []
216
216
  }
217
-
218
217
  ```
219
218
 
220
219
  Connect the Node to the Learning Loop by simply starting the container.
221
220
  After a short time the converted model should be available as well.
222
221
  `curl https://learning-loop.ai/api/zauberzeug/projects/demo/models?format=format_b`
223
222
 
224
- ```
225
-
223
+ ```json
226
224
  {
227
- "models": [
228
- {
229
- "id": "3c20d807-f71c-40dc-a996-8a8968aa5431",
230
- "version": "4.0",
231
- "formats": [
232
- "format_a",
233
- "format_b",
234
- ],
235
- "created": "2021-06-01T06:28:21.289092",
236
- "comment": "uploaded at 2021-06-01 06:28:21.288442",
237
- ...
225
+ "models": [
226
+ {
227
+ "id": "3c20d807-f71c-40dc-a996-8a8968aa5431",
228
+ "version": "4.0",
229
+ "formats": [
230
+ "format_a",
231
+ "format_b",
232
+ ],
233
+ "created": "2021-06-01T06:28:21.289092",
234
+ "comment": "uploaded at 2021-06-01 06:28:21.288442",
235
+ ...
236
+ }
237
+ ]
238
238
  }
239
- ]
240
- }
241
-
242
239
  ```
243
240
 
244
241
  ## About Models (the currency between Nodes)
@@ -257,6 +254,4 @@ After a short time the converted model should be available as well.
257
254
  - Nodes add properties to `model.json`, which contains all the information which are needed by subsequent nodes. These are typically the properties:
258
255
  - `resolution`: resolution in which the model expects images (as `int`, since the resolution is mostly square - later, ` resolution_x`` resolution_y ` would also be conceivable or `resolutions` to give a list of possible resolutions)
259
256
  - `categories`: list of categories with name, id, (later also type), in the order in which they are used by the model -- this is neccessary to be robust about renamings
260
- ```
261
- ````
262
257
 
@@ -65,6 +65,9 @@ The detector also has a sio **upload endpoint** that can be used to upload image
65
65
  - `image`: the image data in jpg format
66
66
  - `tags`: a list of strings. If not provided the tag is `picked_by_system`
67
67
  - `detections`: a dictionary representing the detections. UUIDs for the classes are automatically determined based on the category names. This field is optional. If not provided, no detections are uploaded.
68
+ - `source`: optional source identifier for the image
69
+ - `creation_date`: optional creation date for the image
70
+ - `upload_priority`: boolean flag to prioritize the upload (defaults to False)
68
71
 
69
72
  The endpoint returns None if the upload was successful and an error message otherwise.
70
73
 
@@ -147,58 +150,52 @@ Upload a model with
147
150
  The model should now be available for the format 'format_a'
148
151
  `curl "https://learning-loop.ai/api/zauberzeug/projects/demo/models?format=format_a"`
149
152
 
150
- ````
151
-
152
- {
153
- "models": [
153
+ ```json
154
154
  {
155
- "id": "3c20d807-f71c-40dc-a996-8a8968aa5431",
156
- "version": "4.0",
157
- "formats": [
158
- "format_a"
159
- ],
160
- "created": "2021-06-01T06:28:21.289092",
161
- "comment": "uploaded at 2021-06-01 06:28:21.288442",
162
- ...
163
- }
164
- ]
155
+ "models": [
156
+ {
157
+ "id": "3c20d807-f71c-40dc-a996-8a8968aa5431",
158
+ "version": "4.0",
159
+ "formats": [
160
+ "format_a"
161
+ ],
162
+ "created": "2021-06-01T06:28:21.289092",
163
+ "comment": "uploaded at 2021-06-01 06:28:21.288442",
164
+ ...
165
+ }
166
+ ]
165
167
  }
166
-
167
168
  ```
168
169
 
169
170
  but not in the format_b
170
171
  `curl "https://learning-loop.ai/api/zauberzeug/projects/demo/models?format=format_b"`
171
172
 
172
- ```
173
-
173
+ ```json
174
174
  {
175
- "models": []
175
+ "models": []
176
176
  }
177
-
178
177
  ```
179
178
 
180
179
  Connect the Node to the Learning Loop by simply starting the container.
181
180
  After a short time the converted model should be available as well.
182
181
  `curl https://learning-loop.ai/api/zauberzeug/projects/demo/models?format=format_b`
183
182
 
184
- ```
185
-
183
+ ```json
186
184
  {
187
- "models": [
188
- {
189
- "id": "3c20d807-f71c-40dc-a996-8a8968aa5431",
190
- "version": "4.0",
191
- "formats": [
192
- "format_a",
193
- "format_b",
194
- ],
195
- "created": "2021-06-01T06:28:21.289092",
196
- "comment": "uploaded at 2021-06-01 06:28:21.288442",
197
- ...
185
+ "models": [
186
+ {
187
+ "id": "3c20d807-f71c-40dc-a996-8a8968aa5431",
188
+ "version": "4.0",
189
+ "formats": [
190
+ "format_a",
191
+ "format_b",
192
+ ],
193
+ "created": "2021-06-01T06:28:21.289092",
194
+ "comment": "uploaded at 2021-06-01 06:28:21.288442",
195
+ ...
196
+ }
197
+ ]
198
198
  }
199
- ]
200
- }
201
-
202
199
  ```
203
200
 
204
201
  ## About Models (the currency between Nodes)
@@ -217,5 +214,3 @@ After a short time the converted model should be available as well.
217
214
  - Nodes add properties to `model.json`, which contains all the information which are needed by subsequent nodes. These are typically the properties:
218
215
  - `resolution`: resolution in which the model expects images (as `int`, since the resolution is mostly square - later, ` resolution_x`` resolution_y ` would also be conceivable or `resolutions` to give a list of possible resolutions)
219
216
  - `categories`: list of categories with name, id, (later also type), in the order in which they are used by the model -- this is neccessary to be robust about renamings
220
- ```
221
- ````
@@ -2,8 +2,6 @@ import logging
2
2
  from abc import abstractmethod
3
3
  from typing import List, Optional
4
4
 
5
- import numpy as np
6
-
7
5
  from ..data_classes import ImageMetadata, ModelInformation
8
6
  from ..globals import GLOBALS
9
7
  from .exceptions import NodeNeedsRestartError
@@ -44,13 +42,13 @@ class DetectorLogic():
44
42
  def init(self):
45
43
  """Called when a (new) model was loaded. Initialize the model. Model information available via `self.model_info`"""
46
44
 
47
- def evaluate_with_all_info(self, image: np.ndarray, tags: List[str], source: Optional[str] = None, creation_date: Optional[str] = None) -> ImageMetadata: # pylint: disable=unused-argument
45
+ def evaluate_with_all_info(self, image: bytes, tags: List[str], source: Optional[str] = None, creation_date: Optional[str] = None) -> ImageMetadata: # pylint: disable=unused-argument
48
46
  """Called by the detector node when an image should be evaluated (REST or SocketIO).
49
47
  Tags, source come from the caller and may be used in this function.
50
48
  By default, this function simply calls `evaluate`"""
51
49
  return self.evaluate(image)
52
50
 
53
51
  @abstractmethod
54
- def evaluate(self, image: np.ndarray) -> ImageMetadata:
52
+ def evaluate(self, image: bytes) -> ImageMetadata:
55
53
  """Evaluate the image and return the detections.
56
54
  The object should return empty detections if it is not initialized"""
@@ -1,14 +1,12 @@
1
1
  import asyncio
2
2
  import contextlib
3
- import math
4
3
  import os
5
4
  import shutil
6
5
  import subprocess
7
6
  import sys
8
7
  from dataclasses import asdict
9
8
  from datetime import datetime
10
- from threading import Thread
11
- from typing import Dict, List, Optional
9
+ from typing import Dict, List, Optional, cast
12
10
 
13
11
  import numpy as np
14
12
  import socketio
@@ -30,7 +28,7 @@ from ..data_classes.socket_response import SocketResponse
30
28
  from ..data_exchanger import DataExchanger, DownloadError
31
29
  from ..enums import OperationMode, VersionMode
32
30
  from ..globals import GLOBALS
33
- from ..helpers import environment_reader
31
+ from ..helpers import background_tasks, environment_reader, run
34
32
  from ..node import Node
35
33
  from .detector_logic import DetectorLogic
36
34
  from .exceptions import NodeNeedsRestartError
@@ -227,7 +225,7 @@ class DetectorNode(Node):
227
225
  async def detect(sid, data: Dict) -> Dict:
228
226
  try:
229
227
  det = await self.get_detections(
230
- raw_image=np.frombuffer(data['image'], np.uint8),
228
+ raw_image=data['image'],
231
229
  camera_id=data.get('camera-id', None) or data.get('mac', None),
232
230
  tags=data.get('tags', []),
233
231
  source=data.get('source', None),
@@ -279,9 +277,10 @@ class DetectorNode(Node):
279
277
  return {'error': str(e)}
280
278
 
281
279
  @self.sio.event
282
- async def upload(sid, data: Dict) -> Optional[Dict]:
283
- '''upload an image with detections'''
280
+ async def upload(sid, data: Dict) -> Dict:
281
+ """Upload an image with detections"""
284
282
 
283
+ self.log.debug('Processing upload via socketio.')
285
284
  detection_data = data.get('detections', {})
286
285
  if detection_data and self.detector_logic.model_info is not None:
287
286
  try:
@@ -293,22 +292,19 @@ class DetectorNode(Node):
293
292
  else:
294
293
  image_metadata = ImageMetadata()
295
294
 
296
- tags = data.get('tags', [])
297
- tags.append('picked_by_system')
298
-
299
- source = data.get('source', None)
300
- creation_date = data.get('creation_date', None)
301
-
302
- self.log.debug('running upload via socketio. tags: %s, source: %s, creation_date: %s',
303
- tags, source, creation_date)
304
-
305
- loop = asyncio.get_event_loop()
306
295
  try:
307
- await loop.run_in_executor(None, self.outbox.save, data['image'], image_metadata, tags, source, creation_date)
296
+ await self.upload_images(
297
+ images=[data['image']],
298
+ image_metadata=image_metadata,
299
+ tags=data.get('tags', []),
300
+ source=data.get('source', None),
301
+ creation_date=data.get('creation_date', None),
302
+ upload_priority=data.get('upload_priority', False)
303
+ )
308
304
  except Exception as e:
309
305
  self.log.exception('could not upload via socketio')
310
306
  return {'error': str(e)}
311
- return None
307
+ return {'status': 'OK'}
312
308
 
313
309
  @self.sio.event
314
310
  def connect(sid, environ, auth) -> None:
@@ -469,7 +465,7 @@ class DetectorNode(Node):
469
465
  self.log.warning('Operation mode set to %s, but sync failed: %s', mode, e)
470
466
 
471
467
  def reload(self, reason: str):
472
- '''provide a cause for the reload'''
468
+ """provide a cause for the reload"""
473
469
 
474
470
  self.log.info('########## reloading app because %s', reason)
475
471
  if os.path.isfile('/app/app_code/restart/restart.py'):
@@ -482,7 +478,7 @@ class DetectorNode(Node):
482
478
  self.log.error('could not reload app')
483
479
 
484
480
  async def get_detections(self,
485
- raw_image: np.ndarray,
481
+ raw_image: bytes,
486
482
  camera_id: Optional[str],
487
483
  tags: List[str],
488
484
  source: Optional[str] = None,
@@ -494,8 +490,7 @@ class DetectorNode(Node):
494
490
  It can be converted e.g. using cv2.imdecode(raw_image, cv2.IMREAD_COLOR)"""
495
491
 
496
492
  await self.detection_lock.acquire()
497
- loop = asyncio.get_event_loop()
498
- detections = await loop.run_in_executor(None, self.detector_logic.evaluate_with_all_info, raw_image, tags, source, creation_date)
493
+ detections = await run.io_bound(self.detector_logic.evaluate_with_all_info, raw_image, tags, source, creation_date)
499
494
  self.detection_lock.release()
500
495
 
501
496
  fix_shape_detections(detections)
@@ -503,21 +498,40 @@ class DetectorNode(Node):
503
498
  n_po, n_se = len(detections.point_detections), len(detections.segmentation_detections)
504
499
  self.log.debug('Detected: %d boxes, %d points, %d segs, %d classes', n_bo, n_po, n_se, n_cl)
505
500
 
506
- if autoupload is None or autoupload == 'filtered': # NOTE default is filtered
507
- Thread(target=self.relevance_filter.may_upload_detections,
508
- args=(detections, camera_id, raw_image, tags, source, creation_date)).start()
501
+ autoupload = autoupload or 'filtered'
502
+ if autoupload == 'filtered' and camera_id is not None:
503
+ background_tasks.create(self.relevance_filter.may_upload_detections(
504
+ detections, camera_id, raw_image, tags, source, creation_date
505
+ ))
509
506
  elif autoupload == 'all':
510
- Thread(target=self.outbox.save, args=(raw_image, detections, tags, source, creation_date)).start()
507
+ background_tasks.create(self.outbox.save(raw_image, detections, tags, source, creation_date))
511
508
  elif autoupload == 'disabled':
512
509
  pass
513
510
  else:
514
511
  self.log.error('unknown autoupload value %s', autoupload)
515
512
  return detections
516
513
 
517
- async def upload_images(self, images: List[bytes], source: Optional[str], creation_date: Optional[str]):
518
- loop = asyncio.get_event_loop()
514
+ async def upload_images(
515
+ self, *,
516
+ images: List[bytes],
517
+ image_metadata: Optional[ImageMetadata] = None,
518
+ tags: Optional[List[str]] = None,
519
+ source: Optional[str],
520
+ creation_date: Optional[str],
521
+ upload_priority: bool = False
522
+ ) -> None:
523
+ """Save images to the outbox using an asyncio executor.
524
+ Used by SIO and REST upload endpoints."""
525
+
526
+ if image_metadata is None:
527
+ image_metadata = ImageMetadata()
528
+ if tags is None:
529
+ tags = []
530
+
531
+ tags.append('picked_by_system')
532
+
519
533
  for image in images:
520
- await loop.run_in_executor(None, self.outbox.save, image, ImageMetadata(), ['picked_by_system'], source, creation_date)
534
+ await self.outbox.save(image, image_metadata, tags, source, creation_date, upload_priority)
521
535
 
522
536
  def add_category_id_to_detections(self, model_info: ModelInformation, image_metadata: ImageMetadata):
523
537
  def find_category_id_by_name(categories: List[Category], category_name: str):
@@ -11,14 +11,16 @@ class RelevanceFilter():
11
11
  self.cam_histories: Dict[str, CamObservationHistory] = {}
12
12
  self.outbox: Outbox = outbox
13
13
 
14
- def may_upload_detections(self,
15
- image_metadata: ImageMetadata,
16
- cam_id: str,
17
- raw_image: bytes,
18
- tags: List[str],
19
- source: Optional[str] = None,
20
- creation_date: Optional[str] = None
21
- ) -> List[str]:
14
+ async def may_upload_detections(self,
15
+ image_metadata: ImageMetadata,
16
+ cam_id: str,
17
+ raw_image: bytes,
18
+ tags: List[str],
19
+ source: Optional[str] = None,
20
+ creation_date: Optional[str] = None) -> List[str]:
21
+ """Check if the detection should be uploaded to the outbox.
22
+ If so, upload it and return the list of causes for the upload.
23
+ """
22
24
  for group in self.cam_histories.values():
23
25
  group.forget_old_detections()
24
26
 
@@ -30,5 +32,5 @@ class RelevanceFilter():
30
32
  if len(causes) > 0:
31
33
  tags = tags if tags is not None else []
32
34
  tags.extend(causes)
33
- self.outbox.save(raw_image, image_metadata, tags, source, creation_date)
35
+ await self.outbox.save(raw_image, image_metadata, tags, source, creation_date)
34
36
  return causes