learning-loop-node 0.14.0__tar.gz → 0.16.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.14.0 → learning_loop_node-0.16.0}/PKG-INFO +3 -1
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/README.md +2 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/annotation/annotator_node.py +7 -1
- learning_loop_node-0.16.0/learning_loop_node/data_classes/__init__.py +45 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/data_classes/general.py +10 -11
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/data_classes/image_metadata.py +5 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/data_classes/training.py +3 -2
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/data_exchanger.py +3 -3
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/detector/detector_logic.py +6 -1
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/detector/detector_node.py +197 -139
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/detector/outbox.py +2 -2
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/node.py +49 -36
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/rest.py +3 -2
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/annotator/test_annotator_node.py +4 -1
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/detector/conftest.py +9 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/detector/test_outbox.py +27 -15
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/detector/test_relevance_filter.py +3 -3
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/trainer/conftest.py +2 -2
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/trainer/states/test_state_sync_confusion_matrix.py +4 -1
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/trainer/trainer_logic_generic.py +19 -4
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/trainer/trainer_node.py +4 -3
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/pyproject.toml +1 -1
- learning_loop_node-0.14.0/learning_loop_node/data_classes/__init__.py +0 -19
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/__init__.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/annotation/__init__.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/annotation/annotator_logic.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/data_classes/annotations.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/data_classes/detections.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/data_classes/socket_response.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/detector/__init__.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/detector/exceptions.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/detector/inbox_filter/__init__.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/detector/inbox_filter/cam_observation_history.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/detector/inbox_filter/relevance_filter.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/detector/rest/__init__.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/detector/rest/about.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/detector/rest/backdoor_controls.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/detector/rest/detect.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/detector/rest/model_version_control.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/detector/rest/operation_mode.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/detector/rest/outbox_mode.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/detector/rest/upload.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/enums/__init__.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/enums/annotator.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/enums/detector.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/enums/general.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/enums/trainer.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/examples/novelty_score_updater.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/globals.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/helpers/__init__.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/helpers/background_tasks.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/helpers/environment_reader.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/helpers/gdrive_downloader.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/helpers/log_conf.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/helpers/misc.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/helpers/run.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/loop_communication.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/py.typed +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/__init__.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/annotator/__init__.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/annotator/conftest.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/annotator/pytest.ini +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/detector/__init__.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/detector/inbox_filter/__init__.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/detector/inbox_filter/test_observation.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/detector/inbox_filter/test_relevance_group.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/detector/inbox_filter/test_unexpected_observations_count.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/detector/pytest.ini +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/detector/test.jpg +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/detector/test_client_communication.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/detector/test_detector_node.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/detector/testing_detector.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/general/__init__.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/general/conftest.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/general/pytest.ini +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/general/test_data/file_1.txt +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/general/test_data/file_2.txt +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/general/test_data/model.json +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/general/test_data_classes.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/general/test_downloader.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/general/test_learning_loop_node.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/test_helper.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/trainer/__init__.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/trainer/pytest.ini +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/trainer/state_helper.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/trainer/states/__init__.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/trainer/states/test_state_cleanup.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/trainer/states/test_state_detecting.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/trainer/states/test_state_download_train_model.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/trainer/states/test_state_prepare.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/trainer/states/test_state_train.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/trainer/states/test_state_upload_detections.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/trainer/states/test_state_upload_model.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/trainer/test_errors.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/trainer/test_trainer_states.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/tests/trainer/testing_trainer_logic.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/trainer/__init__.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/trainer/downloader.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/trainer/exceptions.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/trainer/executor.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/trainer/io_helpers.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/trainer/rest/__init__.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/trainer/rest/backdoor_controls.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/trainer/test_executor.py +0 -0
- {learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/trainer/trainer_logic.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.16.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
|
|
@@ -100,6 +100,8 @@ You can additionally provide the following camera parameters:
|
|
|
100
100
|
- `autoupload`: configures auto-submission to the learning loop; `filtered` (default), `all`, `disabled` (example curl parameter `-H 'autoupload: all'`)
|
|
101
101
|
- `camera-id`: a string which groups images for submission together (example curl parameter `-H 'camera-id: front_cam'`)
|
|
102
102
|
|
|
103
|
+
To use the socketio interface, the caller needs to connect to the detector node's socketio server and emit the `detect` or `batch_detect` event with the image data and image metadata. Example code can be found [in the rosys implementation](https://github.com/zauberzeug/rosys/blob/main/rosys/vision/detector_hardware.py).
|
|
104
|
+
|
|
103
105
|
The detector also has a sio **upload endpoint** that can be used to upload images and detections to the learning loop. The function receives a json dictionary, with the following entries:
|
|
104
106
|
|
|
105
107
|
- `image`: the image data in jpg format
|
|
@@ -60,6 +60,8 @@ You can additionally provide the following camera parameters:
|
|
|
60
60
|
- `autoupload`: configures auto-submission to the learning loop; `filtered` (default), `all`, `disabled` (example curl parameter `-H 'autoupload: all'`)
|
|
61
61
|
- `camera-id`: a string which groups images for submission together (example curl parameter `-H 'camera-id: front_cam'`)
|
|
62
62
|
|
|
63
|
+
To use the socketio interface, the caller needs to connect to the detector node's socketio server and emit the `detect` or `batch_detect` event with the image data and image metadata. Example code can be found [in the rosys implementation](https://github.com/zauberzeug/rosys/blob/main/rosys/vision/detector_hardware.py).
|
|
64
|
+
|
|
63
65
|
The detector also has a sio **upload endpoint** that can be used to upload images and detections to the learning loop. The function receives a json dictionary, with the following entries:
|
|
64
66
|
|
|
65
67
|
- `image`: the image data in jpg format
|
|
@@ -18,7 +18,7 @@ from .annotator_logic import AnnotatorLogic
|
|
|
18
18
|
class AnnotatorNode(Node):
|
|
19
19
|
|
|
20
20
|
def __init__(self, name: str, annotator_logic: AnnotatorLogic, uuid: Optional[str] = None):
|
|
21
|
-
super().__init__(name, uuid, 'annotation_node')
|
|
21
|
+
super().__init__(name, uuid=uuid, node_type='annotation_node')
|
|
22
22
|
self.tool = annotator_logic
|
|
23
23
|
self.histories: Dict = {}
|
|
24
24
|
annotator_logic.init(self)
|
|
@@ -35,6 +35,9 @@ class AnnotatorNode(Node):
|
|
|
35
35
|
return self.tool.logout_user(sid)
|
|
36
36
|
|
|
37
37
|
async def _handle_user_input(self, user_input_dict: Dict) -> str:
|
|
38
|
+
if not self.sio_client or not self.sio_client.connected:
|
|
39
|
+
raise ConnectionError('SocketIO client is not connected')
|
|
40
|
+
|
|
38
41
|
user_input = from_dict(data_class=UserInput, data=user_input_dict)
|
|
39
42
|
|
|
40
43
|
if user_input.data.key_up == 'Escape':
|
|
@@ -66,6 +69,9 @@ class AnnotatorNode(Node):
|
|
|
66
69
|
|
|
67
70
|
async def send_status(self):
|
|
68
71
|
|
|
72
|
+
if not self.sio_client or not self.sio_client.connected:
|
|
73
|
+
raise ConnectionError('SocketIO client is not connected')
|
|
74
|
+
|
|
69
75
|
status = AnnotationNodeStatus(
|
|
70
76
|
id=self.uuid,
|
|
71
77
|
name=self.name,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from .annotations import AnnotationData, SegmentationAnnotation, ToolOutput, UserInput
|
|
2
|
+
from .detections import (
|
|
3
|
+
BoxDetection,
|
|
4
|
+
ClassificationDetection,
|
|
5
|
+
Detections,
|
|
6
|
+
Observation,
|
|
7
|
+
Point,
|
|
8
|
+
PointDetection,
|
|
9
|
+
SegmentationDetection,
|
|
10
|
+
Shape,
|
|
11
|
+
)
|
|
12
|
+
from .general import (
|
|
13
|
+
AboutResponse,
|
|
14
|
+
AnnotationNodeStatus,
|
|
15
|
+
Category,
|
|
16
|
+
Context,
|
|
17
|
+
DetectorStatus,
|
|
18
|
+
ErrorConfiguration,
|
|
19
|
+
ModelInformation,
|
|
20
|
+
ModelVersionResponse,
|
|
21
|
+
NodeState,
|
|
22
|
+
NodeStatus,
|
|
23
|
+
)
|
|
24
|
+
from .image_metadata import ImageMetadata, ImagesMetadata
|
|
25
|
+
from .socket_response import SocketResponse
|
|
26
|
+
from .training import (
|
|
27
|
+
Errors,
|
|
28
|
+
PretrainedModel,
|
|
29
|
+
Training,
|
|
30
|
+
TrainingError,
|
|
31
|
+
TrainingOut,
|
|
32
|
+
TrainingStateData,
|
|
33
|
+
TrainingStatus,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
'AboutResponse', 'AnnotationData', 'SegmentationAnnotation', 'ToolOutput', 'UserInput',
|
|
38
|
+
'BoxDetection', 'ClassificationDetection', 'ImageMetadata', 'Observation', 'Point', 'PointDetection',
|
|
39
|
+
'SegmentationDetection', 'Shape', 'Detections',
|
|
40
|
+
'AnnotationNodeStatus', 'Category', 'Context', 'DetectorStatus', 'ErrorConfiguration',
|
|
41
|
+
'ModelInformation', 'NodeState', 'NodeStatus', 'ModelVersionResponse', 'ImagesMetadata',
|
|
42
|
+
'SocketResponse',
|
|
43
|
+
'Errors', 'PretrainedModel', 'Training',
|
|
44
|
+
'TrainingError', 'TrainingOut', 'TrainingStateData', 'TrainingStatus',
|
|
45
|
+
]
|
{learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/data_classes/general.py
RENAMED
|
@@ -148,8 +148,8 @@ class NodeState(str, Enum):
|
|
|
148
148
|
class NodeStatus():
|
|
149
149
|
id: str
|
|
150
150
|
name: str
|
|
151
|
-
state:
|
|
152
|
-
uptime:
|
|
151
|
+
state: NodeState = NodeState.Online
|
|
152
|
+
uptime: int = 0
|
|
153
153
|
errors: Dict = field(default_factory=dict)
|
|
154
154
|
capabilities: List[str] = field(default_factory=list)
|
|
155
155
|
|
|
@@ -175,14 +175,13 @@ class AnnotationNodeStatus(NodeStatus):
|
|
|
175
175
|
|
|
176
176
|
|
|
177
177
|
@dataclass(**KWONLY_SLOTS)
|
|
178
|
-
class
|
|
179
|
-
|
|
178
|
+
class DetectorStatus():
|
|
179
|
+
uuid: str
|
|
180
180
|
name: str
|
|
181
|
+
state: NodeState
|
|
182
|
+
uptime: int
|
|
181
183
|
model_format: str
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
errors:
|
|
185
|
-
|
|
186
|
-
current_model: Optional[str] = None
|
|
187
|
-
target_model: Optional[str] = None
|
|
188
|
-
operation_mode: Optional[str] = None
|
|
184
|
+
current_model: Optional[str]
|
|
185
|
+
target_model: Optional[str]
|
|
186
|
+
errors: Dict
|
|
187
|
+
operation_mode: str
|
|
@@ -35,3 +35,8 @@ class ImageMetadata():
|
|
|
35
35
|
|
|
36
36
|
def __len__(self):
|
|
37
37
|
return len(self.box_detections) + len(self.point_detections) + len(self.segmentation_detections) + len(self.classification_detections)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(**KWONLY_SLOTS)
|
|
41
|
+
class ImagesMetadata():
|
|
42
|
+
items: List[ImageMetadata] = field(default_factory=list, metadata={'description': 'List of image metadata'})
|
{learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/data_classes/training.py
RENAMED
|
@@ -8,6 +8,7 @@ from uuid import uuid4
|
|
|
8
8
|
|
|
9
9
|
from ..enums import TrainerState
|
|
10
10
|
from ..helpers.misc import create_image_folder, create_training_folder
|
|
11
|
+
|
|
11
12
|
# pylint: disable=no-name-in-module
|
|
12
13
|
from .general import Category, Context
|
|
13
14
|
|
|
@@ -52,7 +53,7 @@ class Training():
|
|
|
52
53
|
training_folder: str # f'{project_folder}/trainings/{trainings_id}'
|
|
53
54
|
|
|
54
55
|
categories: List[Category]
|
|
55
|
-
hyperparameters:
|
|
56
|
+
hyperparameters: Dict[str, Any]
|
|
56
57
|
|
|
57
58
|
training_number: int
|
|
58
59
|
training_state: str
|
|
@@ -63,7 +64,7 @@ class Training():
|
|
|
63
64
|
base_model_uuid: Optional[str] = None # model uuid to continue training (is loaded from loop)
|
|
64
65
|
|
|
65
66
|
# NOTE: these are set later after the model has been uploaded
|
|
66
|
-
image_data: Optional[List[
|
|
67
|
+
image_data: Optional[List[Dict]] = None
|
|
67
68
|
skipped_image_count: Optional[int] = None
|
|
68
69
|
model_uuid_for_detecting: Optional[str] = None # Model uuid to load from the loop after training and upload
|
|
69
70
|
|
{learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/data_exchanger.py
RENAMED
|
@@ -7,7 +7,7 @@ from glob import glob
|
|
|
7
7
|
from http import HTTPStatus
|
|
8
8
|
from io import BytesIO
|
|
9
9
|
from time import time
|
|
10
|
-
from typing import Dict, List, Optional
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
11
|
|
|
12
12
|
import aiofiles # type: ignore
|
|
13
13
|
|
|
@@ -68,7 +68,7 @@ class DataExchanger():
|
|
|
68
68
|
assert response.status_code == 200, response
|
|
69
69
|
return (response.json())['image_ids']
|
|
70
70
|
|
|
71
|
-
async def download_images_data(self, image_uuids: List[str], chunk_size: int = 100) -> List[Dict]:
|
|
71
|
+
async def download_images_data(self, image_uuids: List[str], chunk_size: int = 100) -> List[Dict[str, Any]]:
|
|
72
72
|
"""Download image annotations, tags, set and other information for the given image uuids."""
|
|
73
73
|
logging.info('Fetching annotations, tags, sets, etc. for %s images..', len(image_uuids))
|
|
74
74
|
|
|
@@ -78,7 +78,7 @@ class DataExchanger():
|
|
|
78
78
|
return []
|
|
79
79
|
|
|
80
80
|
progress_factor = 0.5 / num_image_ids # first 50% of progress is for downloading data
|
|
81
|
-
images_data: List[Dict] = []
|
|
81
|
+
images_data: List[Dict[str, Any]] = []
|
|
82
82
|
for i in range(0, num_image_ids, chunk_size):
|
|
83
83
|
self.progress = i * progress_factor
|
|
84
84
|
chunk_ids = image_uuids[i:i+chunk_size]
|
|
@@ -2,7 +2,7 @@ import logging
|
|
|
2
2
|
from abc import abstractmethod
|
|
3
3
|
from typing import List, Optional
|
|
4
4
|
|
|
5
|
-
from ..data_classes import ImageMetadata, ModelInformation
|
|
5
|
+
from ..data_classes import ImageMetadata, ImagesMetadata, ModelInformation
|
|
6
6
|
from ..globals import GLOBALS
|
|
7
7
|
from .exceptions import NodeNeedsRestartError
|
|
8
8
|
|
|
@@ -52,3 +52,8 @@ class DetectorLogic():
|
|
|
52
52
|
def evaluate(self, image: bytes) -> ImageMetadata:
|
|
53
53
|
"""Evaluate the image and return the detections.
|
|
54
54
|
The object should return empty detections if it is not initialized"""
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def batch_evaluate(self, images: List[bytes]) -> ImagesMetadata:
|
|
58
|
+
"""Evaluate a batch of images and return the detections.
|
|
59
|
+
The object should return empty detections if it is not initialized"""
|
{learning_loop_node-0.14.0 → learning_loop_node-0.16.0}/learning_loop_node/detector/detector_node.py
RENAMED
|
@@ -6,9 +6,8 @@ import subprocess
|
|
|
6
6
|
import sys
|
|
7
7
|
from dataclasses import asdict
|
|
8
8
|
from datetime import datetime
|
|
9
|
-
from typing import Dict, List, Optional
|
|
9
|
+
from typing import Dict, List, Optional
|
|
10
10
|
|
|
11
|
-
import numpy as np
|
|
12
11
|
import socketio
|
|
13
12
|
from dacite import from_dict
|
|
14
13
|
from fastapi.encoders import jsonable_encoder
|
|
@@ -18,13 +17,13 @@ from ..data_classes import (
|
|
|
18
17
|
AboutResponse,
|
|
19
18
|
Category,
|
|
20
19
|
Context,
|
|
21
|
-
|
|
20
|
+
DetectorStatus,
|
|
22
21
|
ImageMetadata,
|
|
22
|
+
ImagesMetadata,
|
|
23
23
|
ModelInformation,
|
|
24
24
|
ModelVersionResponse,
|
|
25
25
|
Shape,
|
|
26
26
|
)
|
|
27
|
-
from ..data_classes.socket_response import SocketResponse
|
|
28
27
|
from ..data_exchanger import DataExchanger, DownloadError
|
|
29
28
|
from ..enums import OperationMode, VersionMode
|
|
30
29
|
from ..globals import GLOBALS
|
|
@@ -46,7 +45,7 @@ from .rest import upload as rest_upload
|
|
|
46
45
|
class DetectorNode(Node):
|
|
47
46
|
|
|
48
47
|
def __init__(self, name: str, detector: DetectorLogic, uuid: Optional[str] = None, use_backdoor_controls: bool = False) -> None:
|
|
49
|
-
super().__init__(name, uuid, 'detector', False)
|
|
48
|
+
super().__init__(name, uuid=uuid, node_type='detector', needs_login=False, needs_sio=False)
|
|
50
49
|
self.detector_logic = detector
|
|
51
50
|
self.organization = environment_reader.organization()
|
|
52
51
|
self.project = environment_reader.project()
|
|
@@ -73,6 +72,10 @@ class DetectorNode(Node):
|
|
|
73
72
|
self.target_model: Optional[ModelInformation] = None
|
|
74
73
|
self.loop_deployment_target: Optional[ModelInformation] = None
|
|
75
74
|
|
|
75
|
+
self._regular_status_sync_cycles: int = int(os.environ.get('SYNC_CYCLES', '6'))
|
|
76
|
+
"""sync status every 6 cycles (6*10s = 1min)"""
|
|
77
|
+
self._repeat_cycles_to_next_sync: int = 0
|
|
78
|
+
|
|
76
79
|
self.include_router(rest_detect.router, tags=["detect"])
|
|
77
80
|
self.include_router(rest_upload.router, prefix="")
|
|
78
81
|
self.include_router(rest_mode.router, tags=["operation_mode"])
|
|
@@ -83,7 +86,7 @@ class DetectorNode(Node):
|
|
|
83
86
|
if use_backdoor_controls or os.environ.get('USE_BACKDOOR_CONTROLS', '0').lower() in ('1', 'true'):
|
|
84
87
|
self.include_router(backdoor_controls.router)
|
|
85
88
|
|
|
86
|
-
self.
|
|
89
|
+
self._setup_sio_server()
|
|
87
90
|
|
|
88
91
|
def get_about_response(self) -> AboutResponse:
|
|
89
92
|
return AboutResponse(
|
|
@@ -199,13 +202,7 @@ class DetectorNode(Node):
|
|
|
199
202
|
except Exception:
|
|
200
203
|
self.log.exception("error during 'shutdown'")
|
|
201
204
|
|
|
202
|
-
|
|
203
|
-
try:
|
|
204
|
-
await self._check_for_update()
|
|
205
|
-
except Exception:
|
|
206
|
-
self.log.exception("error during '_check_for_update'")
|
|
207
|
-
|
|
208
|
-
def setup_sio_server(self) -> None:
|
|
205
|
+
def _setup_sio_server(self) -> None:
|
|
209
206
|
"""The DetectorNode acts as a SocketIO server. This method sets up the server and defines the event handlers."""
|
|
210
207
|
# pylint: disable=unused-argument
|
|
211
208
|
|
|
@@ -238,8 +235,29 @@ class DetectorNode(Node):
|
|
|
238
235
|
return detection_dict
|
|
239
236
|
except Exception as e:
|
|
240
237
|
self.log.exception('could not detect via socketio')
|
|
241
|
-
with open('/tmp/bad_img_from_socket_io.jpg', 'wb') as f:
|
|
242
|
-
|
|
238
|
+
# with open('/tmp/bad_img_from_socket_io.jpg', 'wb') as f:
|
|
239
|
+
# f.write(data['image'])
|
|
240
|
+
return {'error': str(e)}
|
|
241
|
+
|
|
242
|
+
@self.sio.event
|
|
243
|
+
async def batch_detect(sid, data: Dict) -> Dict:
|
|
244
|
+
try:
|
|
245
|
+
det = await self.get_batch_detections(
|
|
246
|
+
raw_images=data['images'],
|
|
247
|
+
tags=data.get('tags', []),
|
|
248
|
+
camera_id=data.get('camera-id', None) or data.get('mac', None),
|
|
249
|
+
source=data.get('source', None),
|
|
250
|
+
autoupload=data.get('autoupload', None),
|
|
251
|
+
creation_date=data.get('creation_date', None)
|
|
252
|
+
)
|
|
253
|
+
if det is None:
|
|
254
|
+
return {'error': 'no model loaded'}
|
|
255
|
+
detection_dict = jsonable_encoder(asdict(det))
|
|
256
|
+
return detection_dict
|
|
257
|
+
except Exception as e:
|
|
258
|
+
self.log.exception('could not detect via socketio')
|
|
259
|
+
# with open('/tmp/bad_img_from_socket_io.jpg', 'wb') as f:
|
|
260
|
+
# f.write(data['image'])
|
|
243
261
|
return {'error': str(e)}
|
|
244
262
|
|
|
245
263
|
@self.sio.event
|
|
@@ -310,96 +328,22 @@ class DetectorNode(Node):
|
|
|
310
328
|
def connect(sid, environ, auth) -> None:
|
|
311
329
|
self.connected_clients.append(sid)
|
|
312
330
|
|
|
313
|
-
|
|
314
|
-
try:
|
|
315
|
-
self.log.debug('Current operation mode is %s', self.operation_mode)
|
|
316
|
-
try:
|
|
317
|
-
await self.sync_status_with_learning_loop()
|
|
318
|
-
except Exception:
|
|
319
|
-
self.log.exception('Sync with learning loop failed (could not check for updates):')
|
|
320
|
-
return
|
|
331
|
+
# ================================== Repeat Cycle, sync and model updates ==================================
|
|
321
332
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
self.
|
|
327
|
-
if self.
|
|
328
|
-
self.
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
else:
|
|
334
|
-
current_version = None
|
|
335
|
-
|
|
336
|
-
if current_version != self.target_model.version:
|
|
337
|
-
self.log.info('Current model "%s" needs to be updated to %s',
|
|
338
|
-
current_version or "-", self.target_model.version)
|
|
339
|
-
|
|
340
|
-
with step_into(GLOBALS.data_folder):
|
|
341
|
-
model_symlink = 'model'
|
|
342
|
-
target_model_folder = f'models/{self.target_model.version}'
|
|
343
|
-
if os.path.exists(target_model_folder) and len(os.listdir(target_model_folder)) > 0:
|
|
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
|
|
358
|
-
try:
|
|
359
|
-
os.unlink(model_symlink)
|
|
360
|
-
os.remove(model_symlink)
|
|
361
|
-
except Exception:
|
|
362
|
-
pass
|
|
363
|
-
os.symlink(target_model_folder, model_symlink)
|
|
364
|
-
self.log.info('Updated symlink for model to %s', os.readlink(model_symlink))
|
|
365
|
-
|
|
366
|
-
try:
|
|
367
|
-
self.detector_logic.load_model_info_and_init_model()
|
|
368
|
-
except NodeNeedsRestartError:
|
|
369
|
-
self.log.error('Node needs restart')
|
|
370
|
-
sys.exit(0)
|
|
371
|
-
except Exception:
|
|
372
|
-
self.log.exception('Could not load model, will retry download on next check')
|
|
373
|
-
shutil.rmtree(target_model_folder, ignore_errors=True)
|
|
374
|
-
return
|
|
375
|
-
try:
|
|
376
|
-
await self.sync_status_with_learning_loop()
|
|
377
|
-
except Exception:
|
|
378
|
-
pass
|
|
379
|
-
# self.reload(reason='new model installed')
|
|
380
|
-
|
|
381
|
-
except Exception as e:
|
|
382
|
-
self.log.exception('check_for_update failed')
|
|
383
|
-
msg = e.cause if isinstance(e, DownloadError) else str(e)
|
|
384
|
-
self.status.set_error('update_model', f'Could not update model: {msg}')
|
|
385
|
-
try:
|
|
386
|
-
await self.sync_status_with_learning_loop()
|
|
387
|
-
except Exception:
|
|
388
|
-
pass
|
|
389
|
-
|
|
390
|
-
async def sync_status_with_learning_loop(self) -> None:
|
|
391
|
-
"""Sync status of the detector with the Learning Loop.
|
|
392
|
-
The Learning Loop will respond with the model info of the deployment target.
|
|
393
|
-
If version_control is set to FollowLoop, the detector will update the target_model.
|
|
394
|
-
Return if the communication was successful.
|
|
395
|
-
|
|
396
|
-
Raises:
|
|
397
|
-
Exception: If the communication with the Learning Loop failed.
|
|
398
|
-
"""
|
|
333
|
+
async def on_repeat(self) -> None:
|
|
334
|
+
"""Implementation of the repeat cycle. This method is called every 10 seconds.
|
|
335
|
+
To avoid too many requests, the status is only synced every 6 cycles (1 minute)."""
|
|
336
|
+
try:
|
|
337
|
+
self._repeat_cycles_to_next_sync -= 1
|
|
338
|
+
if self._repeat_cycles_to_next_sync <= 0:
|
|
339
|
+
self._repeat_cycles_to_next_sync = self._regular_status_sync_cycles
|
|
340
|
+
await self._sync_status_with_loop()
|
|
341
|
+
await self._update_model_if_required()
|
|
342
|
+
except Exception:
|
|
343
|
+
self.log.exception("error during '_check_for_update'")
|
|
399
344
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
raise Exception('Status sync failed: not connected')
|
|
345
|
+
async def _sync_status_with_loop(self) -> None:
|
|
346
|
+
"""Sync status of the detector with the Learning Loop."""
|
|
403
347
|
|
|
404
348
|
if self.detector_logic.model_info is not None:
|
|
405
349
|
current_model = self.detector_logic.model_info.version
|
|
@@ -408,8 +352,8 @@ class DetectorNode(Node):
|
|
|
408
352
|
|
|
409
353
|
target_model_version = self.target_model.version if self.target_model else None
|
|
410
354
|
|
|
411
|
-
status =
|
|
412
|
-
|
|
355
|
+
status = DetectorStatus(
|
|
356
|
+
uuid=self.uuid,
|
|
413
357
|
name=self.name,
|
|
414
358
|
state=self.status.state,
|
|
415
359
|
errors=self.status.errors,
|
|
@@ -420,49 +364,128 @@ class DetectorNode(Node):
|
|
|
420
364
|
model_format=self.detector_logic.model_format,
|
|
421
365
|
)
|
|
422
366
|
|
|
423
|
-
self.log_status_on_change(status.state
|
|
367
|
+
self.log_status_on_change(status.state, status)
|
|
368
|
+
|
|
369
|
+
try:
|
|
370
|
+
response = await self.loop_communicator.post(
|
|
371
|
+
f'/{self.organization}/projects/{self.project}/detectors', json=jsonable_encoder(asdict(status)))
|
|
372
|
+
except Exception:
|
|
373
|
+
self.log.warning('Exception while trying to sync status with loop')
|
|
374
|
+
|
|
375
|
+
if response.status_code != 200:
|
|
376
|
+
self.log.warning('Status update failed: %s', str(response))
|
|
377
|
+
|
|
378
|
+
async def _update_model_if_required(self) -> None:
|
|
379
|
+
"""Check if a new model is available and update if necessary.
|
|
380
|
+
The Learning Loop will respond with the model info of the deployment target.
|
|
381
|
+
If version_control is set to FollowLoop or the chosen target model is not used,
|
|
382
|
+
the detector will update the target_model."""
|
|
383
|
+
try:
|
|
384
|
+
if self.operation_mode != OperationMode.Idle:
|
|
385
|
+
self.log.debug('not checking for updates; operation mode is %s', self.operation_mode)
|
|
386
|
+
return
|
|
387
|
+
|
|
388
|
+
await self._check_for_new_deployment_target()
|
|
389
|
+
|
|
390
|
+
self.status.reset_error('update_model')
|
|
391
|
+
if self.target_model is None:
|
|
392
|
+
self.log.debug('not running any updates; target model is None')
|
|
393
|
+
return
|
|
394
|
+
|
|
395
|
+
current_version = self.detector_logic.model_info.version \
|
|
396
|
+
if self.detector_logic.model_info is not None else None
|
|
397
|
+
|
|
398
|
+
if current_version != self.target_model.version:
|
|
399
|
+
self.log.info('Updating model from %s to %s',
|
|
400
|
+
current_version or "-", self.target_model.version)
|
|
401
|
+
await self._update_model(self.target_model)
|
|
402
|
+
|
|
403
|
+
except Exception as e:
|
|
404
|
+
self.log.exception('check_for_update failed')
|
|
405
|
+
msg = e.cause if isinstance(e, DownloadError) else str(e)
|
|
406
|
+
self.status.set_error('update_model', f'Could not update model: {msg}')
|
|
407
|
+
await self._sync_status_with_loop()
|
|
424
408
|
|
|
425
|
-
|
|
409
|
+
async def _check_for_new_deployment_target(self) -> None:
|
|
410
|
+
"""Ask the learning loop for the current deployment target and update self.loop_deployment_target.
|
|
411
|
+
If version_control is set to FollowLoop, also update target_model."""
|
|
426
412
|
try:
|
|
427
|
-
response = await self.
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
self.log.
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
if
|
|
434
|
-
self.
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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}')
|
|
443
|
-
|
|
444
|
-
assert socket_response.payload is not None
|
|
445
|
-
|
|
446
|
-
deployment_target_model_id = socket_response.payload['target_model_id']
|
|
447
|
-
deployment_target_model_version = socket_response.payload['target_model_version']
|
|
413
|
+
response = await self.loop_communicator.get(
|
|
414
|
+
f'/{self.organization}/projects/{self.project}/deployment/target')
|
|
415
|
+
except Exception:
|
|
416
|
+
self.log.warning('Exception while trying to check for new deployment target')
|
|
417
|
+
return
|
|
418
|
+
|
|
419
|
+
if response.status_code != 200:
|
|
420
|
+
self.log.warning('Failed to check for new deployment target: %s', str(response))
|
|
421
|
+
return
|
|
422
|
+
|
|
423
|
+
response_data = response.json()
|
|
424
|
+
|
|
425
|
+
deployment_target_uuid = response_data['model_uuid']
|
|
426
|
+
deployment_target_version = response_data['version']
|
|
448
427
|
self.loop_deployment_target = ModelInformation(organization=self.organization, project=self.project,
|
|
449
428
|
host="", categories=[],
|
|
450
|
-
id=
|
|
451
|
-
version=
|
|
429
|
+
id=deployment_target_uuid,
|
|
430
|
+
version=deployment_target_version)
|
|
452
431
|
|
|
453
432
|
if (self.version_control == VersionMode.FollowLoop and
|
|
454
433
|
self.target_model != self.loop_deployment_target):
|
|
455
|
-
|
|
434
|
+
previous_version = self.target_model.version if self.target_model else None
|
|
456
435
|
self.target_model = self.loop_deployment_target
|
|
457
|
-
self.log.info('
|
|
458
|
-
|
|
436
|
+
self.log.info('Deployment target changed from %s to %s',
|
|
437
|
+
previous_version, self.target_model.version)
|
|
438
|
+
|
|
439
|
+
async def _update_model(self, target_model: ModelInformation) -> None:
|
|
440
|
+
"""Download and install the target model.
|
|
441
|
+
On failure, the target_model will be set to None which will trigger a retry on the next check."""
|
|
442
|
+
|
|
443
|
+
with step_into(GLOBALS.data_folder):
|
|
444
|
+
target_model_folder = f'models/{target_model.version}'
|
|
445
|
+
if os.path.exists(target_model_folder) and len(os.listdir(target_model_folder)) > 0:
|
|
446
|
+
self.log.info('No need to download model. %s (already exists)', target_model.version)
|
|
447
|
+
else:
|
|
448
|
+
os.makedirs(target_model_folder, exist_ok=True)
|
|
449
|
+
try:
|
|
450
|
+
await self.data_exchanger.download_model(target_model_folder,
|
|
451
|
+
Context(organization=self.organization,
|
|
452
|
+
project=self.project),
|
|
453
|
+
target_model.id, self.detector_logic.model_format)
|
|
454
|
+
self.log.info('Downloaded model %s', target_model.version)
|
|
455
|
+
except Exception:
|
|
456
|
+
self.log.exception('Could not download model %s', target_model.version)
|
|
457
|
+
shutil.rmtree(target_model_folder, ignore_errors=True)
|
|
458
|
+
self.target_model = None
|
|
459
|
+
return
|
|
460
|
+
|
|
461
|
+
model_symlink = 'model'
|
|
462
|
+
try:
|
|
463
|
+
os.unlink(model_symlink)
|
|
464
|
+
os.remove(model_symlink)
|
|
465
|
+
except Exception:
|
|
466
|
+
pass
|
|
467
|
+
os.symlink(target_model_folder, model_symlink)
|
|
468
|
+
self.log.info('Updated symlink for model to %s', os.readlink(model_symlink))
|
|
469
|
+
|
|
470
|
+
try:
|
|
471
|
+
self.detector_logic.load_model_info_and_init_model()
|
|
472
|
+
except NodeNeedsRestartError:
|
|
473
|
+
self.log.error('Node needs restart')
|
|
474
|
+
sys.exit(0)
|
|
475
|
+
except Exception:
|
|
476
|
+
self.log.exception('Could not load model, will retry download on next check')
|
|
477
|
+
shutil.rmtree(target_model_folder, ignore_errors=True)
|
|
478
|
+
self.target_model = None
|
|
479
|
+
return
|
|
480
|
+
|
|
481
|
+
await self._sync_status_with_loop()
|
|
482
|
+
# self.reload(reason='new model installed')
|
|
483
|
+
|
|
484
|
+
# ================================== API Implementations ==================================
|
|
459
485
|
|
|
460
486
|
async def set_operation_mode(self, mode: OperationMode):
|
|
461
487
|
self.operation_mode = mode
|
|
462
|
-
|
|
463
|
-
await self.sync_status_with_learning_loop()
|
|
464
|
-
except Exception as e:
|
|
465
|
-
self.log.warning('Operation mode set to %s, but sync failed: %s', mode, e)
|
|
488
|
+
await self._sync_status_with_loop()
|
|
466
489
|
|
|
467
490
|
def reload(self, reason: str):
|
|
468
491
|
"""provide a cause for the reload"""
|
|
@@ -479,13 +502,14 @@ class DetectorNode(Node):
|
|
|
479
502
|
|
|
480
503
|
async def get_detections(self,
|
|
481
504
|
raw_image: bytes,
|
|
482
|
-
camera_id: Optional[str],
|
|
483
505
|
tags: List[str],
|
|
506
|
+
*,
|
|
507
|
+
camera_id: Optional[str] = None,
|
|
484
508
|
source: Optional[str] = None,
|
|
485
509
|
autoupload: Optional[str] = None,
|
|
486
510
|
creation_date: Optional[str] = None) -> ImageMetadata:
|
|
487
511
|
""" Main processing function for the detector node when an image is received via REST or SocketIO.
|
|
488
|
-
This function infers the detections from the image, cares about uploading to the loop and returns the detections as
|
|
512
|
+
This function infers the detections from the image, cares about uploading to the loop and returns the detections as ImageMetadata object.
|
|
489
513
|
Note: raw_image is a numpy array of type uint8, but not in the correct shape!
|
|
490
514
|
It can be converted e.g. using cv2.imdecode(raw_image, cv2.IMREAD_COLOR)"""
|
|
491
515
|
|
|
@@ -511,6 +535,40 @@ class DetectorNode(Node):
|
|
|
511
535
|
self.log.error('unknown autoupload value %s', autoupload)
|
|
512
536
|
return detections
|
|
513
537
|
|
|
538
|
+
async def get_batch_detections(self,
|
|
539
|
+
raw_images: List[bytes],
|
|
540
|
+
tags: List[str],
|
|
541
|
+
*,
|
|
542
|
+
camera_id: Optional[str] = None,
|
|
543
|
+
source: Optional[str] = None,
|
|
544
|
+
autoupload: Optional[str] = None,
|
|
545
|
+
creation_date: Optional[str] = None) -> ImagesMetadata:
|
|
546
|
+
""" Processing function for the detector node when a a batch inference is requested via SocketIO.
|
|
547
|
+
This function infers the detections from all images, cares about uploading to the loop and returns the detections as a list of ImageMetadata."""
|
|
548
|
+
|
|
549
|
+
await self.detection_lock.acquire()
|
|
550
|
+
all_detections = await run.io_bound(self.detector_logic.batch_evaluate, raw_images)
|
|
551
|
+
self.detection_lock.release()
|
|
552
|
+
|
|
553
|
+
for detections, raw_image in zip(all_detections.items, raw_images):
|
|
554
|
+
fix_shape_detections(detections)
|
|
555
|
+
n_bo, n_cl = len(detections.box_detections), len(detections.classification_detections)
|
|
556
|
+
n_po, n_se = len(detections.point_detections), len(detections.segmentation_detections)
|
|
557
|
+
self.log.debug('Detected: %d boxes, %d points, %d segs, %d classes', n_bo, n_po, n_se, n_cl)
|
|
558
|
+
|
|
559
|
+
autoupload = autoupload or 'filtered'
|
|
560
|
+
if autoupload == 'filtered' and camera_id is not None:
|
|
561
|
+
background_tasks.create(self.relevance_filter.may_upload_detections(
|
|
562
|
+
detections, camera_id, raw_image, tags, source, creation_date
|
|
563
|
+
))
|
|
564
|
+
elif autoupload == 'all':
|
|
565
|
+
background_tasks.create(self.outbox.save(raw_image, detections, tags, source, creation_date))
|
|
566
|
+
elif autoupload == 'disabled':
|
|
567
|
+
pass
|
|
568
|
+
else:
|
|
569
|
+
self.log.error('unknown autoupload value %s', autoupload)
|
|
570
|
+
return all_detections
|
|
571
|
+
|
|
514
572
|
async def upload_images(
|
|
515
573
|
self, *,
|
|
516
574
|
images: List[bytes],
|