learning-loop-node 0.13.5__py3-none-any.whl → 0.13.7__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/data_exchanger.py +1 -1
- learning_loop_node/detector/detector_logic.py +10 -18
- learning_loop_node/detector/detector_node.py +43 -22
- learning_loop_node/loop_communication.py +18 -18
- learning_loop_node/node.py +2 -1
- learning_loop_node/tests/detector/conftest.py +0 -4
- learning_loop_node/tests/detector/test_client_communication.py +0 -3
- {learning_loop_node-0.13.5.dist-info → learning_loop_node-0.13.7.dist-info}/METADATA +5 -3
- {learning_loop_node-0.13.5.dist-info → learning_loop_node-0.13.7.dist-info}/RECORD +10 -10
- {learning_loop_node-0.13.5.dist-info → learning_loop_node-0.13.7.dist-info}/WHEEL +0 -0
|
@@ -143,7 +143,7 @@ class DataExchanger():
|
|
|
143
143
|
logging.info('Downloading model data for uuid %s from the loop to %s..', model_uuid, target_folder)
|
|
144
144
|
|
|
145
145
|
path = f'/{context.organization}/projects/{context.project}/models/{model_uuid}/{model_format}/file'
|
|
146
|
-
response = await self.loop_communicator.get(path, requires_login=False)
|
|
146
|
+
response = await self.loop_communicator.get(path, requires_login=False, timeout=60*10)
|
|
147
147
|
if response.status_code != 200:
|
|
148
148
|
decoded_content = response.content.decode('utf-8')
|
|
149
149
|
logging.error('could not download loop/%s: %s, content: %s', path,
|
|
@@ -13,37 +13,29 @@ class DetectorLogic():
|
|
|
13
13
|
|
|
14
14
|
def __init__(self, model_format: str) -> None:
|
|
15
15
|
self.model_format: str = model_format
|
|
16
|
-
self.
|
|
16
|
+
self.model_info: Optional[ModelInformation] = None
|
|
17
17
|
|
|
18
18
|
self._remaining_init_attempts: int = 2
|
|
19
19
|
|
|
20
20
|
async def soft_reload(self):
|
|
21
|
-
self.
|
|
22
|
-
|
|
23
|
-
@property
|
|
24
|
-
def model_info(self) -> ModelInformation:
|
|
25
|
-
if self._model_info is None:
|
|
26
|
-
raise Exception('Model not loaded')
|
|
27
|
-
return self._model_info
|
|
28
|
-
|
|
29
|
-
@property
|
|
30
|
-
def is_initialized(self) -> bool:
|
|
31
|
-
return self._model_info is not None
|
|
21
|
+
self.model_info = None
|
|
32
22
|
|
|
33
23
|
def load_model_info_and_init_model(self):
|
|
34
24
|
logging.info('Loading model from %s', GLOBALS.data_folder)
|
|
35
|
-
self.
|
|
36
|
-
if self.
|
|
25
|
+
self.model_info = ModelInformation.load_from_disk(f'{GLOBALS.data_folder}/model')
|
|
26
|
+
if self.model_info is None:
|
|
37
27
|
logging.error('No model found')
|
|
38
|
-
self.
|
|
28
|
+
self.model_info = None
|
|
29
|
+
return
|
|
30
|
+
|
|
39
31
|
try:
|
|
40
32
|
self.init()
|
|
41
|
-
logging.info('Successfully loaded model %s', self.
|
|
33
|
+
logging.info('Successfully loaded model %s', self.model_info)
|
|
42
34
|
self._remaining_init_attempts = 2
|
|
43
35
|
except Exception:
|
|
44
36
|
self._remaining_init_attempts -= 1
|
|
45
|
-
self.
|
|
46
|
-
logging.error('Could not init model %s. Retries left: %s', self.
|
|
37
|
+
self.model_info = None
|
|
38
|
+
logging.error('Could not init model %s. Retries left: %s', self.model_info, self._remaining_init_attempts)
|
|
47
39
|
if self._remaining_init_attempts == 0:
|
|
48
40
|
raise NodeNeedsRestartError('Could not init model') from None
|
|
49
41
|
raise
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import contextlib
|
|
3
|
+
import math
|
|
3
4
|
import os
|
|
4
5
|
import shutil
|
|
5
6
|
import subprocess
|
|
@@ -15,8 +16,16 @@ from dacite import from_dict
|
|
|
15
16
|
from fastapi.encoders import jsonable_encoder
|
|
16
17
|
from socketio import AsyncClient
|
|
17
18
|
|
|
18
|
-
from ..data_classes import (
|
|
19
|
-
|
|
19
|
+
from ..data_classes import (
|
|
20
|
+
AboutResponse,
|
|
21
|
+
Category,
|
|
22
|
+
Context,
|
|
23
|
+
DetectionStatus,
|
|
24
|
+
ImageMetadata,
|
|
25
|
+
ModelInformation,
|
|
26
|
+
ModelVersionResponse,
|
|
27
|
+
Shape,
|
|
28
|
+
)
|
|
20
29
|
from ..data_classes.socket_response import SocketResponse
|
|
21
30
|
from ..data_exchanger import DataExchanger, DownloadError
|
|
22
31
|
from ..enums import OperationMode, VersionMode
|
|
@@ -82,13 +91,13 @@ class DetectorNode(Node):
|
|
|
82
91
|
return AboutResponse(
|
|
83
92
|
operation_mode=self.operation_mode.value,
|
|
84
93
|
state=self.status.state,
|
|
85
|
-
model_info=self.detector_logic.
|
|
94
|
+
model_info=self.detector_logic.model_info, # pylint: disable=protected-access
|
|
86
95
|
target_model=self.target_model.version if self.target_model else None,
|
|
87
96
|
version_control=self.version_control.value
|
|
88
97
|
)
|
|
89
98
|
|
|
90
99
|
def get_model_version_response(self) -> ModelVersionResponse:
|
|
91
|
-
current_version = self.detector_logic.
|
|
100
|
+
current_version = self.detector_logic.model_info.version if self.detector_logic.model_info is not None else 'None' # pylint: disable=protected-access
|
|
92
101
|
target_version = self.target_model.version if self.target_model is not None else 'None'
|
|
93
102
|
loop_version = self.loop_deployment_target.version if self.loop_deployment_target is not None else 'None'
|
|
94
103
|
|
|
@@ -237,7 +246,7 @@ class DetectorNode(Node):
|
|
|
237
246
|
|
|
238
247
|
@self.sio.event
|
|
239
248
|
async def info(sid) -> Dict:
|
|
240
|
-
if self.detector_logic.
|
|
249
|
+
if self.detector_logic.model_info is not None:
|
|
241
250
|
return asdict(self.detector_logic.model_info)
|
|
242
251
|
return {"status": "No model loaded"}
|
|
243
252
|
|
|
@@ -274,7 +283,7 @@ class DetectorNode(Node):
|
|
|
274
283
|
'''upload an image with detections'''
|
|
275
284
|
|
|
276
285
|
detection_data = data.get('detections', {})
|
|
277
|
-
if detection_data and self.detector_logic.
|
|
286
|
+
if detection_data and self.detector_logic.model_info is not None:
|
|
278
287
|
try:
|
|
279
288
|
image_metadata = from_dict(data_class=ImageMetadata, data=detection_data)
|
|
280
289
|
except Exception as e:
|
|
@@ -323,7 +332,7 @@ class DetectorNode(Node):
|
|
|
323
332
|
self.log.debug('not checking for updates; no target model selected')
|
|
324
333
|
return
|
|
325
334
|
|
|
326
|
-
if self.detector_logic.
|
|
335
|
+
if self.detector_logic.model_info is not None:
|
|
327
336
|
current_version = self.detector_logic.model_info.version
|
|
328
337
|
else:
|
|
329
338
|
current_version = None
|
|
@@ -335,16 +344,21 @@ class DetectorNode(Node):
|
|
|
335
344
|
with step_into(GLOBALS.data_folder):
|
|
336
345
|
model_symlink = 'model'
|
|
337
346
|
target_model_folder = f'models/{self.target_model.version}'
|
|
338
|
-
if
|
|
339
|
-
os.makedirs(target_model_folder)
|
|
340
|
-
await self.data_exchanger.download_model(target_model_folder,
|
|
341
|
-
Context(organization=self.organization,
|
|
342
|
-
project=self.project),
|
|
343
|
-
self.target_model.id,
|
|
344
|
-
self.detector_logic.model_format)
|
|
345
|
-
self.log.info('Downloaded model %s', self.target_model.version)
|
|
346
|
-
else:
|
|
347
|
+
if os.path.exists(target_model_folder) and len(os.listdir(target_model_folder)) > 0:
|
|
347
348
|
self.log.info('No need to download model %s (already exists)', self.target_model.version)
|
|
349
|
+
else:
|
|
350
|
+
os.makedirs(target_model_folder, exist_ok=True)
|
|
351
|
+
try:
|
|
352
|
+
await self.data_exchanger.download_model(target_model_folder,
|
|
353
|
+
Context(organization=self.organization,
|
|
354
|
+
project=self.project),
|
|
355
|
+
self.target_model.id,
|
|
356
|
+
self.detector_logic.model_format)
|
|
357
|
+
self.log.info('Downloaded model %s', self.target_model.version)
|
|
358
|
+
except Exception:
|
|
359
|
+
self.log.exception('Could not download model %s', self.target_model.version)
|
|
360
|
+
shutil.rmtree(target_model_folder, ignore_errors=True)
|
|
361
|
+
return
|
|
348
362
|
try:
|
|
349
363
|
os.unlink(model_symlink)
|
|
350
364
|
os.remove(model_symlink)
|
|
@@ -391,9 +405,9 @@ class DetectorNode(Node):
|
|
|
391
405
|
self.log.info('Status sync failed: not connected')
|
|
392
406
|
raise Exception('Status sync failed: not connected')
|
|
393
407
|
|
|
394
|
-
|
|
408
|
+
if self.detector_logic.model_info is not None:
|
|
395
409
|
current_model = self.detector_logic.model_info.version
|
|
396
|
-
|
|
410
|
+
else:
|
|
397
411
|
current_model = None
|
|
398
412
|
|
|
399
413
|
target_model_version = self.target_model.version if self.target_model else None
|
|
@@ -413,16 +427,23 @@ class DetectorNode(Node):
|
|
|
413
427
|
self.log_status_on_change(status.state or 'None', status)
|
|
414
428
|
|
|
415
429
|
# NOTE: sending organization and project is no longer required!
|
|
416
|
-
|
|
430
|
+
try:
|
|
431
|
+
response = await self.sio_client.call('update_detector', (self.organization, self.project, jsonable_encoder(asdict(status))))
|
|
432
|
+
except TimeoutError:
|
|
433
|
+
self.socket_connection_broken = True
|
|
434
|
+
self.log.exception('TimeoutError for sending status update (will try to reconnect):')
|
|
435
|
+
raise Exception('Status update failed due to timeout') from None
|
|
436
|
+
|
|
417
437
|
if not response:
|
|
418
438
|
self.socket_connection_broken = True
|
|
419
|
-
|
|
439
|
+
self.log.error('Status update failed (will try to reconnect): %s', response)
|
|
440
|
+
raise Exception('Status update failed: Did not receive a response from the learning loop')
|
|
420
441
|
|
|
421
442
|
socket_response = from_dict(data_class=SocketResponse, data=response)
|
|
422
443
|
if not socket_response.success:
|
|
423
444
|
self.socket_connection_broken = True
|
|
424
|
-
self.log.error('
|
|
425
|
-
raise Exception(f'
|
|
445
|
+
self.log.error('Status update failed (will try to reconnect): %s', response)
|
|
446
|
+
raise Exception(f'Status update failed. Response from learning loop: {response}')
|
|
426
447
|
|
|
427
448
|
assert socket_response.payload is not None
|
|
428
449
|
|
|
@@ -100,31 +100,31 @@ class LoopCommunicator():
|
|
|
100
100
|
raise TimeoutError('Backend not ready within timeout')
|
|
101
101
|
await asyncio.sleep(10)
|
|
102
102
|
|
|
103
|
-
async def
|
|
103
|
+
async def _retry_on_401(self, func: Callable[..., Awaitable[httpx.Response]], *args, **kwargs) -> httpx.Response:
|
|
104
104
|
response = await func(*args, **kwargs)
|
|
105
105
|
if response.status_code == 401:
|
|
106
106
|
await self.ensure_login(relogin=True)
|
|
107
107
|
response = await func(*args, **kwargs)
|
|
108
108
|
return response
|
|
109
109
|
|
|
110
|
-
async def get(self, path: str, requires_login: bool = True, api_prefix: str = '/api') -> httpx.Response:
|
|
110
|
+
async def get(self, path: str, requires_login: bool = True, api_prefix: str = '/api', timeout: int = 60) -> httpx.Response:
|
|
111
111
|
if requires_login:
|
|
112
112
|
await self.ensure_login()
|
|
113
|
-
return await self.
|
|
113
|
+
return await self._retry_on_401(self._get, path, api_prefix, timeout)
|
|
114
114
|
return await self._get(path, api_prefix)
|
|
115
115
|
|
|
116
116
|
@retry_on_429
|
|
117
|
-
async def _get(self, path: str, api_prefix: str) -> httpx.Response:
|
|
118
|
-
return await self.async_client.get(api_prefix+path)
|
|
117
|
+
async def _get(self, path: str, api_prefix: str, timeout: int = 60) -> httpx.Response:
|
|
118
|
+
return await self.async_client.get(api_prefix+path, timeout=timeout)
|
|
119
119
|
|
|
120
|
-
async def put(self, path: str, files: Optional[List[str]] = None, requires_login: bool = True, api_prefix: str = '/api', **kwargs) -> httpx.Response:
|
|
120
|
+
async def put(self, path: str, files: Optional[List[str]] = None, requires_login: bool = True, api_prefix: str = '/api', timeout: int = 60, **kwargs) -> httpx.Response:
|
|
121
121
|
if requires_login:
|
|
122
122
|
await self.ensure_login()
|
|
123
|
-
return await self.
|
|
124
|
-
return await self._put(path, files, api_prefix, **kwargs)
|
|
123
|
+
return await self._retry_on_401(self._put, path, files, api_prefix, timeout, **kwargs)
|
|
124
|
+
return await self._put(path, files, api_prefix, timeout, **kwargs)
|
|
125
125
|
|
|
126
126
|
@retry_on_429
|
|
127
|
-
async def _put(self, path: str, files: Optional[List[str]], api_prefix: str, **kwargs) -> httpx.Response:
|
|
127
|
+
async def _put(self, path: str, files: Optional[List[str]], api_prefix: str, timeout: int = 60, **kwargs) -> httpx.Response:
|
|
128
128
|
if files is None:
|
|
129
129
|
return await self.async_client.put(api_prefix+path, **kwargs)
|
|
130
130
|
|
|
@@ -139,29 +139,29 @@ class LoopCommunicator():
|
|
|
139
139
|
|
|
140
140
|
try:
|
|
141
141
|
file_list = [('files', fh) for fh in file_handles] # Use file handles
|
|
142
|
-
response = await self.async_client.put(api_prefix+path, files=file_list)
|
|
142
|
+
response = await self.async_client.put(api_prefix+path, files=file_list, timeout=timeout)
|
|
143
143
|
finally:
|
|
144
144
|
for fh in file_handles:
|
|
145
145
|
fh.close() # Ensure all files are closed
|
|
146
146
|
|
|
147
147
|
return response
|
|
148
148
|
|
|
149
|
-
async def post(self, path: str, requires_login: bool = True, api_prefix: str = '/api', **kwargs) -> httpx.Response:
|
|
149
|
+
async def post(self, path: str, requires_login: bool = True, api_prefix: str = '/api', timeout: int = 60, **kwargs) -> httpx.Response:
|
|
150
150
|
if requires_login:
|
|
151
151
|
await self.ensure_login()
|
|
152
|
-
return await self.
|
|
152
|
+
return await self._retry_on_401(self._post, path, api_prefix, timeout, **kwargs)
|
|
153
153
|
return await self._post(path, api_prefix, **kwargs)
|
|
154
154
|
|
|
155
155
|
@retry_on_429
|
|
156
|
-
async def _post(self, path, api_prefix='/api', **kwargs) -> httpx.Response:
|
|
157
|
-
return await self.async_client.post(api_prefix+path, **kwargs)
|
|
156
|
+
async def _post(self, path, api_prefix='/api', timeout: int = 60, **kwargs) -> httpx.Response:
|
|
157
|
+
return await self.async_client.post(api_prefix+path, timeout=timeout, **kwargs)
|
|
158
158
|
|
|
159
|
-
async def delete(self, path: str, requires_login: bool = True, api_prefix: str = '/api', **kwargs) -> httpx.Response:
|
|
159
|
+
async def delete(self, path: str, requires_login: bool = True, api_prefix: str = '/api', timeout: int = 60, **kwargs) -> httpx.Response:
|
|
160
160
|
if requires_login:
|
|
161
161
|
await self.ensure_login()
|
|
162
|
-
return await self.
|
|
162
|
+
return await self._retry_on_401(self._delete, path, api_prefix, timeout, **kwargs)
|
|
163
163
|
return await self._delete(path, api_prefix, **kwargs)
|
|
164
164
|
|
|
165
165
|
@retry_on_429
|
|
166
|
-
async def _delete(self, path, api_prefix, **kwargs) -> httpx.Response:
|
|
167
|
-
return await self.async_client.delete(api_prefix+path, **kwargs)
|
|
166
|
+
async def _delete(self, path, api_prefix, timeout: int = 60, **kwargs) -> httpx.Response:
|
|
167
|
+
return await self.async_client.delete(api_prefix+path, timeout=timeout, **kwargs)
|
learning_loop_node/node.py
CHANGED
|
@@ -74,6 +74,7 @@ class Node(FastAPI):
|
|
|
74
74
|
self.repeat_loop_lock = asyncio.Lock()
|
|
75
75
|
|
|
76
76
|
self.previous_state: Optional[str] = None
|
|
77
|
+
self.repeat_loop_cycle_sec = 5
|
|
77
78
|
|
|
78
79
|
def log_status_on_change(self, current_state_str: str, full_status: Any):
|
|
79
80
|
if self.previous_state != current_state_str:
|
|
@@ -146,7 +147,7 @@ class Node(FastAPI):
|
|
|
146
147
|
except Exception:
|
|
147
148
|
self.log.exception('error in repeat loop')
|
|
148
149
|
|
|
149
|
-
await asyncio.sleep(
|
|
150
|
+
await asyncio.sleep(self.repeat_loop_cycle_sec)
|
|
150
151
|
|
|
151
152
|
async def _ensure_sio_connection(self):
|
|
152
153
|
if self.socket_connection_broken or self._sio_client is None or not self.sio_client.connected:
|
|
@@ -129,10 +129,6 @@ class MockDetectorLogic(DetectorLogic): # pylint: disable=abstract-method
|
|
|
129
129
|
model_name="mock",
|
|
130
130
|
)])
|
|
131
131
|
|
|
132
|
-
@property
|
|
133
|
-
def is_initialized(self):
|
|
134
|
-
return True
|
|
135
|
-
|
|
136
132
|
def evaluate_with_all_info(self, image: np.ndarray, tags: List[str], source: Optional[str] = None, creation_date: Optional[str] = None):
|
|
137
133
|
return self.image_metadata
|
|
138
134
|
|
|
@@ -51,9 +51,6 @@ def test_rest_detect(test_detector_node: DetectorNode, grouping_key: str):
|
|
|
51
51
|
headers = {grouping_key: '0:0:0:0', 'tags': 'some_tag'}
|
|
52
52
|
|
|
53
53
|
assert isinstance(test_detector_node.detector_logic, TestingDetectorLogic)
|
|
54
|
-
# test_detector_node.detector_logic.mock_is_initialized = True
|
|
55
|
-
# print(test_detector_node.detector_logic.mock_is_initialized)
|
|
56
|
-
# print(test_detector_node.detector_logic.is_initialized)
|
|
57
54
|
response = requests.post(f'http://localhost:{GLOBALS.detector_port}/detect',
|
|
58
55
|
files=image, headers=headers, timeout=30)
|
|
59
56
|
assert response.status_code == 200
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: learning-loop-node
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.7
|
|
4
4
|
Summary: Python Library for Nodes which connect to the Zauberzeug Learning Loop
|
|
5
5
|
Home-page: https://github.com/zauberzeug/learning_loop_node
|
|
6
6
|
License: MIT
|
|
@@ -63,8 +63,8 @@ You can configure connection to our Learning Loop by specifying the following en
|
|
|
63
63
|
| LOOP_USERNAME | USERNAME | Learning Loop user name | all besides Detector |
|
|
64
64
|
| LOOP_PASSWORD | PASSWORD | Learning Loop password | all besides Detector |
|
|
65
65
|
| LOOP_SSL_CERT_PATH | - | Path to the SSL certificate | all (opt.) |
|
|
66
|
-
| LOOP_ORGANIZATION | ORGANIZATION | Organization
|
|
67
|
-
| LOOP_PROJECT | PROJECT | Project
|
|
66
|
+
| LOOP_ORGANIZATION | ORGANIZATION | Organization ID | Detector |
|
|
67
|
+
| LOOP_PROJECT | PROJECT | Project ID | Detector (opt.) |
|
|
68
68
|
| MIN_UNCERTAIN_THRESHOLD | - | smallest confidence (float) at which auto-upload will happen | Detector (opt.) |
|
|
69
69
|
| MAX_UNCERTAIN_THRESHOLD | - | largest confidence (float) at which auto-upload will happen | Detector (opt.) |
|
|
70
70
|
| INFERENCE_BATCH_SIZE | - | Batch size of trainer when calculating detections | Trainer (opt.) |
|
|
@@ -73,6 +73,8 @@ You can configure connection to our Learning Loop by specifying the following en
|
|
|
73
73
|
| TRAINER_IDLE_TIMEOUT_SEC | - | Automatically shutdown trainer after timeout (in seconds) | Trainer (opt.) |
|
|
74
74
|
| USE_BACKDOOR_CONTROLS | - | Always enable backdoor controls (set to 1) | Trainer / Detector (opt.) |
|
|
75
75
|
|
|
76
|
+
Note that organization and project IDs are always lower case and may differ from the names in the Learning Loop which can have uppercase letters.
|
|
77
|
+
|
|
76
78
|
#### Testing
|
|
77
79
|
|
|
78
80
|
We use github actions for CI. Tests can also be executed locally by running
|
|
@@ -9,10 +9,10 @@ learning_loop_node/data_classes/general.py,sha256=r7fVfuQvbo8qOTT7zylgfM45TbIvYu
|
|
|
9
9
|
learning_loop_node/data_classes/image_metadata.py,sha256=56nNSf_7aMlvKsJOG8vKCzJHcqKGHVRoULp85pJ2imA,1598
|
|
10
10
|
learning_loop_node/data_classes/socket_response.py,sha256=tIdt-oYf6ULoJIDYQCecNM9OtWR6_wJ9tL0Ksu83Vko,655
|
|
11
11
|
learning_loop_node/data_classes/training.py,sha256=FFPsr2AA7ynYz39MLZaFJ0sF_9Axll5HHbAA8nnirp0,5726
|
|
12
|
-
learning_loop_node/data_exchanger.py,sha256=
|
|
12
|
+
learning_loop_node/data_exchanger.py,sha256=2gV2epi24NQm8MgZKhi-sUNAP8CmcFLwihLagHxzKgA,9070
|
|
13
13
|
learning_loop_node/detector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
learning_loop_node/detector/detector_logic.py,sha256=
|
|
15
|
-
learning_loop_node/detector/detector_node.py,sha256=
|
|
14
|
+
learning_loop_node/detector/detector_logic.py,sha256=s1EFLrk_SFvLJOsIj9b0lp-Oq0DgfVWxT6Q34Vmi_JE,2243
|
|
15
|
+
learning_loop_node/detector/detector_node.py,sha256=svf4HsP7LjR_5aVRsqRzyJkdXWE_tNyHU4sNYJCpMO4,26334
|
|
16
16
|
learning_loop_node/detector/exceptions.py,sha256=C6KbNPlSbtfgDrZx2Hbhm7Suk9jVoR3fMRCO0CkrMsQ,196
|
|
17
17
|
learning_loop_node/detector/inbox_filter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
18
|
learning_loop_node/detector/inbox_filter/cam_observation_history.py,sha256=1PHgXRrhSQ34HSFw7mdX8ndRxHf_i1aP5nXXnrZxhAY,3312
|
|
@@ -38,8 +38,8 @@ learning_loop_node/helpers/environment_reader.py,sha256=6DxDJecLHxiGczByhyVa_Jss
|
|
|
38
38
|
learning_loop_node/helpers/gdrive_downloader.py,sha256=zeYJciTAJVRpu_eFjwgYLCpIa6hU1d71anqEBb564Rk,1145
|
|
39
39
|
learning_loop_node/helpers/log_conf.py,sha256=hqVAa_9NnYEU6N0dcOKmph82p7MpgKqeF_eomTLYzWY,961
|
|
40
40
|
learning_loop_node/helpers/misc.py,sha256=J29iBmsEUAraKKDN1m1NKiHQ3QrP5ub5HBU6cllSP2g,7384
|
|
41
|
-
learning_loop_node/loop_communication.py,sha256=
|
|
42
|
-
learning_loop_node/node.py,sha256=
|
|
41
|
+
learning_loop_node/loop_communication.py,sha256=opulqBKRLXlUQgjA3t0pg8CNA-JXJRCPPUspRxRuuGw,7556
|
|
42
|
+
learning_loop_node/node.py,sha256=IRV81q1G3-A6_BLNqB3NBT7T_dN5OXegBoM9JHMJuLM,11030
|
|
43
43
|
learning_loop_node/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
44
44
|
learning_loop_node/rest.py,sha256=omwlRHLnyG-kgCBVnZDk5_SAPobL9g7slWeX21wsPGw,1551
|
|
45
45
|
learning_loop_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -48,14 +48,14 @@ learning_loop_node/tests/annotator/conftest.py,sha256=G4ZvdZUdvPp9bYCzg3eEVkGCeX
|
|
|
48
48
|
learning_loop_node/tests/annotator/pytest.ini,sha256=8QdjmawLy1zAzXrJ88or1kpFDhJw0W5UOnDfGGs_igU,262
|
|
49
49
|
learning_loop_node/tests/annotator/test_annotator_node.py,sha256=UWRXRSBc1e795ftkp7xrEXbyR4LYvFDDHRpZGqC3vr8,1974
|
|
50
50
|
learning_loop_node/tests/detector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
51
|
-
learning_loop_node/tests/detector/conftest.py,sha256=
|
|
51
|
+
learning_loop_node/tests/detector/conftest.py,sha256=gut-RaacarhWJNCvGEz7O7kj3cS7vJ4SvAxCmR87PIw,5263
|
|
52
52
|
learning_loop_node/tests/detector/inbox_filter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
53
|
learning_loop_node/tests/detector/inbox_filter/test_observation.py,sha256=k4WYdvnuV7d_r7zI4M2aA8WuBjm0aycQ0vj1rGE2q4w,1370
|
|
54
54
|
learning_loop_node/tests/detector/inbox_filter/test_relevance_group.py,sha256=r-wABFQVsTNTjv7vYGr8wbHfOWy43F_B14ZDWHfiZ-A,7613
|
|
55
55
|
learning_loop_node/tests/detector/inbox_filter/test_unexpected_observations_count.py,sha256=3KKwf-J9oJRMIuuVju2vT9IM9vWhKvswPiXJI8KxmcU,1661
|
|
56
56
|
learning_loop_node/tests/detector/pytest.ini,sha256=8QdjmawLy1zAzXrJ88or1kpFDhJw0W5UOnDfGGs_igU,262
|
|
57
57
|
learning_loop_node/tests/detector/test.jpg,sha256=msA-vHPmvPiro_D102Qmn1fn4vNfooqYYEXPxZUmYpk,161390
|
|
58
|
-
learning_loop_node/tests/detector/test_client_communication.py,sha256=
|
|
58
|
+
learning_loop_node/tests/detector/test_client_communication.py,sha256=PUjnWnY-9RCZe-gqrtWf3o0ylCNH3WuzHoL7v3eAjAQ,8984
|
|
59
59
|
learning_loop_node/tests/detector/test_detector_node.py,sha256=0ZMV6coAvdq-nH8CwY9_LR2tUcH9VLcAB1CWuwHQMpo,3023
|
|
60
60
|
learning_loop_node/tests/detector/test_outbox.py,sha256=IfCz4iBmYA4bm3TK4q2NmWyzQCwZWhUbBrKQNHGxZM4,3007
|
|
61
61
|
learning_loop_node/tests/detector/test_relevance_filter.py,sha256=ZKcCstFWCDxJzKdVlAe8E6sZzv5NiH8mADhaZjokHoU,2052
|
|
@@ -97,6 +97,6 @@ learning_loop_node/trainer/test_executor.py,sha256=6BVGDN_6f5GEMMEvDLSG1yzMybSvg
|
|
|
97
97
|
learning_loop_node/trainer/trainer_logic.py,sha256=eK-01qZzi10UjLMCQX8vy5eW2FoghPj3rzzDC-s3Si4,8792
|
|
98
98
|
learning_loop_node/trainer/trainer_logic_generic.py,sha256=RQqon8JIVzxaNh0KdEe6tMxebsY0DgZllEohHR-AgqU,26846
|
|
99
99
|
learning_loop_node/trainer/trainer_node.py,sha256=Dl4ZQAjjXQggibeBjvhXAoFClw1ZX2Kkt3v_fjrJnCI,4508
|
|
100
|
-
learning_loop_node-0.13.
|
|
101
|
-
learning_loop_node-0.13.
|
|
102
|
-
learning_loop_node-0.13.
|
|
100
|
+
learning_loop_node-0.13.7.dist-info/METADATA,sha256=X408fRS2UiAjUgqlpo-zkJNqkJgsjvtn56jq4rdgVQE,12908
|
|
101
|
+
learning_loop_node-0.13.7.dist-info/WHEEL,sha256=WGfLGfLX43Ei_YORXSnT54hxFygu34kMpcQdmgmEwCQ,88
|
|
102
|
+
learning_loop_node-0.13.7.dist-info/RECORD,,
|
|
File without changes
|