learning-loop-node 0.13.6__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.
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/PKG-INFO +37 -40
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/README.md +36 -39
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/data_exchanger.py +1 -1
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/detector/detector_logic.py +2 -4
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/detector/detector_node.py +69 -43
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/detector/inbox_filter/relevance_filter.py +11 -9
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/detector/outbox.py +134 -44
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/detector/rest/detect.py +3 -3
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/detector/rest/upload.py +4 -3
- learning_loop_node-0.14.0/learning_loop_node/helpers/background_tasks.py +78 -0
- learning_loop_node-0.14.0/learning_loop_node/helpers/run.py +21 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/loop_communication.py +18 -18
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/node.py +11 -4
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/annotator/conftest.py +9 -4
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/annotator/test_annotator_node.py +10 -2
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/inbox_filter/test_unexpected_observations_count.py +4 -3
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/test_client_communication.py +1 -23
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/test_outbox.py +7 -16
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/general/conftest.py +8 -2
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/pyproject.toml +1 -1
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/__init__.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/annotation/__init__.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/annotation/annotator_logic.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/annotation/annotator_node.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/data_classes/__init__.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/data_classes/annotations.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/data_classes/detections.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/data_classes/general.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/data_classes/image_metadata.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/data_classes/socket_response.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/data_classes/training.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/detector/__init__.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/detector/exceptions.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/detector/inbox_filter/__init__.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/detector/inbox_filter/cam_observation_history.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/detector/rest/__init__.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/detector/rest/about.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/detector/rest/backdoor_controls.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/detector/rest/model_version_control.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/detector/rest/operation_mode.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/detector/rest/outbox_mode.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/enums/__init__.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/enums/annotator.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/enums/detector.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/enums/general.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/enums/trainer.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/examples/novelty_score_updater.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/globals.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/helpers/__init__.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/helpers/environment_reader.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/helpers/gdrive_downloader.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/helpers/log_conf.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/helpers/misc.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/py.typed +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/rest.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/__init__.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/annotator/__init__.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/annotator/pytest.ini +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/__init__.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/conftest.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/inbox_filter/__init__.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/inbox_filter/test_observation.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/inbox_filter/test_relevance_group.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/pytest.ini +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/test.jpg +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/test_detector_node.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/test_relevance_filter.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/detector/testing_detector.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/general/__init__.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/general/pytest.ini +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/general/test_data/file_1.txt +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/general/test_data/file_2.txt +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/general/test_data/model.json +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/general/test_data_classes.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/general/test_downloader.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/general/test_learning_loop_node.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/test_helper.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/__init__.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/conftest.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/pytest.ini +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/state_helper.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/states/__init__.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/states/test_state_cleanup.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/states/test_state_detecting.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/states/test_state_download_train_model.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/states/test_state_prepare.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/states/test_state_sync_confusion_matrix.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/states/test_state_train.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/states/test_state_upload_detections.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/states/test_state_upload_model.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/test_errors.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/test_trainer_states.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/tests/trainer/testing_trainer_logic.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/trainer/__init__.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/trainer/downloader.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/trainer/exceptions.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/trainer/executor.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/trainer/io_helpers.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/trainer/rest/__init__.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/trainer/rest/backdoor_controls.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/trainer/test_executor.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/trainer/trainer_logic.py +0 -0
- {learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/trainer/trainer_logic_generic.py +0 -0
- {learning_loop_node-0.13.6 → 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.
|
|
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
|
|
@@ -63,8 +63,8 @@ You can configure connection to our Learning Loop by specifying the following en
|
|
|
63
63
|
| LOOP_USERNAME | USERNAME | Learning Loop user name | all besides Detector |
|
|
64
64
|
| LOOP_PASSWORD | PASSWORD | Learning Loop password | all besides Detector |
|
|
65
65
|
| LOOP_SSL_CERT_PATH | - | Path to the SSL certificate | all (opt.) |
|
|
66
|
-
| LOOP_ORGANIZATION | ORGANIZATION | Organization
|
|
67
|
-
| LOOP_PROJECT | PROJECT | Project
|
|
66
|
+
| LOOP_ORGANIZATION | ORGANIZATION | Organization ID | Detector |
|
|
67
|
+
| LOOP_PROJECT | PROJECT | Project ID | Detector (opt.) |
|
|
68
68
|
| MIN_UNCERTAIN_THRESHOLD | - | smallest confidence (float) at which auto-upload will happen | Detector (opt.) |
|
|
69
69
|
| MAX_UNCERTAIN_THRESHOLD | - | largest confidence (float) at which auto-upload will happen | Detector (opt.) |
|
|
70
70
|
| INFERENCE_BATCH_SIZE | - | Batch size of trainer when calculating detections | Trainer (opt.) |
|
|
@@ -73,6 +73,8 @@ You can configure connection to our Learning Loop by specifying the following en
|
|
|
73
73
|
| TRAINER_IDLE_TIMEOUT_SEC | - | Automatically shutdown trainer after timeout (in seconds) | Trainer (opt.) |
|
|
74
74
|
| USE_BACKDOOR_CONTROLS | - | Always enable backdoor controls (set to 1) | Trainer / Detector (opt.) |
|
|
75
75
|
|
|
76
|
+
Note that organization and project IDs are always lower case and may differ from the names in the Learning Loop which can have uppercase letters.
|
|
77
|
+
|
|
76
78
|
#### Testing
|
|
77
79
|
|
|
78
80
|
We use github actions for CI. Tests can also be executed locally by running
|
|
@@ -103,6 +105,9 @@ The detector also has a sio **upload endpoint** that can be used to upload image
|
|
|
103
105
|
- `image`: the image data in jpg format
|
|
104
106
|
- `tags`: a list of strings. If not provided the tag is `picked_by_system`
|
|
105
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)
|
|
106
111
|
|
|
107
112
|
The endpoint returns None if the upload was successful and an error message otherwise.
|
|
108
113
|
|
|
@@ -185,58 +190,52 @@ Upload a model with
|
|
|
185
190
|
The model should now be available for the format 'format_a'
|
|
186
191
|
`curl "https://learning-loop.ai/api/zauberzeug/projects/demo/models?format=format_a"`
|
|
187
192
|
|
|
188
|
-
|
|
189
|
-
|
|
193
|
+
```json
|
|
190
194
|
{
|
|
191
|
-
"models": [
|
|
192
|
-
{
|
|
193
|
-
"id": "3c20d807-f71c-40dc-a996-8a8968aa5431",
|
|
194
|
-
"version": "4.0",
|
|
195
|
-
"formats": [
|
|
196
|
-
"format_a"
|
|
197
|
-
],
|
|
198
|
-
"created": "2021-06-01T06:28:21.289092",
|
|
199
|
-
"comment": "uploaded at 2021-06-01 06:28:21.288442",
|
|
200
|
-
...
|
|
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
|
+
]
|
|
201
207
|
}
|
|
202
|
-
]
|
|
203
|
-
}
|
|
204
|
-
|
|
205
208
|
```
|
|
206
209
|
|
|
207
210
|
but not in the format_b
|
|
208
211
|
`curl "https://learning-loop.ai/api/zauberzeug/projects/demo/models?format=format_b"`
|
|
209
212
|
|
|
210
|
-
```
|
|
211
|
-
|
|
213
|
+
```json
|
|
212
214
|
{
|
|
213
|
-
"models": []
|
|
215
|
+
"models": []
|
|
214
216
|
}
|
|
215
|
-
|
|
216
217
|
```
|
|
217
218
|
|
|
218
219
|
Connect the Node to the Learning Loop by simply starting the container.
|
|
219
220
|
After a short time the converted model should be available as well.
|
|
220
221
|
`curl https://learning-loop.ai/api/zauberzeug/projects/demo/models?format=format_b`
|
|
221
222
|
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
{
|
|
225
|
-
"models": [
|
|
223
|
+
```json
|
|
226
224
|
{
|
|
227
|
-
"
|
|
228
|
-
|
|
229
|
-
"
|
|
230
|
-
"
|
|
231
|
-
"
|
|
232
|
-
|
|
233
|
-
"
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
239
|
```
|
|
241
240
|
|
|
242
241
|
## About Models (the currency between Nodes)
|
|
@@ -255,6 +254,4 @@ After a short time the converted model should be available as well.
|
|
|
255
254
|
- Nodes add properties to `model.json`, which contains all the information which are needed by subsequent nodes. These are typically the properties:
|
|
256
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)
|
|
257
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
|
|
258
|
-
```
|
|
259
|
-
````
|
|
260
257
|
|
|
@@ -23,8 +23,8 @@ You can configure connection to our Learning Loop by specifying the following en
|
|
|
23
23
|
| LOOP_USERNAME | USERNAME | Learning Loop user name | all besides Detector |
|
|
24
24
|
| LOOP_PASSWORD | PASSWORD | Learning Loop password | all besides Detector |
|
|
25
25
|
| LOOP_SSL_CERT_PATH | - | Path to the SSL certificate | all (opt.) |
|
|
26
|
-
| LOOP_ORGANIZATION | ORGANIZATION | Organization
|
|
27
|
-
| LOOP_PROJECT | PROJECT | Project
|
|
26
|
+
| LOOP_ORGANIZATION | ORGANIZATION | Organization ID | Detector |
|
|
27
|
+
| LOOP_PROJECT | PROJECT | Project ID | Detector (opt.) |
|
|
28
28
|
| MIN_UNCERTAIN_THRESHOLD | - | smallest confidence (float) at which auto-upload will happen | Detector (opt.) |
|
|
29
29
|
| MAX_UNCERTAIN_THRESHOLD | - | largest confidence (float) at which auto-upload will happen | Detector (opt.) |
|
|
30
30
|
| INFERENCE_BATCH_SIZE | - | Batch size of trainer when calculating detections | Trainer (opt.) |
|
|
@@ -33,6 +33,8 @@ You can configure connection to our Learning Loop by specifying the following en
|
|
|
33
33
|
| TRAINER_IDLE_TIMEOUT_SEC | - | Automatically shutdown trainer after timeout (in seconds) | Trainer (opt.) |
|
|
34
34
|
| USE_BACKDOOR_CONTROLS | - | Always enable backdoor controls (set to 1) | Trainer / Detector (opt.) |
|
|
35
35
|
|
|
36
|
+
Note that organization and project IDs are always lower case and may differ from the names in the Learning Loop which can have uppercase letters.
|
|
37
|
+
|
|
36
38
|
#### Testing
|
|
37
39
|
|
|
38
40
|
We use github actions for CI. Tests can also be executed locally by running
|
|
@@ -63,6 +65,9 @@ The detector also has a sio **upload endpoint** that can be used to upload image
|
|
|
63
65
|
- `image`: the image data in jpg format
|
|
64
66
|
- `tags`: a list of strings. If not provided the tag is `picked_by_system`
|
|
65
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)
|
|
66
71
|
|
|
67
72
|
The endpoint returns None if the upload was successful and an error message otherwise.
|
|
68
73
|
|
|
@@ -145,58 +150,52 @@ Upload a model with
|
|
|
145
150
|
The model should now be available for the format 'format_a'
|
|
146
151
|
`curl "https://learning-loop.ai/api/zauberzeug/projects/demo/models?format=format_a"`
|
|
147
152
|
|
|
148
|
-
|
|
149
|
-
|
|
153
|
+
```json
|
|
150
154
|
{
|
|
151
|
-
"models": [
|
|
152
|
-
{
|
|
153
|
-
"id": "3c20d807-f71c-40dc-a996-8a8968aa5431",
|
|
154
|
-
"version": "4.0",
|
|
155
|
-
"formats": [
|
|
156
|
-
"format_a"
|
|
157
|
-
],
|
|
158
|
-
"created": "2021-06-01T06:28:21.289092",
|
|
159
|
-
"comment": "uploaded at 2021-06-01 06:28:21.288442",
|
|
160
|
-
...
|
|
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
|
+
]
|
|
161
167
|
}
|
|
162
|
-
]
|
|
163
|
-
}
|
|
164
|
-
|
|
165
168
|
```
|
|
166
169
|
|
|
167
170
|
but not in the format_b
|
|
168
171
|
`curl "https://learning-loop.ai/api/zauberzeug/projects/demo/models?format=format_b"`
|
|
169
172
|
|
|
170
|
-
```
|
|
171
|
-
|
|
173
|
+
```json
|
|
172
174
|
{
|
|
173
|
-
"models": []
|
|
175
|
+
"models": []
|
|
174
176
|
}
|
|
175
|
-
|
|
176
177
|
```
|
|
177
178
|
|
|
178
179
|
Connect the Node to the Learning Loop by simply starting the container.
|
|
179
180
|
After a short time the converted model should be available as well.
|
|
180
181
|
`curl https://learning-loop.ai/api/zauberzeug/projects/demo/models?format=format_b`
|
|
181
182
|
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
{
|
|
185
|
-
"models": [
|
|
183
|
+
```json
|
|
186
184
|
{
|
|
187
|
-
"
|
|
188
|
-
|
|
189
|
-
"
|
|
190
|
-
"
|
|
191
|
-
"
|
|
192
|
-
|
|
193
|
-
"
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
199
|
```
|
|
201
200
|
|
|
202
201
|
## About Models (the currency between Nodes)
|
|
@@ -215,5 +214,3 @@ After a short time the converted model should be available as well.
|
|
|
215
214
|
- Nodes add properties to `model.json`, which contains all the information which are needed by subsequent nodes. These are typically the properties:
|
|
216
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)
|
|
217
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
|
|
218
|
-
```
|
|
219
|
-
````
|
{learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/data_exchanger.py
RENAMED
|
@@ -143,7 +143,7 @@ class DataExchanger():
|
|
|
143
143
|
logging.info('Downloading model data for uuid %s from the loop to %s..', model_uuid, target_folder)
|
|
144
144
|
|
|
145
145
|
path = f'/{context.organization}/projects/{context.project}/models/{model_uuid}/{model_format}/file'
|
|
146
|
-
response = await self.loop_communicator.get(path, requires_login=False)
|
|
146
|
+
response = await self.loop_communicator.get(path, requires_login=False, timeout=60*10)
|
|
147
147
|
if response.status_code != 200:
|
|
148
148
|
decoded_content = response.content.decode('utf-8')
|
|
149
149
|
logging.error('could not download loop/%s: %s, content: %s', path,
|
|
@@ -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:
|
|
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:
|
|
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"""
|
{learning_loop_node-0.13.6 → learning_loop_node-0.14.0}/learning_loop_node/detector/detector_node.py
RENAMED
|
@@ -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
|
|
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=
|
|
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) ->
|
|
283
|
-
|
|
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
|
|
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
|
|
307
|
+
return {'status': 'OK'}
|
|
312
308
|
|
|
313
309
|
@self.sio.event
|
|
314
310
|
def connect(sid, environ, auth) -> None:
|
|
@@ -344,16 +340,21 @@ class DetectorNode(Node):
|
|
|
344
340
|
with step_into(GLOBALS.data_folder):
|
|
345
341
|
model_symlink = 'model'
|
|
346
342
|
target_model_folder = f'models/{self.target_model.version}'
|
|
347
|
-
if
|
|
348
|
-
os.makedirs(target_model_folder)
|
|
349
|
-
await self.data_exchanger.download_model(target_model_folder,
|
|
350
|
-
Context(organization=self.organization,
|
|
351
|
-
project=self.project),
|
|
352
|
-
self.target_model.id,
|
|
353
|
-
self.detector_logic.model_format)
|
|
354
|
-
self.log.info('Downloaded model %s', self.target_model.version)
|
|
355
|
-
else:
|
|
343
|
+
if os.path.exists(target_model_folder) and len(os.listdir(target_model_folder)) > 0:
|
|
356
344
|
self.log.info('No need to download model %s (already exists)', self.target_model.version)
|
|
345
|
+
else:
|
|
346
|
+
os.makedirs(target_model_folder, exist_ok=True)
|
|
347
|
+
try:
|
|
348
|
+
await self.data_exchanger.download_model(target_model_folder,
|
|
349
|
+
Context(organization=self.organization,
|
|
350
|
+
project=self.project),
|
|
351
|
+
self.target_model.id,
|
|
352
|
+
self.detector_logic.model_format)
|
|
353
|
+
self.log.info('Downloaded model %s', self.target_model.version)
|
|
354
|
+
except Exception:
|
|
355
|
+
self.log.exception('Could not download model %s', self.target_model.version)
|
|
356
|
+
shutil.rmtree(target_model_folder, ignore_errors=True)
|
|
357
|
+
return
|
|
357
358
|
try:
|
|
358
359
|
os.unlink(model_symlink)
|
|
359
360
|
os.remove(model_symlink)
|
|
@@ -422,16 +423,23 @@ class DetectorNode(Node):
|
|
|
422
423
|
self.log_status_on_change(status.state or 'None', status)
|
|
423
424
|
|
|
424
425
|
# NOTE: sending organization and project is no longer required!
|
|
425
|
-
|
|
426
|
+
try:
|
|
427
|
+
response = await self.sio_client.call('update_detector', (self.organization, self.project, jsonable_encoder(asdict(status))))
|
|
428
|
+
except TimeoutError:
|
|
429
|
+
self.socket_connection_broken = True
|
|
430
|
+
self.log.exception('TimeoutError for sending status update (will try to reconnect):')
|
|
431
|
+
raise Exception('Status update failed due to timeout') from None
|
|
432
|
+
|
|
426
433
|
if not response:
|
|
427
434
|
self.socket_connection_broken = True
|
|
428
|
-
|
|
435
|
+
self.log.error('Status update failed (will try to reconnect): %s', response)
|
|
436
|
+
raise Exception('Status update failed: Did not receive a response from the learning loop')
|
|
429
437
|
|
|
430
438
|
socket_response = from_dict(data_class=SocketResponse, data=response)
|
|
431
439
|
if not socket_response.success:
|
|
432
440
|
self.socket_connection_broken = True
|
|
433
|
-
self.log.error('
|
|
434
|
-
raise Exception(f'
|
|
441
|
+
self.log.error('Status update failed (will try to reconnect): %s', response)
|
|
442
|
+
raise Exception(f'Status update failed. Response from learning loop: {response}')
|
|
435
443
|
|
|
436
444
|
assert socket_response.payload is not None
|
|
437
445
|
|
|
@@ -457,7 +465,7 @@ class DetectorNode(Node):
|
|
|
457
465
|
self.log.warning('Operation mode set to %s, but sync failed: %s', mode, e)
|
|
458
466
|
|
|
459
467
|
def reload(self, reason: str):
|
|
460
|
-
|
|
468
|
+
"""provide a cause for the reload"""
|
|
461
469
|
|
|
462
470
|
self.log.info('########## reloading app because %s', reason)
|
|
463
471
|
if os.path.isfile('/app/app_code/restart/restart.py'):
|
|
@@ -470,7 +478,7 @@ class DetectorNode(Node):
|
|
|
470
478
|
self.log.error('could not reload app')
|
|
471
479
|
|
|
472
480
|
async def get_detections(self,
|
|
473
|
-
raw_image:
|
|
481
|
+
raw_image: bytes,
|
|
474
482
|
camera_id: Optional[str],
|
|
475
483
|
tags: List[str],
|
|
476
484
|
source: Optional[str] = None,
|
|
@@ -482,8 +490,7 @@ class DetectorNode(Node):
|
|
|
482
490
|
It can be converted e.g. using cv2.imdecode(raw_image, cv2.IMREAD_COLOR)"""
|
|
483
491
|
|
|
484
492
|
await self.detection_lock.acquire()
|
|
485
|
-
|
|
486
|
-
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)
|
|
487
494
|
self.detection_lock.release()
|
|
488
495
|
|
|
489
496
|
fix_shape_detections(detections)
|
|
@@ -491,21 +498,40 @@ class DetectorNode(Node):
|
|
|
491
498
|
n_po, n_se = len(detections.point_detections), len(detections.segmentation_detections)
|
|
492
499
|
self.log.debug('Detected: %d boxes, %d points, %d segs, %d classes', n_bo, n_po, n_se, n_cl)
|
|
493
500
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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
|
+
))
|
|
497
506
|
elif autoupload == 'all':
|
|
498
|
-
|
|
507
|
+
background_tasks.create(self.outbox.save(raw_image, detections, tags, source, creation_date))
|
|
499
508
|
elif autoupload == 'disabled':
|
|
500
509
|
pass
|
|
501
510
|
else:
|
|
502
511
|
self.log.error('unknown autoupload value %s', autoupload)
|
|
503
512
|
return detections
|
|
504
513
|
|
|
505
|
-
async def upload_images(
|
|
506
|
-
|
|
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
|
+
|
|
507
533
|
for image in images:
|
|
508
|
-
await
|
|
534
|
+
await self.outbox.save(image, image_metadata, tags, source, creation_date, upload_priority)
|
|
509
535
|
|
|
510
536
|
def add_category_id_to_detections(self, model_info: ModelInformation, image_metadata: ImageMetadata):
|
|
511
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|