learning-loop-node 0.9.2__tar.gz → 0.10.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.9.2 → learning_loop_node-0.10.0}/PKG-INFO +1 -1
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/__init__.py +2 -3
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/annotation/annotator_logic.py +2 -2
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/annotation/annotator_node.py +16 -15
- learning_loop_node-0.10.0/learning_loop_node/data_classes/__init__.py +19 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/data_classes/detections.py +7 -2
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/data_classes/general.py +8 -5
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/data_classes/training.py +49 -21
- learning_loop_node-0.10.0/learning_loop_node/data_exchanger.py +172 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/detector_logic.py +0 -2
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/detector_node.py +14 -15
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/inbox_filter/cam_observation_history.py +4 -7
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/outbox.py +0 -1
- learning_loop_node-0.10.0/learning_loop_node/detector/rest/about.py +25 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/tests/conftest.py +4 -1
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/tests/test_client_communication.py +18 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/tests/test_outbox.py +2 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/tests/testing_detector.py +0 -7
- learning_loop_node-0.10.0/learning_loop_node/globals.py +8 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/helpers/gdrive_downloader.py +1 -1
- learning_loop_node-0.10.0/learning_loop_node/helpers/misc.py +216 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/loop_communication.py +57 -25
- learning_loop_node-0.10.0/learning_loop_node/node.py +179 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/tests/test_downloader.py +8 -7
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/tests/test_executor.py +14 -11
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/tests/test_helper.py +3 -5
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/trainer/downloader.py +1 -1
- learning_loop_node-0.10.0/learning_loop_node/trainer/executor.py +109 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/trainer/io_helpers.py +66 -9
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/trainer/rest/backdoor_controls.py +10 -5
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/trainer/rest/controls.py +3 -1
- learning_loop_node-0.10.0/learning_loop_node/trainer/tests/conftest.py +66 -0
- learning_loop_node-0.10.0/learning_loop_node/trainer/tests/states/__init__.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/trainer/tests/states/test_state_cleanup.py +5 -3
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/trainer/tests/states/test_state_detecting.py +23 -20
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/trainer/tests/states/test_state_download_train_model.py +18 -12
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/trainer/tests/states/test_state_prepare.py +13 -12
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/trainer/tests/states/test_state_sync_confusion_matrix.py +21 -18
- learning_loop_node-0.10.0/learning_loop_node/trainer/tests/states/test_state_train.py +69 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/trainer/tests/states/test_state_upload_detections.py +34 -32
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/trainer/tests/states/test_state_upload_model.py +22 -20
- learning_loop_node-0.10.0/learning_loop_node/trainer/tests/test_errors.py +45 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/trainer/tests/test_trainer_states.py +4 -5
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/trainer/tests/testing_trainer_logic.py +25 -30
- learning_loop_node-0.10.0/learning_loop_node/trainer/trainer_logic.py +187 -0
- learning_loop_node-0.10.0/learning_loop_node/trainer/trainer_logic_generic.py +495 -0
- learning_loop_node-0.10.0/learning_loop_node/trainer/trainer_node.py +94 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/pyproject.toml +1 -1
- learning_loop_node-0.9.2/learning_loop_node/converter/converter_logic.py +0 -68
- learning_loop_node-0.9.2/learning_loop_node/converter/converter_node.py +0 -125
- learning_loop_node-0.9.2/learning_loop_node/converter/tests/test_converter.py +0 -55
- learning_loop_node-0.9.2/learning_loop_node/data_classes/__init__.py +0 -12
- learning_loop_node-0.9.2/learning_loop_node/data_exchanger.py +0 -226
- learning_loop_node-0.9.2/learning_loop_node/detector/__init__.py +0 -1
- learning_loop_node-0.9.2/learning_loop_node/globals.py +0 -8
- learning_loop_node-0.9.2/learning_loop_node/helpers/misc.py +0 -109
- learning_loop_node-0.9.2/learning_loop_node/node.py +0 -252
- learning_loop_node-0.9.2/learning_loop_node/trainer/executor.py +0 -105
- learning_loop_node-0.9.2/learning_loop_node/trainer/tests/conftest.py +0 -75
- learning_loop_node-0.9.2/learning_loop_node/trainer/tests/states/test_state_train.py +0 -70
- learning_loop_node-0.9.2/learning_loop_node/trainer/tests/test_errors.py +0 -37
- learning_loop_node-0.9.2/learning_loop_node/trainer/trainer_logic.py +0 -697
- learning_loop_node-0.9.2/learning_loop_node/trainer/trainer_node.py +0 -173
- learning_loop_node-0.9.2/learning_loop_node/trainer/training_syncronizer.py +0 -52
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/README.md +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/annotation/__init__.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/annotation/tests/test_annotator_node.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/conftest.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/data_classes/annotations.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/data_classes/socket_response.py +0 -0
- {learning_loop_node-0.9.2/learning_loop_node/converter → learning_loop_node-0.10.0/learning_loop_node/detector}/__init__.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/inbox_filter/__init__.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/inbox_filter/relevance_filter.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/inbox_filter/tests/test_observation.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/inbox_filter/tests/test_relevance_group.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/inbox_filter/tests/test_unexpected_observations_count.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/rest/__init__.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/rest/backdoor_controls.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/rest/detect.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/rest/operation_mode.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/rest/upload.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/tests/__init__.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/tests/test.jpg +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/tests/test_relevance_filter.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/examples/novelty_score_updater.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/helpers/__init__.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/helpers/environment_reader.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/helpers/log_conf.py +0 -0
- /learning_loop_node-0.9.2/learning_loop_node/tests/__init__.py → /learning_loop_node-0.10.0/learning_loop_node/py.typed +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/pytest.ini +0 -0
- {learning_loop_node-0.9.2/learning_loop_node/trainer → learning_loop_node-0.10.0/learning_loop_node/tests}/__init__.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/tests/conftest.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/tests/test_data/file_1.txt +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/tests/test_data/file_2.txt +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/tests/test_data/model.json +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/tests/test_data_classes.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/tests/test_learning_loop_node.py +0 -0
- {learning_loop_node-0.9.2/learning_loop_node/trainer/rest → learning_loop_node-0.10.0/learning_loop_node/trainer}/__init__.py +0 -0
- {learning_loop_node-0.9.2/learning_loop_node/trainer/tests → learning_loop_node-0.10.0/learning_loop_node/trainer/rest}/__init__.py +0 -0
- {learning_loop_node-0.9.2/learning_loop_node/trainer/tests/states → learning_loop_node-0.10.0/learning_loop_node/trainer/tests}/__init__.py +0 -0
- {learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/trainer/tests/state_helper.py +0 -0
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import os
|
|
3
|
-
import sys
|
|
4
2
|
|
|
5
|
-
from .converter.converter_node import ConverterNode
|
|
6
3
|
# from . import log_conf
|
|
7
4
|
from .detector.detector_logic import DetectorLogic
|
|
8
5
|
from .detector.detector_node import DetectorNode
|
|
9
6
|
from .globals import GLOBALS
|
|
10
7
|
from .trainer.trainer_node import TrainerNode
|
|
11
8
|
|
|
9
|
+
__all__ = ['TrainerNode', 'DetectorNode', 'DetectorLogic', 'GLOBALS']
|
|
10
|
+
|
|
12
11
|
logging.info('>>>>>>>>>>>>>>>>>> LOOP INITIALIZED <<<<<<<<<<<<<<<<<<<<<<<')
|
|
@@ -7,10 +7,10 @@ from ..node import Node
|
|
|
7
7
|
|
|
8
8
|
class AnnotatorLogic():
|
|
9
9
|
|
|
10
|
-
def __init__(self):
|
|
10
|
+
def __init__(self) -> None:
|
|
11
11
|
self._node: Optional[Node] = None
|
|
12
12
|
|
|
13
|
-
def init(self, node: Node):
|
|
13
|
+
def init(self, node: Node) -> None:
|
|
14
14
|
self._node = node
|
|
15
15
|
|
|
16
16
|
@abstractmethod
|
|
@@ -8,7 +8,7 @@ from socketio import AsyncClient
|
|
|
8
8
|
from ..data_classes import AnnotationNodeStatus, Context, NodeState, UserInput
|
|
9
9
|
from ..data_classes.socket_response import SocketResponse
|
|
10
10
|
from ..data_exchanger import DataExchanger
|
|
11
|
-
from ..helpers.misc import create_image_folder
|
|
11
|
+
from ..helpers.misc import create_image_folder, create_project_folder
|
|
12
12
|
from ..node import Node
|
|
13
13
|
from .annotator_logic import AnnotatorLogic
|
|
14
14
|
|
|
@@ -18,10 +18,11 @@ 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)
|
|
21
|
+
super().__init__(name, uuid, 'annotation_node')
|
|
22
22
|
self.tool = annotator_logic
|
|
23
23
|
self.histories: Dict = {}
|
|
24
24
|
annotator_logic.init(self)
|
|
25
|
+
self.status_sent = False
|
|
25
26
|
|
|
26
27
|
def register_sio_events(self, sio_client: AsyncClient):
|
|
27
28
|
|
|
@@ -50,8 +51,6 @@ class AnnotatorNode(Node):
|
|
|
50
51
|
raise
|
|
51
52
|
|
|
52
53
|
if tool_result.annotation:
|
|
53
|
-
if not self.sio_is_initialized():
|
|
54
|
-
raise Exception('Socket client waas not initialized')
|
|
55
54
|
await self.sio_client.call('update_segmentation_annotation', (user_input.data.context.organization,
|
|
56
55
|
user_input.data.context.project,
|
|
57
56
|
jsonable_encoder(asdict(tool_result.annotation))), timeout=30)
|
|
@@ -67,6 +66,9 @@ class AnnotatorNode(Node):
|
|
|
67
66
|
return self.histories.setdefault(frontend_id, self.tool.create_empty_history())
|
|
68
67
|
|
|
69
68
|
async def send_status(self):
|
|
69
|
+
if self.status_sent:
|
|
70
|
+
return
|
|
71
|
+
|
|
70
72
|
status = AnnotationNodeStatus(
|
|
71
73
|
id=self.uuid,
|
|
72
74
|
name=self.name,
|
|
@@ -75,28 +77,27 @@ class AnnotatorNode(Node):
|
|
|
75
77
|
)
|
|
76
78
|
|
|
77
79
|
self.log.info(f'Sending status {status}')
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
try:
|
|
81
|
+
result = await self.sio_client.call('update_annotation_node', jsonable_encoder(asdict(status)), timeout=10)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
self.log.error(f'Error for updating: {str(e)}')
|
|
84
|
+
return
|
|
85
|
+
|
|
81
86
|
assert isinstance(result, Dict)
|
|
82
87
|
response = from_dict(data_class=SocketResponse, data=result)
|
|
83
88
|
|
|
84
89
|
if not response.success:
|
|
85
90
|
self.log.error(f'Error for updating: Response from loop was : {asdict(response)}')
|
|
91
|
+
else:
|
|
92
|
+
self.status_sent = True
|
|
86
93
|
|
|
87
94
|
async def download_image(self, context: Context, uuid: str):
|
|
88
|
-
project_folder =
|
|
95
|
+
project_folder = create_project_folder(context)
|
|
89
96
|
images_folder = create_image_folder(project_folder)
|
|
90
97
|
|
|
91
98
|
downloader = DataExchanger(context=context, loop_communicator=self.loop_communicator)
|
|
92
99
|
await downloader.download_images([uuid], images_folder)
|
|
93
100
|
|
|
94
|
-
async def get_state(self):
|
|
95
|
-
return NodeState.Online
|
|
96
|
-
|
|
97
|
-
def get_node_type(self):
|
|
98
|
-
return 'annotation_node'
|
|
99
|
-
|
|
100
101
|
async def on_startup(self):
|
|
101
102
|
pass
|
|
102
103
|
|
|
@@ -104,4 +105,4 @@ class AnnotatorNode(Node):
|
|
|
104
105
|
pass
|
|
105
106
|
|
|
106
107
|
async def on_repeat(self):
|
|
107
|
-
|
|
108
|
+
await self.send_status()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .annotations import AnnotationData, AnnotationEventType, SegmentationAnnotation, ToolOutput, UserInput
|
|
2
|
+
from .detections import (BoxDetection, ClassificationDetection, Detections, Observation, Point, PointDetection,
|
|
3
|
+
SegmentationDetection, Shape)
|
|
4
|
+
from .general import (AnnotationNodeStatus, Category, CategoryType, Context, DetectionStatus, ErrorConfiguration,
|
|
5
|
+
ModelInformation, NodeState, NodeStatus)
|
|
6
|
+
from .socket_response import SocketResponse
|
|
7
|
+
from .training import (Errors, Hyperparameter, Model, PretrainedModel, TrainerState, Training, TrainingData,
|
|
8
|
+
TrainingError, TrainingOut, TrainingStateData, TrainingStatus)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
'AnnotationData', 'AnnotationEventType', 'SegmentationAnnotation', 'ToolOutput', 'UserInput',
|
|
12
|
+
'BoxDetection', 'ClassificationDetection', 'Detections', 'Observation', 'Point', 'PointDetection',
|
|
13
|
+
'SegmentationDetection', 'Shape',
|
|
14
|
+
'AnnotationNodeStatus', 'Category', 'CategoryType', 'Context', 'DetectionStatus', 'ErrorConfiguration',
|
|
15
|
+
'ModelInformation', 'NodeState', 'NodeStatus',
|
|
16
|
+
'SocketResponse',
|
|
17
|
+
'Errors', 'Hyperparameter', 'Model', 'PretrainedModel', 'TrainerState', 'Training', 'TrainingData',
|
|
18
|
+
'TrainingError', 'TrainingOut', 'TrainingStateData', 'TrainingStatus',
|
|
19
|
+
]
|
{learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/data_classes/detections.py
RENAMED
|
@@ -13,8 +13,11 @@ KWONLY_SLOTS = {'kw_only': True, 'slots': True} if sys.version_info >= (3, 10) e
|
|
|
13
13
|
|
|
14
14
|
@dataclass(**KWONLY_SLOTS)
|
|
15
15
|
class BoxDetection():
|
|
16
|
+
"""Coordinates according to COCO format. x,y is the top left corner of the box.
|
|
17
|
+
x increases to the right, y increases downwards.
|
|
18
|
+
"""
|
|
16
19
|
category_name: str
|
|
17
|
-
x: int
|
|
20
|
+
x: int
|
|
18
21
|
y: int
|
|
19
22
|
width: int
|
|
20
23
|
height: int
|
|
@@ -47,6 +50,8 @@ class BoxDetection():
|
|
|
47
50
|
|
|
48
51
|
@dataclass(**KWONLY_SLOTS)
|
|
49
52
|
class PointDetection():
|
|
53
|
+
"""Coordinates according to COCO format. x,y is the center of the point.
|
|
54
|
+
x increases to the right, y increases downwards."""
|
|
50
55
|
category_name: str
|
|
51
56
|
x: float
|
|
52
57
|
y: float
|
|
@@ -111,7 +116,7 @@ class Detections():
|
|
|
111
116
|
point_detections: List[PointDetection] = field(default_factory=list)
|
|
112
117
|
segmentation_detections: List[SegmentationDetection] = field(default_factory=list)
|
|
113
118
|
classification_detections: List[ClassificationDetection] = field(default_factory=list)
|
|
114
|
-
tags:
|
|
119
|
+
tags: List[str] = field(default_factory=list)
|
|
115
120
|
date: Optional[str] = field(default_factory=current_datetime)
|
|
116
121
|
image_id: Optional[str] = None # used for detection of trainers
|
|
117
122
|
|
{learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/data_classes/general.py
RENAMED
|
@@ -34,10 +34,6 @@ class Category():
|
|
|
34
34
|
return [from_dict(data_class=Category, data=value) for value in values]
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def create_category(identifier: str, name: str, ctype: Union[CategoryType, str]): # TODO: This is probably unused
|
|
38
|
-
return Category(id=identifier, name=name, description='', hotkey='', color='', type=ctype, point_size=None)
|
|
39
|
-
|
|
40
|
-
|
|
41
37
|
@dataclass(**KWONLY_SLOTS)
|
|
42
38
|
class Context():
|
|
43
39
|
organization: str
|
|
@@ -57,6 +53,7 @@ class ModelInformation():
|
|
|
57
53
|
categories: List[Category]
|
|
58
54
|
resolution: Optional[int] = None
|
|
59
55
|
model_root_path: Optional[str] = None
|
|
56
|
+
model_size: Optional[str] = None
|
|
60
57
|
|
|
61
58
|
@property
|
|
62
59
|
def context(self):
|
|
@@ -64,6 +61,8 @@ class ModelInformation():
|
|
|
64
61
|
|
|
65
62
|
@staticmethod
|
|
66
63
|
def load_from_disk(model_root_path: str) -> Optional['ModelInformation']:
|
|
64
|
+
"""Load model.json from model_root_path and return ModelInformation object.
|
|
65
|
+
"""
|
|
67
66
|
model_info_file_path = f'{model_root_path}/model.json'
|
|
68
67
|
if not os.path.exists(model_info_file_path):
|
|
69
68
|
logging.warning(f"could not find model information file '{model_info_file_path}'")
|
|
@@ -90,6 +89,10 @@ class ModelInformation():
|
|
|
90
89
|
del self_as_dict['model_root_path']
|
|
91
90
|
f.write(json.dumps(self_as_dict))
|
|
92
91
|
|
|
92
|
+
@staticmethod
|
|
93
|
+
def from_dict(data: Dict) -> 'ModelInformation':
|
|
94
|
+
return from_dict(ModelInformation, data=data)
|
|
95
|
+
|
|
93
96
|
|
|
94
97
|
@dataclass(**KWONLY_SLOTS)
|
|
95
98
|
class ErrorConfiguration():
|
|
@@ -117,7 +120,7 @@ class NodeState(str, Enum):
|
|
|
117
120
|
class NodeStatus():
|
|
118
121
|
id: str
|
|
119
122
|
name: str
|
|
120
|
-
state: Optional[NodeState] = NodeState.
|
|
123
|
+
state: Optional[NodeState] = NodeState.Online
|
|
121
124
|
uptime: Optional[int] = 0
|
|
122
125
|
errors: Dict = field(default_factory=dict)
|
|
123
126
|
capabilities: List[str] = field(default_factory=list)
|
{learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/data_classes/training.py
RENAMED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
|
|
2
2
|
import sys
|
|
3
|
+
import time
|
|
3
4
|
from dataclasses import dataclass, field
|
|
4
5
|
from enum import Enum
|
|
5
|
-
from
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict, List, Optional
|
|
6
8
|
|
|
7
9
|
# pylint: disable=no-name-in-module
|
|
8
10
|
from .general import Category, Context
|
|
@@ -16,6 +18,14 @@ class Hyperparameter():
|
|
|
16
18
|
flip_rl: bool
|
|
17
19
|
flip_ud: bool
|
|
18
20
|
|
|
21
|
+
@staticmethod
|
|
22
|
+
def from_data(data: Dict):
|
|
23
|
+
return Hyperparameter(
|
|
24
|
+
resolution=data['resolution'],
|
|
25
|
+
flip_rl=data.get('flip_rl', False),
|
|
26
|
+
flip_ud=data.get('flip_ud', False)
|
|
27
|
+
)
|
|
28
|
+
|
|
19
29
|
|
|
20
30
|
@dataclass(**KWONLY_SLOTS)
|
|
21
31
|
class TrainingData():
|
|
@@ -41,14 +51,15 @@ class PretrainedModel():
|
|
|
41
51
|
description: str
|
|
42
52
|
|
|
43
53
|
|
|
44
|
-
class
|
|
54
|
+
class TrainerState(str, Enum):
|
|
55
|
+
Idle = 'idle'
|
|
45
56
|
Initialized = 'initialized'
|
|
46
57
|
Preparing = 'preparing'
|
|
47
58
|
DataDownloading = 'data_downloading'
|
|
48
59
|
DataDownloaded = 'data_downloaded'
|
|
49
60
|
TrainModelDownloading = 'train_model_downloading'
|
|
50
61
|
TrainModelDownloaded = 'train_model_downloaded'
|
|
51
|
-
TrainingRunning = '
|
|
62
|
+
TrainingRunning = 'running'
|
|
52
63
|
TrainingFinished = 'training_finished'
|
|
53
64
|
ConfusionMatrixSyncing = 'confusion_matrix_syncing'
|
|
54
65
|
ConfusionMatrixSynced = 'confusion_matrix_synced'
|
|
@@ -62,9 +73,9 @@ class TrainingState(str, Enum):
|
|
|
62
73
|
|
|
63
74
|
@dataclass(**KWONLY_SLOTS)
|
|
64
75
|
class TrainingStatus():
|
|
65
|
-
id: str #
|
|
76
|
+
id: str # NOTE this must not be changed, but tests wont detect a change -> update tests!
|
|
66
77
|
name: str
|
|
67
|
-
state:
|
|
78
|
+
state: Optional[str]
|
|
68
79
|
errors: Optional[Dict]
|
|
69
80
|
uptime: Optional[float]
|
|
70
81
|
progress: Optional[float]
|
|
@@ -77,13 +88,13 @@ class TrainingStatus():
|
|
|
77
88
|
architecture: Optional[str] = None
|
|
78
89
|
context: Optional[Context] = None
|
|
79
90
|
|
|
80
|
-
def short_str(self):
|
|
91
|
+
def short_str(self) -> str:
|
|
81
92
|
prgr = f'{self.progress * 100:.0f}%' if self.progress else ''
|
|
82
93
|
trtesk = f'{self.train_image_count}/{self.test_image_count}/{self.skipped_image_count}' if self.train_image_count else 'n.a.'
|
|
83
94
|
cntxt = f'{self.context.organization}/{self.context.project}' if self.context else ''
|
|
84
95
|
hyps = f'({self.hyperparameters})' if self.hyperparameters else ''
|
|
85
96
|
arch = f'.{self.architecture} - ' if self.architecture else ''
|
|
86
|
-
return f'[{str(self.state)} {prgr}. {self.name}({self.id}). Tr/Ts/Tsk: {trtesk} {cntxt}{arch}{hyps}]'
|
|
97
|
+
return f'[{str(self.state).rsplit(".", maxsplit=1)[-1]} {prgr}. {self.name}({self.id}). Tr/Ts/Tsk: {trtesk} {cntxt}{arch}{hyps}]'
|
|
87
98
|
|
|
88
99
|
|
|
89
100
|
@dataclass(**KWONLY_SLOTS)
|
|
@@ -91,21 +102,35 @@ class Training():
|
|
|
91
102
|
id: str
|
|
92
103
|
context: Context
|
|
93
104
|
|
|
94
|
-
project_folder: str
|
|
95
|
-
images_folder: str
|
|
96
|
-
training_folder: str
|
|
105
|
+
project_folder: str # f'{GLOBALS.data_folder}/{context.organization}/{context.project}'
|
|
106
|
+
images_folder: str # f'{project_folder}/images'
|
|
107
|
+
training_folder: str # f'{project_folder}/trainings/{trainings_id}'
|
|
108
|
+
start_time: float = field(default_factory=time.time)
|
|
109
|
+
|
|
110
|
+
# model uuid to download (to continue training) | is not a uuid when training from scratch (blank or pt-name from provided_pretrained_models->name)
|
|
111
|
+
base_model_uuid_or_name: Optional[str] = None
|
|
97
112
|
|
|
98
|
-
base_model_id: Optional[str] = None
|
|
99
113
|
data: Optional[TrainingData] = None
|
|
100
114
|
training_number: Optional[int] = None
|
|
101
|
-
training_state: Optional[
|
|
102
|
-
|
|
115
|
+
training_state: Optional[str] = None
|
|
116
|
+
model_uuid_for_detecting: Optional[str] = None
|
|
103
117
|
hyperparameters: Optional[Dict] = None
|
|
104
118
|
|
|
119
|
+
@property
|
|
120
|
+
def training_folder_path(self) -> Path:
|
|
121
|
+
return Path(self.training_folder)
|
|
122
|
+
|
|
123
|
+
def set_values_from_data(self, data: Dict) -> None:
|
|
124
|
+
self.data = TrainingData(categories=Category.from_list(data['categories']))
|
|
125
|
+
self.data.hyperparameter = Hyperparameter.from_data(data=data)
|
|
126
|
+
self.training_number = data['training_number']
|
|
127
|
+
self.base_model_uuid_or_name = data['id']
|
|
128
|
+
self.training_state = TrainerState.Initialized
|
|
129
|
+
|
|
105
130
|
|
|
106
131
|
@dataclass(**KWONLY_SLOTS)
|
|
107
132
|
class TrainingOut():
|
|
108
|
-
confusion_matrix: Optional[Dict] = None
|
|
133
|
+
confusion_matrix: Optional[Dict] = None # This is actually just class-wise metrics
|
|
109
134
|
train_image_count: Optional[int] = None
|
|
110
135
|
test_image_count: Optional[int] = None
|
|
111
136
|
trainer_id: Optional[str] = None
|
|
@@ -113,9 +138,9 @@ class TrainingOut():
|
|
|
113
138
|
|
|
114
139
|
|
|
115
140
|
@dataclass(**KWONLY_SLOTS)
|
|
116
|
-
class
|
|
117
|
-
confusion_matrix:
|
|
118
|
-
meta_information:
|
|
141
|
+
class TrainingStateData():
|
|
142
|
+
confusion_matrix: Dict = field(default_factory=dict)
|
|
143
|
+
meta_information: Dict = field(default_factory=dict)
|
|
119
144
|
|
|
120
145
|
|
|
121
146
|
@dataclass(**KWONLY_SLOTS)
|
|
@@ -130,8 +155,8 @@ class Model():
|
|
|
130
155
|
|
|
131
156
|
|
|
132
157
|
class Errors():
|
|
133
|
-
def __init__(self):
|
|
134
|
-
self._errors: Dict = {}
|
|
158
|
+
def __init__(self) -> None:
|
|
159
|
+
self._errors: Dict[str, str] = {}
|
|
135
160
|
|
|
136
161
|
def set(self, key: str, value: str):
|
|
137
162
|
self._errors[key] = value
|
|
@@ -140,7 +165,7 @@ class Errors():
|
|
|
140
165
|
def errors(self) -> Dict:
|
|
141
166
|
return self._errors
|
|
142
167
|
|
|
143
|
-
def reset(self, key: str):
|
|
168
|
+
def reset(self, key: str) -> None:
|
|
144
169
|
try:
|
|
145
170
|
del self._errors[key]
|
|
146
171
|
except AttributeError:
|
|
@@ -148,7 +173,7 @@ class Errors():
|
|
|
148
173
|
except KeyError:
|
|
149
174
|
pass
|
|
150
175
|
|
|
151
|
-
def reset_all(self):
|
|
176
|
+
def reset_all(self) -> None:
|
|
152
177
|
self._errors = {}
|
|
153
178
|
|
|
154
179
|
def has_error_for(self, key: str) -> bool:
|
|
@@ -162,3 +187,6 @@ class TrainingError(Exception):
|
|
|
162
187
|
def __init__(self, cause: str, *args: object) -> None:
|
|
163
188
|
super().__init__(*args)
|
|
164
189
|
self.cause = cause
|
|
190
|
+
|
|
191
|
+
def __str__(self) -> str:
|
|
192
|
+
return f'TrainingError: {self.cause}'
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import zipfile
|
|
6
|
+
from glob import glob
|
|
7
|
+
from http import HTTPStatus
|
|
8
|
+
from io import BytesIO
|
|
9
|
+
from time import time
|
|
10
|
+
from typing import Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
import aiofiles # type: ignore
|
|
13
|
+
|
|
14
|
+
from .data_classes import Context
|
|
15
|
+
from .helpers.misc import create_resource_paths, create_task, is_valid_image
|
|
16
|
+
from .loop_communication import LoopCommunicator
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DownloadError(Exception):
|
|
20
|
+
|
|
21
|
+
def __init__(self, cause: str, *args: object) -> None:
|
|
22
|
+
super().__init__(*args)
|
|
23
|
+
self.cause = cause
|
|
24
|
+
|
|
25
|
+
def __str__(self) -> str:
|
|
26
|
+
return f'DownloadError: {self.cause}'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class DataExchanger():
|
|
30
|
+
|
|
31
|
+
def __init__(self, context: Optional[Context], loop_communicator: LoopCommunicator):
|
|
32
|
+
"""Exchanges data with the learning loop via the loop_communicator (rest api).
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
context (Optional[Context]): The context of the node. This is the organization and project name.
|
|
36
|
+
loop_communicator (LoopCommunicator): The loop_communicator to use for communication with the learning loop.
|
|
37
|
+
|
|
38
|
+
Note:
|
|
39
|
+
The context can be set later with the set_context method.
|
|
40
|
+
"""
|
|
41
|
+
self.set_context(context)
|
|
42
|
+
self.progress = 0.0
|
|
43
|
+
self.loop_communicator = loop_communicator
|
|
44
|
+
|
|
45
|
+
self.check_jpeg = shutil.which('jpeginfo') is not None
|
|
46
|
+
if self.check_jpeg:
|
|
47
|
+
logging.info('Detected command line tool "jpeginfo". Images will be checked for validity')
|
|
48
|
+
else:
|
|
49
|
+
logging.error('Missing command line tool "jpeginfo". We cannot check for validity of images.')
|
|
50
|
+
|
|
51
|
+
def set_context(self, context: Optional[Context]) -> None:
|
|
52
|
+
self._context = context
|
|
53
|
+
self.progress = 0.0
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def context(self) -> Context:
|
|
57
|
+
assert self._context, 'DataExchanger: Context was not set yet.. call set_context() first.'
|
|
58
|
+
return self._context
|
|
59
|
+
|
|
60
|
+
# ---------------------------- END OF INIT ----------------------------
|
|
61
|
+
|
|
62
|
+
async def fetch_image_uuids(self, query_params: Optional[str] = '') -> List[str]:
|
|
63
|
+
"""Fetch image uuids from the learning loop data endpoint."""
|
|
64
|
+
logging.info(f'Fetching image uuids for {self.context.organization}/{self.context.project}..')
|
|
65
|
+
|
|
66
|
+
response = await self.loop_communicator.get(f'/{self.context.organization}/projects/{self.context.project}/data?{query_params}')
|
|
67
|
+
assert response.status_code == 200, response
|
|
68
|
+
return (response.json())['image_ids']
|
|
69
|
+
|
|
70
|
+
async def download_images_data(self, image_uuids: List[str], chunk_size: int = 100) -> List[Dict]:
|
|
71
|
+
"""Download image annotations, tags, set and other information for the given image uuids."""
|
|
72
|
+
logging.info(f'Fetching annotations, tags, sets, etc. for {len(image_uuids)} images..')
|
|
73
|
+
|
|
74
|
+
num_image_ids = len(image_uuids)
|
|
75
|
+
if num_image_ids == 0:
|
|
76
|
+
logging.info('got empty list. No images were downloaded')
|
|
77
|
+
return []
|
|
78
|
+
|
|
79
|
+
progress_factor = 0.5 / num_image_ids # 50% of progress is for downloading data
|
|
80
|
+
images_data: List[Dict] = []
|
|
81
|
+
for i in range(0, num_image_ids, chunk_size):
|
|
82
|
+
self.progress = i * progress_factor
|
|
83
|
+
chunk_ids = image_uuids[i:i+chunk_size]
|
|
84
|
+
response = await self.loop_communicator.get(f'/{self.context.organization}/projects/{self.context.project}/images?ids={",".join(chunk_ids)}')
|
|
85
|
+
if response.status_code != 200:
|
|
86
|
+
logging.error(f'Error {response.status_code} during downloading image data. Continue with next batch..')
|
|
87
|
+
continue
|
|
88
|
+
images_data += response.json()['images']
|
|
89
|
+
|
|
90
|
+
return images_data
|
|
91
|
+
|
|
92
|
+
async def download_images(self, image_uuids: List[str], image_folder: str, chunk_size: int = 10) -> None:
|
|
93
|
+
"""Downloads images (actual image data). Will skip existing images"""
|
|
94
|
+
logging.info(f'Downloading {len(image_uuids)} images (actual image data).. skipping existing images.')
|
|
95
|
+
if not image_uuids:
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
existing_uuids = {os.path.splitext(os.path.basename(image))[0] for image in glob(f'{image_folder}/*.jpg')}
|
|
99
|
+
new_image_uuids = [id for id in image_uuids if id not in existing_uuids]
|
|
100
|
+
|
|
101
|
+
paths, _ = create_resource_paths(self.context.organization, self.context.project, new_image_uuids)
|
|
102
|
+
num_image_ids = len(image_uuids)
|
|
103
|
+
os.makedirs(image_folder, exist_ok=True)
|
|
104
|
+
|
|
105
|
+
progress_factor = 0.5 / num_image_ids # second 50% of progress is for downloading images
|
|
106
|
+
for i in range(0, num_image_ids, chunk_size):
|
|
107
|
+
self.progress = 0.5 + i * progress_factor
|
|
108
|
+
chunk_paths = paths[i:i+chunk_size]
|
|
109
|
+
chunk_ids = image_uuids[i:i+chunk_size]
|
|
110
|
+
tasks = []
|
|
111
|
+
for j, chunk_j in enumerate(chunk_paths):
|
|
112
|
+
start = time()
|
|
113
|
+
tasks.append(create_task(self._download_one_image(chunk_j, chunk_ids[j], image_folder)))
|
|
114
|
+
await asyncio.sleep(max(0, 0.02 - (time() - start))) # prevent too many requests at once
|
|
115
|
+
await asyncio.gather(*tasks)
|
|
116
|
+
|
|
117
|
+
async def _download_one_image(self, path: str, image_id: str, image_folder: str) -> None:
|
|
118
|
+
response = await self.loop_communicator.get(path)
|
|
119
|
+
if response.status_code != HTTPStatus.OK:
|
|
120
|
+
logging.error(f'bad status code {response.status_code} for {path}. Details: {response.text}')
|
|
121
|
+
return
|
|
122
|
+
filename = f'{image_folder}/{image_id}.jpg'
|
|
123
|
+
async with aiofiles.open(filename, 'wb') as f:
|
|
124
|
+
await f.write(response.content)
|
|
125
|
+
if not await is_valid_image(filename, self.check_jpeg):
|
|
126
|
+
os.remove(filename)
|
|
127
|
+
|
|
128
|
+
async def download_model(self, target_folder: str, context: Context, model_uuid: str, model_format: str) -> List[str]:
|
|
129
|
+
"""Downloads a model (and additional meta data like model.json) and returns the paths of the downloaded files.
|
|
130
|
+
Used before training a model (when continuing a finished training) or before detecting images.
|
|
131
|
+
"""
|
|
132
|
+
logging.info(f'Downloading model data for uuid {model_uuid} from the loop to {target_folder}..')
|
|
133
|
+
|
|
134
|
+
path = f'/{context.organization}/projects/{context.project}/models/{model_uuid}/{model_format}/file'
|
|
135
|
+
response = await self.loop_communicator.get(path, requires_login=False)
|
|
136
|
+
if response.status_code != 200:
|
|
137
|
+
content = response.json()
|
|
138
|
+
logging.error(f'could not download loop/{path}: {response.status_code}, content: {content}')
|
|
139
|
+
raise DownloadError(content['detail'])
|
|
140
|
+
try:
|
|
141
|
+
provided_filename = response.headers.get(
|
|
142
|
+
"Content-Disposition").split("filename=")[1].strip('"')
|
|
143
|
+
content = response.content
|
|
144
|
+
except:
|
|
145
|
+
logging.exception(f'Error during downloading model {path}:')
|
|
146
|
+
raise
|
|
147
|
+
|
|
148
|
+
tmp_path = f'/tmp/{os.path.splitext(provided_filename)[0]}'
|
|
149
|
+
shutil.rmtree(tmp_path, ignore_errors=True)
|
|
150
|
+
with zipfile.ZipFile(BytesIO(content), 'r') as zip_:
|
|
151
|
+
zip_.extractall(tmp_path)
|
|
152
|
+
|
|
153
|
+
created_files = []
|
|
154
|
+
for file in glob(f'{tmp_path}/**/*', recursive=True):
|
|
155
|
+
new_file = shutil.move(file, target_folder)
|
|
156
|
+
created_files.append(new_file)
|
|
157
|
+
|
|
158
|
+
shutil.rmtree(tmp_path, ignore_errors=True)
|
|
159
|
+
logging.info(f'Downloaded model {model_uuid}({model_format}) to {target_folder}.')
|
|
160
|
+
return created_files
|
|
161
|
+
|
|
162
|
+
async def upload_model_get_uuid(self, context: Context, files: List[str], training_number: Optional[int], mformat: str) -> Optional[str]:
|
|
163
|
+
"""Used by the trainers. Function returns the new model uuid to use for detection."""
|
|
164
|
+
response = await self.loop_communicator.put(f'/{context.organization}/projects/{context.project}/trainings/{training_number}/models/latest/{mformat}/file', files=files)
|
|
165
|
+
if response.status_code != 200:
|
|
166
|
+
logging.error(f'Could not upload model for training {training_number}, format {mformat}: {response.text}')
|
|
167
|
+
response.raise_for_status()
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
uploaded_model = response.json()
|
|
171
|
+
logging.info(f'Uploaded model for training {training_number}, format {mformat}. Response is: {uploaded_model}')
|
|
172
|
+
return uploaded_model['id']
|
{learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/detector_logic.py
RENAMED
|
@@ -13,11 +13,9 @@ class DetectorLogic():
|
|
|
13
13
|
def __init__(self, model_format: str) -> None:
|
|
14
14
|
self.model_format: str = model_format
|
|
15
15
|
self._model_info: Optional[ModelInformation] = None
|
|
16
|
-
self.target_model: Optional[str] = None
|
|
17
16
|
|
|
18
17
|
async def soft_reload(self):
|
|
19
18
|
self._model_info = None
|
|
20
|
-
self.target_model = None
|
|
21
19
|
|
|
22
20
|
@property
|
|
23
21
|
def model_info(self) -> ModelInformation:
|
{learning_loop_node-0.9.2 → learning_loop_node-0.10.0}/learning_loop_node/detector/detector_node.py
RENAMED
|
@@ -14,8 +14,7 @@ from fastapi.encoders import jsonable_encoder
|
|
|
14
14
|
from fastapi_socketio import SocketManager
|
|
15
15
|
from socketio import AsyncClient
|
|
16
16
|
|
|
17
|
-
from ..data_classes import
|
|
18
|
-
ModelInformation, NodeState, Shape)
|
|
17
|
+
from ..data_classes import Category, Context, Detections, DetectionStatus, ModelInformation, Shape
|
|
19
18
|
from ..data_classes.socket_response import SocketResponse
|
|
20
19
|
from ..data_exchanger import DataExchanger, DownloadError
|
|
21
20
|
from ..globals import GLOBALS
|
|
@@ -24,6 +23,7 @@ from ..node import Node
|
|
|
24
23
|
from .detector_logic import DetectorLogic
|
|
25
24
|
from .inbox_filter.relevance_filter import RelevanceFilter
|
|
26
25
|
from .outbox import Outbox
|
|
26
|
+
from .rest import about as rest_about
|
|
27
27
|
from .rest import backdoor_controls
|
|
28
28
|
from .rest import detect as rest_detect
|
|
29
29
|
from .rest import operation_mode as rest_mode
|
|
@@ -34,9 +34,8 @@ from .rest.operation_mode import OperationMode
|
|
|
34
34
|
class DetectorNode(Node):
|
|
35
35
|
|
|
36
36
|
def __init__(self, name: str, detector: DetectorLogic, uuid: Optional[str] = None, use_backdoor_controls: bool = False) -> None:
|
|
37
|
-
super().__init__(name, uuid)
|
|
37
|
+
super().__init__(name, uuid, 'detector', False)
|
|
38
38
|
self.detector_logic = detector
|
|
39
|
-
self.needs_login = False
|
|
40
39
|
self.organization = environment_reader.organization()
|
|
41
40
|
self.project = environment_reader.project()
|
|
42
41
|
assert self.organization and self.project, 'Detector node needs an organization and an project'
|
|
@@ -50,11 +49,13 @@ class DetectorNode(Node):
|
|
|
50
49
|
self.loop_communicator)
|
|
51
50
|
|
|
52
51
|
self.relevance_filter: RelevanceFilter = RelevanceFilter(self.outbox)
|
|
53
|
-
self.target_model = None
|
|
52
|
+
self.target_model: Optional[str] = None
|
|
54
53
|
|
|
55
54
|
self.include_router(rest_detect.router, tags=["detect"])
|
|
56
55
|
self.include_router(rest_upload.router, prefix="")
|
|
57
56
|
self.include_router(rest_mode.router, tags=["operation_mode"])
|
|
57
|
+
self.include_router(rest_about.router, tags=["about"])
|
|
58
|
+
|
|
58
59
|
if use_backdoor_controls:
|
|
59
60
|
self.include_router(backdoor_controls.router)
|
|
60
61
|
|
|
@@ -168,6 +169,8 @@ class DetectorNode(Node):
|
|
|
168
169
|
def _connect(sid, environ, auth) -> None:
|
|
169
170
|
self.connected_clients.append(sid)
|
|
170
171
|
|
|
172
|
+
print('>>>>>>>>>>>>>>>>>>>>>>> setting up sio server', flush=True)
|
|
173
|
+
|
|
171
174
|
self.sio_server = SocketManager(app=self)
|
|
172
175
|
self.sio_server.on('detect', _detect)
|
|
173
176
|
self.sio_server.on('info', _info)
|
|
@@ -183,7 +186,9 @@ class DetectorNode(Node):
|
|
|
183
186
|
if not update_to_model_id:
|
|
184
187
|
self.log.info('could not check for updates')
|
|
185
188
|
return
|
|
186
|
-
|
|
189
|
+
|
|
190
|
+
# TODO: solve race condition (it should not be required to recheck if model_info is not None, but it is!)
|
|
191
|
+
if self.detector_logic.is_initialized:
|
|
187
192
|
model_info = self.detector_logic._model_info # pylint: disable=protected-access
|
|
188
193
|
if model_info is not None:
|
|
189
194
|
self.log.info(f'Current model: {model_info.version} with id {model_info.id}')
|
|
@@ -218,8 +223,7 @@ class DetectorNode(Node):
|
|
|
218
223
|
await self.data_exchanger.download_model(target_model_folder,
|
|
219
224
|
Context(organization=self.organization,
|
|
220
225
|
project=self.project),
|
|
221
|
-
update_to_model_id,
|
|
222
|
-
self.detector_logic.model_format)
|
|
226
|
+
update_to_model_id, self.detector_logic.model_format)
|
|
223
227
|
try:
|
|
224
228
|
os.unlink(model_symlink)
|
|
225
229
|
os.remove(model_symlink)
|
|
@@ -254,7 +258,7 @@ class DetectorNode(Node):
|
|
|
254
258
|
name=self.name,
|
|
255
259
|
state=self.status.state,
|
|
256
260
|
errors=self.status.errors,
|
|
257
|
-
uptime=int((datetime.now() - self.
|
|
261
|
+
uptime=int((datetime.now() - self.startup_datetime).total_seconds()),
|
|
258
262
|
operation_mode=self.operation_mode,
|
|
259
263
|
current_model=current_model,
|
|
260
264
|
target_model=self.target_model,
|
|
@@ -270,13 +274,11 @@ class DetectorNode(Node):
|
|
|
270
274
|
return False
|
|
271
275
|
|
|
272
276
|
assert socket_response.payload is not None
|
|
277
|
+
# TODO This is weird because target_model_version is stored in self and target_model_id is returned
|
|
273
278
|
self.target_model = socket_response.payload['target_model_version']
|
|
274
279
|
self.log.info(f'After sending status. Target_model is {self.target_model}')
|
|
275
280
|
return socket_response.payload['target_model_id']
|
|
276
281
|
|
|
277
|
-
async def get_state(self):
|
|
278
|
-
return NodeState.Online # NOTE At the moment only trainer-nodes use a meaningful state
|
|
279
|
-
|
|
280
282
|
async def set_operation_mode(self, mode: OperationMode):
|
|
281
283
|
self.operation_mode = mode
|
|
282
284
|
await self.send_status()
|
|
@@ -351,9 +353,6 @@ class DetectorNode(Node):
|
|
|
351
353
|
classification_detection.category_id = category_id
|
|
352
354
|
return detections
|
|
353
355
|
|
|
354
|
-
def get_node_type(self):
|
|
355
|
-
return 'detector'
|
|
356
|
-
|
|
357
356
|
def register_sio_events(self, sio_client: AsyncClient):
|
|
358
357
|
pass
|
|
359
358
|
|