learning-loop-node 0.9.3__py3-none-any.whl → 0.10.1__py3-none-any.whl
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/__init__.py +2 -3
- learning_loop_node/annotation/annotator_logic.py +2 -2
- learning_loop_node/annotation/annotator_node.py +16 -15
- learning_loop_node/data_classes/__init__.py +17 -10
- learning_loop_node/data_classes/detections.py +7 -2
- learning_loop_node/data_classes/general.py +4 -5
- learning_loop_node/data_classes/training.py +49 -21
- learning_loop_node/data_exchanger.py +85 -139
- learning_loop_node/detector/__init__.py +0 -1
- learning_loop_node/detector/detector_node.py +10 -13
- learning_loop_node/detector/inbox_filter/cam_observation_history.py +4 -7
- learning_loop_node/detector/outbox.py +0 -1
- learning_loop_node/detector/rest/about.py +1 -0
- learning_loop_node/detector/tests/conftest.py +0 -1
- learning_loop_node/detector/tests/test_client_communication.py +5 -3
- learning_loop_node/detector/tests/test_outbox.py +2 -0
- learning_loop_node/detector/tests/testing_detector.py +1 -8
- learning_loop_node/globals.py +2 -2
- learning_loop_node/helpers/gdrive_downloader.py +1 -1
- learning_loop_node/helpers/misc.py +124 -17
- learning_loop_node/loop_communication.py +57 -25
- learning_loop_node/node.py +62 -135
- learning_loop_node/tests/test_downloader.py +8 -7
- learning_loop_node/tests/test_executor.py +14 -11
- learning_loop_node/tests/test_helper.py +3 -5
- learning_loop_node/trainer/downloader.py +1 -1
- learning_loop_node/trainer/executor.py +87 -83
- learning_loop_node/trainer/io_helpers.py +68 -9
- learning_loop_node/trainer/rest/backdoor_controls.py +10 -5
- learning_loop_node/trainer/rest/controls.py +3 -1
- learning_loop_node/trainer/tests/conftest.py +19 -28
- learning_loop_node/trainer/tests/states/test_state_cleanup.py +5 -3
- learning_loop_node/trainer/tests/states/test_state_detecting.py +23 -20
- learning_loop_node/trainer/tests/states/test_state_download_train_model.py +18 -12
- learning_loop_node/trainer/tests/states/test_state_prepare.py +13 -12
- learning_loop_node/trainer/tests/states/test_state_sync_confusion_matrix.py +21 -18
- learning_loop_node/trainer/tests/states/test_state_train.py +27 -28
- learning_loop_node/trainer/tests/states/test_state_upload_detections.py +34 -32
- learning_loop_node/trainer/tests/states/test_state_upload_model.py +22 -20
- learning_loop_node/trainer/tests/test_errors.py +20 -12
- learning_loop_node/trainer/tests/test_trainer_states.py +4 -5
- learning_loop_node/trainer/tests/testing_trainer_logic.py +25 -30
- learning_loop_node/trainer/trainer_logic.py +80 -590
- learning_loop_node/trainer/trainer_logic_generic.py +495 -0
- learning_loop_node/trainer/trainer_node.py +27 -106
- {learning_loop_node-0.9.3.dist-info → learning_loop_node-0.10.1.dist-info}/METADATA +1 -1
- learning_loop_node-0.10.1.dist-info/RECORD +85 -0
- learning_loop_node/converter/converter_logic.py +0 -68
- learning_loop_node/converter/converter_node.py +0 -125
- learning_loop_node/converter/tests/test_converter.py +0 -55
- learning_loop_node/trainer/training_syncronizer.py +0 -52
- learning_loop_node-0.9.3.dist-info/RECORD +0 -88
- /learning_loop_node/{converter/__init__.py → py.typed} +0 -0
- {learning_loop_node-0.9.3.dist-info → learning_loop_node-0.10.1.dist-info}/WHEEL +0 -0
|
@@ -1,74 +1,59 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import time
|
|
3
2
|
from dataclasses import asdict
|
|
4
|
-
from typing import Dict, Optional
|
|
3
|
+
from typing import Dict, Optional
|
|
5
4
|
|
|
6
|
-
from dacite import from_dict
|
|
7
5
|
from fastapi.encoders import jsonable_encoder
|
|
8
6
|
from socketio import AsyncClient
|
|
9
7
|
|
|
10
|
-
from ..data_classes import
|
|
11
|
-
from ..data_classes.socket_response import SocketResponse
|
|
8
|
+
from ..data_classes import TrainingStatus
|
|
12
9
|
from ..node import Node
|
|
13
10
|
from .io_helpers import LastTrainingIO
|
|
14
11
|
from .rest import backdoor_controls, controls
|
|
15
|
-
from .
|
|
12
|
+
from .trainer_logic_generic import TrainerLogicGeneric
|
|
16
13
|
|
|
17
14
|
|
|
18
15
|
class TrainerNode(Node):
|
|
19
16
|
|
|
20
|
-
def __init__(self, name: str, trainer_logic:
|
|
21
|
-
super().__init__(name, uuid)
|
|
22
|
-
trainer_logic._node = self
|
|
17
|
+
def __init__(self, name: str, trainer_logic: TrainerLogicGeneric, uuid: Optional[str] = None, use_backdoor_controls: bool = False):
|
|
18
|
+
super().__init__(name, uuid, 'trainer')
|
|
19
|
+
trainer_logic._node = self
|
|
23
20
|
self.trainer_logic = trainer_logic
|
|
24
21
|
self.last_training_io = LastTrainingIO(self.uuid)
|
|
22
|
+
self.trainer_logic._last_training_io = self.last_training_io
|
|
23
|
+
|
|
25
24
|
self.include_router(controls.router, tags=["controls"])
|
|
26
25
|
if use_backdoor_controls:
|
|
27
26
|
self.include_router(backdoor_controls.router, tags=["controls"])
|
|
28
27
|
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
@property
|
|
32
|
-
def progress(self) -> Union[float, None]:
|
|
33
|
-
return self.trainer_logic.general_progress if (self.trainer_logic is not None and
|
|
34
|
-
hasattr(self.trainer_logic, 'general_progress')) else None
|
|
35
|
-
|
|
36
|
-
@property
|
|
37
|
-
def training_uptime(self) -> Union[float, None]:
|
|
38
|
-
return time.time() - self.trainer_logic.start_time if self.trainer_logic.start_time else None
|
|
39
|
-
|
|
40
|
-
# ----------------------------------- LIVECYCLE: ABSTRACT NODE METHODS --------------------------
|
|
28
|
+
# ----------------------------------- NODE LIVECYCLE METHODS --------------------------
|
|
41
29
|
|
|
42
30
|
async def on_startup(self):
|
|
43
31
|
pass
|
|
44
32
|
|
|
45
33
|
async def on_shutdown(self):
|
|
46
34
|
self.log.info('shutdown detected, stopping training')
|
|
47
|
-
await self.trainer_logic.
|
|
35
|
+
await self.trainer_logic.on_shutdown()
|
|
48
36
|
|
|
49
37
|
async def on_repeat(self):
|
|
50
38
|
try:
|
|
51
|
-
if await self.
|
|
39
|
+
if await self.trainer_logic.try_continue_run_if_incomplete():
|
|
52
40
|
return # NOTE: we prevent sending idle status after starting a continuation
|
|
53
41
|
await self.send_status()
|
|
54
42
|
except Exception as e:
|
|
55
43
|
if isinstance(e, asyncio.TimeoutError):
|
|
56
44
|
self.log.warning('timeout when sending status to learning loop, reconnecting sio_client')
|
|
57
|
-
await self.sio_client.disconnect()
|
|
58
|
-
# NOTE: reconnect happens in node._on_repeat
|
|
45
|
+
await self.sio_client.disconnect() # NOTE: reconnect happens in node._on_repeat
|
|
59
46
|
else:
|
|
60
47
|
self.log.exception(f'could not send status state: {e}')
|
|
61
48
|
|
|
62
|
-
# ---------------------------------------------- NODE
|
|
49
|
+
# ---------------------------------------------- NODE METHODS ---------------------------------------------------
|
|
63
50
|
|
|
64
51
|
def register_sio_events(self, sio_client: AsyncClient):
|
|
65
52
|
|
|
66
53
|
@sio_client.event
|
|
67
54
|
async def begin_training(organization: str, project: str, details: Dict):
|
|
68
|
-
assert self._sio_client is not None
|
|
69
55
|
self.log.info('received begin_training from server')
|
|
70
|
-
self.trainer_logic.
|
|
71
|
-
asyncio.get_event_loop().create_task(self.trainer_logic.run())
|
|
56
|
+
await self.trainer_logic.begin_training(organization, project, details)
|
|
72
57
|
return True
|
|
73
58
|
|
|
74
59
|
@sio_client.event
|
|
@@ -81,93 +66,29 @@ class TrainerNode(Node):
|
|
|
81
66
|
return True
|
|
82
67
|
|
|
83
68
|
async def send_status(self):
|
|
84
|
-
if
|
|
69
|
+
if not self.sio_client.connected:
|
|
85
70
|
self.log.warning('cannot send status - not connected to the Learning Loop')
|
|
86
71
|
return
|
|
87
72
|
|
|
88
|
-
if not self.trainer_logic.is_initialized:
|
|
89
|
-
state_for_learning_loop = str(NodeState.Idle.value)
|
|
90
|
-
else:
|
|
91
|
-
assert self.trainer_logic.training.training_state is not None
|
|
92
|
-
state_for_learning_loop = TrainerNode.state_for_learning_loop(
|
|
93
|
-
self.trainer_logic.training.training_state)
|
|
94
|
-
|
|
95
73
|
status = TrainingStatus(id=self.uuid,
|
|
96
74
|
name=self.name,
|
|
97
|
-
state=
|
|
75
|
+
state=self.trainer_logic.state,
|
|
98
76
|
errors={},
|
|
99
|
-
uptime=self.training_uptime,
|
|
100
|
-
progress=self.
|
|
77
|
+
uptime=self.trainer_logic.training_uptime,
|
|
78
|
+
progress=self.trainer_logic.general_progress)
|
|
101
79
|
|
|
102
80
|
status.pretrained_models = self.trainer_logic.provided_pretrained_models
|
|
103
81
|
status.architecture = self.trainer_logic.model_architecture
|
|
104
82
|
|
|
105
|
-
if
|
|
106
|
-
status.train_image_count =
|
|
107
|
-
status.test_image_count =
|
|
108
|
-
status.skipped_image_count =
|
|
109
|
-
status.hyperparameters = self.trainer_logic.
|
|
83
|
+
if data := self.trainer_logic.training_data:
|
|
84
|
+
status.train_image_count = data.train_image_count()
|
|
85
|
+
status.test_image_count = data.test_image_count()
|
|
86
|
+
status.skipped_image_count = data.skipped_image_count
|
|
87
|
+
status.hyperparameters = self.trainer_logic.hyperparameters_for_state_sync
|
|
110
88
|
status.errors = self.trainer_logic.errors.errors
|
|
111
|
-
status.context = self.trainer_logic.
|
|
89
|
+
status.context = self.trainer_logic.training_context
|
|
112
90
|
|
|
113
91
|
self.log.info(f'sending status: {status.short_str()}')
|
|
114
|
-
result = await self.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if not response.success:
|
|
119
|
-
self.log.error(f'Error when sending status update: Response from loop was:\n {asdict(response)}')
|
|
120
|
-
|
|
121
|
-
async def continue_run_if_incomplete(self) -> bool:
|
|
122
|
-
if not self.trainer_logic.is_initialized and self.last_training_io.exists():
|
|
123
|
-
self.log.info('found incomplete training, continuing now.')
|
|
124
|
-
self.trainer_logic.init_from_last_training()
|
|
125
|
-
asyncio.get_event_loop().create_task(self.trainer_logic.run())
|
|
126
|
-
return True
|
|
127
|
-
return False
|
|
128
|
-
|
|
129
|
-
async def get_state(self):
|
|
130
|
-
if self.trainer_logic._executor is not None and self.trainer_logic._executor.is_process_running(): # pylint: disable=protected-access
|
|
131
|
-
return NodeState.Running
|
|
132
|
-
return NodeState.Idle
|
|
133
|
-
|
|
134
|
-
def get_node_type(self):
|
|
135
|
-
return 'trainer'
|
|
136
|
-
|
|
137
|
-
# --------------------------------------------------- HELPER ---------------------------------------------------
|
|
138
|
-
|
|
139
|
-
@staticmethod
|
|
140
|
-
def state_for_learning_loop(trainer_state: Union[TrainingState, str]) -> str:
|
|
141
|
-
if trainer_state == TrainingState.Initialized:
|
|
142
|
-
return 'Training is initialized'
|
|
143
|
-
if trainer_state == TrainingState.DataDownloading:
|
|
144
|
-
return 'Downloading data'
|
|
145
|
-
if trainer_state == TrainingState.DataDownloaded:
|
|
146
|
-
return 'Data downloaded'
|
|
147
|
-
if trainer_state == TrainingState.TrainModelDownloading:
|
|
148
|
-
return 'Downloading model'
|
|
149
|
-
if trainer_state == TrainingState.TrainModelDownloaded:
|
|
150
|
-
return 'Model downloaded'
|
|
151
|
-
if trainer_state == TrainingState.TrainingRunning:
|
|
152
|
-
return NodeState.Running
|
|
153
|
-
if trainer_state == TrainingState.TrainingFinished:
|
|
154
|
-
return 'Training finished'
|
|
155
|
-
if trainer_state == TrainingState.Detecting:
|
|
156
|
-
return NodeState.Detecting
|
|
157
|
-
if trainer_state == TrainingState.ConfusionMatrixSyncing:
|
|
158
|
-
return 'Syncing confusion matrix'
|
|
159
|
-
if trainer_state == TrainingState.ConfusionMatrixSynced:
|
|
160
|
-
return 'Confusion matrix synced'
|
|
161
|
-
if trainer_state == TrainingState.TrainModelUploading:
|
|
162
|
-
return 'Uploading trained model'
|
|
163
|
-
if trainer_state == TrainingState.TrainModelUploaded:
|
|
164
|
-
return 'Trained model uploaded'
|
|
165
|
-
if trainer_state == TrainingState.Detecting:
|
|
166
|
-
return 'calculating detections'
|
|
167
|
-
if trainer_state == TrainingState.Detected:
|
|
168
|
-
return 'Detections calculated'
|
|
169
|
-
if trainer_state == TrainingState.DetectionUploading:
|
|
170
|
-
return 'Uploading detections'
|
|
171
|
-
if trainer_state == TrainingState.ReadyForCleanup:
|
|
172
|
-
return 'Cleaning training'
|
|
173
|
-
return 'unknown state'
|
|
92
|
+
result = await self.sio_client.call('update_trainer', jsonable_encoder(asdict(status)), timeout=30)
|
|
93
|
+
if isinstance(result, Dict) and not result['success']:
|
|
94
|
+
self.log.error(f'Error when sending status update: Response from loop was:\n {result}')
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
learning_loop_node/__init__.py,sha256=onN5s8-x_xBsCM6NLmJO0Ym1sJHeCFaGw8qb0oQZmz8,364
|
|
2
|
+
learning_loop_node/annotation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
learning_loop_node/annotation/annotator_logic.py,sha256=BTaopkJZkIf1CI5lfsVKsxbxoUIbDJrevavuQUT5e_c,1000
|
|
4
|
+
learning_loop_node/annotation/annotator_node.py,sha256=wk11CQtM3A0Dr7efCn_Mw2X7ql5xn2sgEJzrIeSBC6Q,4043
|
|
5
|
+
learning_loop_node/annotation/tests/test_annotator_node.py,sha256=yCox-ohCXOpCbcTit7PwtE4iLHud8qiZ6UOqqycwPxg,2065
|
|
6
|
+
learning_loop_node/conftest.py,sha256=Q_8Dbl3RwI8X_Y5Zvzstr6NJEqY3KldZXVx618OLdb4,3492
|
|
7
|
+
learning_loop_node/data_classes/__init__.py,sha256=wCX88lDgbb8V-gtVCVe9i-NvvZuMe5FX7eD_UJgYYXw,1305
|
|
8
|
+
learning_loop_node/data_classes/annotations.py,sha256=iInU0Nuy_oYT_sj4k_n-W0UShCBI2cHQYrt8imymbtM,1211
|
|
9
|
+
learning_loop_node/data_classes/detections.py,sha256=1BcU5PNzIbryWcj2xJ6ysLBTBwGOdv9SxSJiUG8WEmw,4349
|
|
10
|
+
learning_loop_node/data_classes/general.py,sha256=44GJrJvGfPwDUmRsS7If9uSlE6KPP50LGUX91VzesLw,4664
|
|
11
|
+
learning_loop_node/data_classes/socket_response.py,sha256=tIdt-oYf6ULoJIDYQCecNM9OtWR6_wJ9tL0Ksu83Vko,655
|
|
12
|
+
learning_loop_node/data_classes/training.py,sha256=hnMHZMk-WNRERyo7U97qL09v1tIdhnzPfTH-JgifLwU,6164
|
|
13
|
+
learning_loop_node/data_exchanger.py,sha256=hxF0zANA35f5EV8tkQ4yjelrKuvafMaKUya0CCjVrK0,8221
|
|
14
|
+
learning_loop_node/detector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
learning_loop_node/detector/detector_logic.py,sha256=se0jRFbV7BfTvCuCI3gcUllSYIZ5dxTkvdISe6pPTRg,1660
|
|
16
|
+
learning_loop_node/detector/detector_node.py,sha256=Qmj87e5-mmS5SnT_VlfZ1I6vhS6XWzt60H9w82LiZbk,16649
|
|
17
|
+
learning_loop_node/detector/inbox_filter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
learning_loop_node/detector/inbox_filter/cam_observation_history.py,sha256=TD346I9ymtIP0_CJXCIKMRuiXbfVVanXNu_iHAwDd7Q,3318
|
|
19
|
+
learning_loop_node/detector/inbox_filter/relevance_filter.py,sha256=s2FuwZ-tD_5obkSutstjc8pE_hLGbrv9WjrEO9t8rJ8,1011
|
|
20
|
+
learning_loop_node/detector/inbox_filter/tests/test_observation.py,sha256=ORN08yjprqmgmtU25RsVysniyrWX-qGvqFN8ZkkYxow,1385
|
|
21
|
+
learning_loop_node/detector/inbox_filter/tests/test_relevance_group.py,sha256=RUgsk1CnKSOCRZBzNjE7AZTqk06-yelgUqvHFRLH7_I,7865
|
|
22
|
+
learning_loop_node/detector/inbox_filter/tests/test_unexpected_observations_count.py,sha256=y_dFUV21h6uZc90Q43s0u4oivJfuCNWlk5iXAWiXGgc,1804
|
|
23
|
+
learning_loop_node/detector/outbox.py,sha256=kxOzIhffTbrBCvZqGQsrwDS68zWJe00mN12O_qNwwVI,4176
|
|
24
|
+
learning_loop_node/detector/rest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
+
learning_loop_node/detector/rest/about.py,sha256=-PNqlQI_tzRvoSI_UR9rX8-5GeiENNpRDQ4Ylw3wYVs,607
|
|
26
|
+
learning_loop_node/detector/rest/backdoor_controls.py,sha256=38axRG66Z3_Q6bYKa7Hw-ldChEAu-dJcBM_Sl_17Ozo,1725
|
|
27
|
+
learning_loop_node/detector/rest/detect.py,sha256=8Rl1swANKgHc42P1z75t_PErQxpCKKPdAsKqDIZgdNU,1873
|
|
28
|
+
learning_loop_node/detector/rest/operation_mode.py,sha256=eIo6_56qyZECftf4AEN8wJMABIojC0TRazvWeg0Uj_s,1664
|
|
29
|
+
learning_loop_node/detector/rest/upload.py,sha256=MifNhban7GeaCjwa39lDhTQWyRuVyvyGFGscoszplH0,435
|
|
30
|
+
learning_loop_node/detector/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
+
learning_loop_node/detector/tests/conftest.py,sha256=v08N5_jvQyeqQkOzkTaanCwwVd75Rf-tc1M2-fgiv54,3254
|
|
32
|
+
learning_loop_node/detector/tests/test.jpg,sha256=msA-vHPmvPiro_D102Qmn1fn4vNfooqYYEXPxZUmYpk,161390
|
|
33
|
+
learning_loop_node/detector/tests/test_client_communication.py,sha256=QjoES3qMqcsfZgNHI825_bfvjdxxX8NNncCRKIeV9Wo,4603
|
|
34
|
+
learning_loop_node/detector/tests/test_outbox.py,sha256=bXvbxBVSF2O0M3V9gSDEqjMQ1g12_hwckq7rziG3i1c,2051
|
|
35
|
+
learning_loop_node/detector/tests/test_relevance_filter.py,sha256=FzeOU6k17VIQvAHR8fjHbcPeAE7D7C-2Yxol0lDrMEM,1981
|
|
36
|
+
learning_loop_node/detector/tests/testing_detector.py,sha256=2DSwIYJDOG4ixOGU8OxjsZQgaOdVU7_d3ASKsSkf8qc,564
|
|
37
|
+
learning_loop_node/examples/novelty_score_updater.py,sha256=1DRgM9lxjFV-q2JvGDDsNLz_ic_rhEZ9wc6ZdjcxwPE,2038
|
|
38
|
+
learning_loop_node/globals.py,sha256=tgw_8RYOipPV9aYlyUhYtXfUxvJKRvfUk6u-qVAtZmY,174
|
|
39
|
+
learning_loop_node/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
|
+
learning_loop_node/helpers/environment_reader.py,sha256=1GNTXRBcv6QKolbJ50TpsbWoVyw1oO0kvKrt9yjJ0nA,1543
|
|
41
|
+
learning_loop_node/helpers/gdrive_downloader.py,sha256=zeYJciTAJVRpu_eFjwgYLCpIa6hU1d71anqEBb564Rk,1145
|
|
42
|
+
learning_loop_node/helpers/log_conf.py,sha256=3yd-jaMOeD5cRIgA5w_BH2L5odf8c4-ZjD89Bdqwe44,824
|
|
43
|
+
learning_loop_node/helpers/misc.py,sha256=j4is8Rv0ttnCqF-R-wP3xwEi67OI6IBJav5Woo5lyDk,7701
|
|
44
|
+
learning_loop_node/loop_communication.py,sha256=w_CF1Ynl6GdpL1cdZ6wbb-X0-5gmGlPLjSfDQDRpwLE,6157
|
|
45
|
+
learning_loop_node/node.py,sha256=jpTP3gnGxFDF7tq9H7MVfVIIHm8e7bYA5euttHNJAEg,7252
|
|
46
|
+
learning_loop_node/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
|
+
learning_loop_node/pytest.ini,sha256=8QdjmawLy1zAzXrJ88or1kpFDhJw0W5UOnDfGGs_igU,262
|
|
48
|
+
learning_loop_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
|
+
learning_loop_node/tests/conftest.py,sha256=vFj8jjwSBzt6IAIxEo7y0rN0I94SPkK2pZsMvrvET5Q,920
|
|
50
|
+
learning_loop_node/tests/test_data/file_1.txt,sha256=Lis06nfvbFPVCBZyEgQlfI_Nle2YDq1GQBlYvEfFtxw,19
|
|
51
|
+
learning_loop_node/tests/test_data/file_2.txt,sha256=Xp8EETGhZBdVAgb4URowSSpOytwwwJdV0Renkdur7R8,19
|
|
52
|
+
learning_loop_node/tests/test_data/model.json,sha256=_xNDucGOWila8gWnu8yFfrqmQ45Xq-_39eLKzjRtvpE,516
|
|
53
|
+
learning_loop_node/tests/test_data_classes.py,sha256=m8LEk1quGErxuPzNdW_ExqQjkwE4u7ribwnTdyeiHR8,788
|
|
54
|
+
learning_loop_node/tests/test_downloader.py,sha256=RkXNVQJkfPddGzxfJgSF7kc-V6Qp1OOvYYuhQ9g_ls4,2806
|
|
55
|
+
learning_loop_node/tests/test_executor.py,sha256=-QLylzigUEoUzeQssPR3ECFkD2tWH-fsA_lEGXy993c,2119
|
|
56
|
+
learning_loop_node/tests/test_helper.py,sha256=AjOrTu3dHIlJLYI0mxcNx8MCmFF6IjLhHtPzuai4O9E,2334
|
|
57
|
+
learning_loop_node/tests/test_learning_loop_node.py,sha256=4qWi1ovBzebUAbvw8ecSa-TBGKYuJvlKe2AMnMZ-Qs8,701
|
|
58
|
+
learning_loop_node/trainer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
59
|
+
learning_loop_node/trainer/downloader.py,sha256=qzx7zzObcFEvRVQFe8gi8KJNIapASi1_XssbspXD1Rw,1469
|
|
60
|
+
learning_loop_node/trainer/executor.py,sha256=j-1pkc_5b1Y0Bh8KiQthAK7SoOfJelr5Qi7L0p1sTxE,3933
|
|
61
|
+
learning_loop_node/trainer/io_helpers.py,sha256=fyjImT1NY10gLV0KM4Ns7sRrtKFswVJU_pV9-c3IVEY,7152
|
|
62
|
+
learning_loop_node/trainer/rest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
63
|
+
learning_loop_node/trainer/rest/backdoor_controls.py,sha256=YQcG0KwxzKDNYeMtHrSwr26q__N7ty0o6Kar6CLWAd0,5869
|
|
64
|
+
learning_loop_node/trainer/rest/controls.py,sha256=XF37i2edeMHKdSXyJc4ZqaTZ38u6d3u3Sb3C-Mwyfko,934
|
|
65
|
+
learning_loop_node/trainer/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
66
|
+
learning_loop_node/trainer/tests/conftest.py,sha256=UIVmnZZVe1DigOxeraiLh89cyw1QNQFvNJWmPUY1uFI,2455
|
|
67
|
+
learning_loop_node/trainer/tests/state_helper.py,sha256=igoGqTBqcqqFcDng2i7ctC67bYR1hLPDl4G_mNRG6r8,934
|
|
68
|
+
learning_loop_node/trainer/tests/states/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
69
|
+
learning_loop_node/trainer/tests/states/test_state_cleanup.py,sha256=tiL31hSjg1Bl2obzg2ufAVpil5qW0YkpDCpSHPqXQrk,1312
|
|
70
|
+
learning_loop_node/trainer/tests/states/test_state_detecting.py,sha256=NU065RhpUwhExldGp-79YD_P9bG4O9DNcLjBfW0qmik,3811
|
|
71
|
+
learning_loop_node/trainer/tests/states/test_state_download_train_model.py,sha256=rNRXIyqyHzHz4fXY1Lsf7WKg8FFVFYfFPIevMCBBcCY,2940
|
|
72
|
+
learning_loop_node/trainer/tests/states/test_state_prepare.py,sha256=fx9_bgPTaR5ANVB8n_hW8dXcaJIh_iKEnInmhzamZ9E,2432
|
|
73
|
+
learning_loop_node/trainer/tests/states/test_state_sync_confusion_matrix.py,sha256=zfNbHB3GFSJXXoEkW-8PYtmX62md3feWp4oisyzs8A4,4773
|
|
74
|
+
learning_loop_node/trainer/tests/states/test_state_train.py,sha256=j1vedjH2EwLTgHhon6eR9ttp-Sw9ozR9-9QgAKlFO-M,3248
|
|
75
|
+
learning_loop_node/trainer/tests/states/test_state_upload_detections.py,sha256=38MumBw_N6Pt5cXI8x-xZjczmWUc7BIqyw4TSfim9h4,7751
|
|
76
|
+
learning_loop_node/trainer/tests/states/test_state_upload_model.py,sha256=gAXJJcoxj2EVdFJcPAeqxGAjeL1a4ofLtytnM4joi-Q,3808
|
|
77
|
+
learning_loop_node/trainer/tests/test_errors.py,sha256=8H-kjs9kEBoHWcQVJIZvW5zcwCs1VQI5Tf5I0VSbCUA,2245
|
|
78
|
+
learning_loop_node/trainer/tests/test_trainer_states.py,sha256=OBrClH6srAM2hqqel2xTtfHCeTKYZlG_S4KO2G2GrS4,1147
|
|
79
|
+
learning_loop_node/trainer/tests/testing_trainer_logic.py,sha256=7sQ6okiOhM4IhvRRo4XvLPjxnBrqFu9SPbuDX2LLwRs,3925
|
|
80
|
+
learning_loop_node/trainer/trainer_logic.py,sha256=PJxiO1chPdvpq8UTtzv_nVam9CouCswX9b1FnRwT2Tw,8411
|
|
81
|
+
learning_loop_node/trainer/trainer_logic_generic.py,sha256=KFDuxgzrGITHQaJoGvhjHxWzhbb4Q7HBxSpks4CeGBg,24801
|
|
82
|
+
learning_loop_node/trainer/trainer_node.py,sha256=bcyOMeLXrLuLgsPqS8lwEOSZ6vCjGLgT0pLXgaylI1Q,4155
|
|
83
|
+
learning_loop_node-0.10.1.dist-info/METADATA,sha256=j9ZrDeZGnT8IuYUAmt53KSvVtjEhsn3kxBAlPkbX2cE,9158
|
|
84
|
+
learning_loop_node-0.10.1.dist-info/WHEEL,sha256=WGfLGfLX43Ei_YORXSnT54hxFygu34kMpcQdmgmEwCQ,88
|
|
85
|
+
learning_loop_node-0.10.1.dist-info/RECORD,,
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import os
|
|
3
|
-
import shutil
|
|
4
|
-
from abc import abstractmethod
|
|
5
|
-
from typing import List, Optional
|
|
6
|
-
|
|
7
|
-
from ..data_classes import ModelInformation
|
|
8
|
-
from ..node import Node
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class ConverterLogic():
|
|
12
|
-
|
|
13
|
-
def __init__(
|
|
14
|
-
self, source_format: str, target_format: str):
|
|
15
|
-
self.source_format = source_format
|
|
16
|
-
self.target_format = target_format
|
|
17
|
-
self._node: Optional[Node] = None
|
|
18
|
-
self.model_folder: Optional[str] = None
|
|
19
|
-
|
|
20
|
-
def init(self, node: Node) -> None:
|
|
21
|
-
self._node = node
|
|
22
|
-
|
|
23
|
-
@property
|
|
24
|
-
def node(self) -> Node:
|
|
25
|
-
if self._node is None:
|
|
26
|
-
raise Exception('ConverterLogic not initialized')
|
|
27
|
-
return self._node
|
|
28
|
-
|
|
29
|
-
async def convert(self, model_information: ModelInformation) -> None:
|
|
30
|
-
project_folder = Node.create_project_folder(model_information.context)
|
|
31
|
-
|
|
32
|
-
self.model_folder = ConverterLogic.create_model_folder(project_folder, model_information.id)
|
|
33
|
-
await self.node.data_exchanger.download_model(self.model_folder,
|
|
34
|
-
model_information.context,
|
|
35
|
-
model_information.id,
|
|
36
|
-
self.source_format)
|
|
37
|
-
|
|
38
|
-
with open(f'{self.model_folder}/model.json', 'r') as f:
|
|
39
|
-
content = json.load(f)
|
|
40
|
-
if 'resolution' in content:
|
|
41
|
-
model_information.resolution = content['resolution']
|
|
42
|
-
|
|
43
|
-
await self._convert(model_information)
|
|
44
|
-
|
|
45
|
-
async def upload_model(self, context, model_id: str) -> None:
|
|
46
|
-
files = self.get_converted_files(model_id)
|
|
47
|
-
await self.node.data_exchanger.upload_model(context, files, model_id, self.target_format)
|
|
48
|
-
|
|
49
|
-
@abstractmethod
|
|
50
|
-
async def _convert(self, model_information: ModelInformation) -> None:
|
|
51
|
-
"""Converts the model in self.model_folder to the target format."""
|
|
52
|
-
|
|
53
|
-
@abstractmethod
|
|
54
|
-
def get_converted_files(self, model_id) -> List[str]:
|
|
55
|
-
"""Returns a list of files that should be uploaded to the server."""
|
|
56
|
-
|
|
57
|
-
@staticmethod
|
|
58
|
-
def create_convert_folder(project_folder: str) -> str:
|
|
59
|
-
image_folder = f'{project_folder}/images'
|
|
60
|
-
os.makedirs(image_folder, exist_ok=True)
|
|
61
|
-
return image_folder
|
|
62
|
-
|
|
63
|
-
@staticmethod
|
|
64
|
-
def create_model_folder(project_folder: str, model_id: str) -> str:
|
|
65
|
-
model_folder = f'{project_folder}/{model_id}'
|
|
66
|
-
shutil.rmtree(model_folder, ignore_errors=True) # cleanup
|
|
67
|
-
os.makedirs(model_folder, exist_ok=True)
|
|
68
|
-
return model_folder
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from dataclasses import asdict
|
|
3
|
-
from http import HTTPStatus
|
|
4
|
-
from typing import List, Optional
|
|
5
|
-
|
|
6
|
-
from dacite import from_dict
|
|
7
|
-
from fastapi.encoders import jsonable_encoder
|
|
8
|
-
from fastapi_utils.tasks import repeat_every
|
|
9
|
-
from socketio import AsyncClient
|
|
10
|
-
|
|
11
|
-
from ..data_classes import Category, ModelInformation, NodeState
|
|
12
|
-
from ..node import Node
|
|
13
|
-
from .converter_logic import ConverterLogic
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class ConverterNode(Node):
|
|
17
|
-
converter: ConverterLogic
|
|
18
|
-
skip_check_state: bool = False
|
|
19
|
-
bad_model_ids: List[str] = []
|
|
20
|
-
|
|
21
|
-
def __init__(self, name: str, converter: ConverterLogic, uuid: Optional[str] = None):
|
|
22
|
-
super().__init__(name, uuid)
|
|
23
|
-
self.converter = converter
|
|
24
|
-
converter.init(self)
|
|
25
|
-
|
|
26
|
-
@self.on_event("startup")
|
|
27
|
-
@repeat_every(seconds=60, raise_exceptions=True, wait_first=False)
|
|
28
|
-
async def check_state():
|
|
29
|
-
if not self.skip_check_state:
|
|
30
|
-
try:
|
|
31
|
-
await self.check_state()
|
|
32
|
-
except Exception:
|
|
33
|
-
logging.error('could not check state. Is loop reachable?')
|
|
34
|
-
|
|
35
|
-
async def convert_model(self, model_information: ModelInformation):
|
|
36
|
-
if model_information.id in self.bad_model_ids:
|
|
37
|
-
logging.info(
|
|
38
|
-
f'skipping bad model model {model_information.id} for {model_information.context.organization}/{model_information.context.project}.')
|
|
39
|
-
return
|
|
40
|
-
try:
|
|
41
|
-
logging.info(
|
|
42
|
-
f'converting model {jsonable_encoder(asdict(model_information))}')
|
|
43
|
-
await self.converter.convert(model_information)
|
|
44
|
-
logging.info('uploading model ')
|
|
45
|
-
await self.converter.upload_model(model_information.context, model_information.id)
|
|
46
|
-
except Exception as e:
|
|
47
|
-
self.bad_model_ids.append(model_information.id)
|
|
48
|
-
logging.error(
|
|
49
|
-
f'could not convert model {model_information.id} for {model_information.context.organization}/{model_information.context.project}. Details: {str(e)}.')
|
|
50
|
-
|
|
51
|
-
async def check_state(self):
|
|
52
|
-
logging.info(f'checking state: {self.status.state}')
|
|
53
|
-
|
|
54
|
-
if self.status.state == NodeState.Running:
|
|
55
|
-
return
|
|
56
|
-
self.status.state = NodeState.Running
|
|
57
|
-
try:
|
|
58
|
-
await self.convert_models()
|
|
59
|
-
except Exception as exc:
|
|
60
|
-
logging.error(str(exc))
|
|
61
|
-
|
|
62
|
-
self.status.state = NodeState.Idle
|
|
63
|
-
|
|
64
|
-
async def convert_models(self) -> None:
|
|
65
|
-
try:
|
|
66
|
-
response = await self.loop_communicator.get('/projects')
|
|
67
|
-
assert response.status_code == 200, f'Assert statuscode 200, but was {response.status_code}.'
|
|
68
|
-
content = response.json()
|
|
69
|
-
projects = content['projects']
|
|
70
|
-
|
|
71
|
-
for project in projects:
|
|
72
|
-
organization_id = project['organization_id']
|
|
73
|
-
project_id = project['project_id']
|
|
74
|
-
|
|
75
|
-
response = await self.loop_communicator.get(f'{project["resource"]}')
|
|
76
|
-
if response.status_code != HTTPStatus.OK:
|
|
77
|
-
logging.error(f'got bad response for {response.url}: {str(response.status_code)}')
|
|
78
|
-
continue
|
|
79
|
-
|
|
80
|
-
project_categories = [from_dict(data_class=Category, data=c) for c in response.json()['categories']]
|
|
81
|
-
|
|
82
|
-
path = f'{project["resource"]}/models'
|
|
83
|
-
models_response = await self.loop_communicator.get(path)
|
|
84
|
-
assert models_response.status_code == 200
|
|
85
|
-
content = models_response.json()
|
|
86
|
-
models = content['models']
|
|
87
|
-
|
|
88
|
-
for model in models:
|
|
89
|
-
if (model['version']
|
|
90
|
-
and self.converter.source_format in model['formats']
|
|
91
|
-
and self.converter.target_format not in model['formats']
|
|
92
|
-
):
|
|
93
|
-
# if self.converter.source_format in model['formats'] and project_id == 'drawingbot' and model['version'] == "6.0":
|
|
94
|
-
model_information = ModelInformation(
|
|
95
|
-
host=self.loop_communicator.base_url,
|
|
96
|
-
organization=organization_id,
|
|
97
|
-
project=project_id,
|
|
98
|
-
id=model['id'],
|
|
99
|
-
categories=project_categories,
|
|
100
|
-
version=model['version'],
|
|
101
|
-
)
|
|
102
|
-
await self.convert_model(model_information)
|
|
103
|
-
except Exception:
|
|
104
|
-
logging.exception('could not convert models')
|
|
105
|
-
|
|
106
|
-
async def send_status(self):
|
|
107
|
-
pass
|
|
108
|
-
|
|
109
|
-
async def on_startup(self):
|
|
110
|
-
pass
|
|
111
|
-
|
|
112
|
-
async def on_shutdown(self):
|
|
113
|
-
pass
|
|
114
|
-
|
|
115
|
-
async def on_repeat(self):
|
|
116
|
-
pass
|
|
117
|
-
|
|
118
|
-
def register_sio_events(self, sio_client: AsyncClient):
|
|
119
|
-
pass
|
|
120
|
-
|
|
121
|
-
async def get_state(self):
|
|
122
|
-
return NodeState.Idle # NOTE unused for this node type
|
|
123
|
-
|
|
124
|
-
def get_node_type(self):
|
|
125
|
-
return 'converter'
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import List
|
|
3
|
-
|
|
4
|
-
import pytest
|
|
5
|
-
|
|
6
|
-
from learning_loop_node.converter.converter_logic import ConverterLogic
|
|
7
|
-
from learning_loop_node.converter.converter_node import ConverterNode
|
|
8
|
-
from learning_loop_node.data_classes import ModelInformation
|
|
9
|
-
from learning_loop_node.loop_communication import LoopCommunicator
|
|
10
|
-
from learning_loop_node.tests import test_helper
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class TestConverter(ConverterLogic):
|
|
14
|
-
__test__ = False # hint for pytest
|
|
15
|
-
|
|
16
|
-
def __init__(self, source_format: str, target_format: str, models: List[ModelInformation]):
|
|
17
|
-
super().__init__(source_format, target_format)
|
|
18
|
-
self.models = models
|
|
19
|
-
|
|
20
|
-
async def _convert(self, model_information: ModelInformation) -> None:
|
|
21
|
-
self.models.append(model_information)
|
|
22
|
-
|
|
23
|
-
def get_converted_files(self, model_id) -> List[str]:
|
|
24
|
-
return [] # test: test_meta_information fails because model cannot be uploaded
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@pytest.mark.asyncio
|
|
28
|
-
@pytest.fixture()
|
|
29
|
-
async def setup_converter_test_project(glc: LoopCommunicator):
|
|
30
|
-
await glc.delete("/zauberzeug/projects/pytest_conv?keep_images=true")
|
|
31
|
-
project_configuration = {
|
|
32
|
-
'project_name': 'pytest_conv', 'box_categories': 1, 'point_categories': 1, 'inbox': 0, 'annotate': 0, 'review': 0,
|
|
33
|
-
'complete': 0, 'image_style': 'plain', 'thumbs': False, 'trainings': 1}
|
|
34
|
-
r = await glc.post("/zauberzeug/projects/generator", json=project_configuration)
|
|
35
|
-
assert r.status_code == 200
|
|
36
|
-
yield
|
|
37
|
-
await glc.delete("/zauberzeug/projects/pytest?keep_images=true")
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
# pylint: disable=redefined-outer-name, unused-argument
|
|
41
|
-
@pytest.mark.asyncio
|
|
42
|
-
async def test_meta_information(setup_converter_test_project):
|
|
43
|
-
model_id = await test_helper.get_latest_model_id(project='pytest_conv')
|
|
44
|
-
|
|
45
|
-
converter = TestConverter(source_format='mocked', target_format='test', models=[])
|
|
46
|
-
node = ConverterNode(name='test', converter=converter)
|
|
47
|
-
await node.convert_models()
|
|
48
|
-
|
|
49
|
-
pytest_project_model = [m for m in converter.models if m.id == model_id][0]
|
|
50
|
-
|
|
51
|
-
categories = pytest_project_model.categories
|
|
52
|
-
assert len(categories) == 2
|
|
53
|
-
category_types = [category.type for category in categories]
|
|
54
|
-
assert 'box' in category_types
|
|
55
|
-
assert 'point' in category_types
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import asyncio
|
|
3
|
-
import logging
|
|
4
|
-
from dataclasses import asdict
|
|
5
|
-
from typing import TYPE_CHECKING, Any
|
|
6
|
-
|
|
7
|
-
import socketio
|
|
8
|
-
from dacite import from_dict
|
|
9
|
-
from fastapi.encoders import jsonable_encoder
|
|
10
|
-
|
|
11
|
-
from ..data_classes import TrainingOut
|
|
12
|
-
from ..data_classes.socket_response import SocketResponse
|
|
13
|
-
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from .trainer_logic import TrainerLogic
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
async def try_sync_model(trainer: 'TrainerLogic', trainer_node_uuid: str, sio_client: socketio.AsyncClient):
|
|
19
|
-
try:
|
|
20
|
-
model = trainer.get_new_model()
|
|
21
|
-
except Exception as exc:
|
|
22
|
-
logging.exception('error while getting new model')
|
|
23
|
-
raise Exception(f'Could not get new model: {str(exc)}') from exc
|
|
24
|
-
logging.debug(f'new model {model}')
|
|
25
|
-
|
|
26
|
-
if model:
|
|
27
|
-
response = await sync_model(trainer, trainer_node_uuid, sio_client, model)
|
|
28
|
-
|
|
29
|
-
if not response.success:
|
|
30
|
-
error_msg = f'Error for update_training: Response from loop was : {asdict(response)}'
|
|
31
|
-
logging.error(error_msg)
|
|
32
|
-
raise Exception(error_msg)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
async def sync_model(trainer, trainer_node_uuid, sio_client, model):
|
|
36
|
-
current_training = trainer.training
|
|
37
|
-
new_training = TrainingOut(
|
|
38
|
-
trainer_id=trainer_node_uuid,
|
|
39
|
-
confusion_matrix=model.confusion_matrix,
|
|
40
|
-
train_image_count=current_training.data.train_image_count(),
|
|
41
|
-
test_image_count=current_training.data.test_image_count(),
|
|
42
|
-
hyperparameters=trainer.hyperparameters)
|
|
43
|
-
|
|
44
|
-
await asyncio.sleep(0.1) # NOTE needed for tests.
|
|
45
|
-
|
|
46
|
-
result = await sio_client.call('update_training', (current_training.context.organization, current_training.context.project, jsonable_encoder(new_training)))
|
|
47
|
-
response = from_dict(data_class=SocketResponse, data=result)
|
|
48
|
-
|
|
49
|
-
if response.success:
|
|
50
|
-
logging.info(f'successfully updated training {asdict(new_training)}')
|
|
51
|
-
trainer.on_model_published(model)
|
|
52
|
-
return response
|