learning-loop-node 0.8.5__py3-none-any.whl → 0.8.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 +16 -2
- learning_loop_node/detector/detector_logic.py +1 -1
- learning_loop_node/detector/detector_node.py +7 -2
- learning_loop_node/detector/inbox_filter/cam_observation_history.py +2 -1
- learning_loop_node/tests/test_downloader.py +24 -1
- learning_loop_node/trainer/trainer_logic.py +3 -3
- {learning_loop_node-0.8.5.dist-info → learning_loop_node-0.8.7.dist-info}/METADATA +16 -14
- {learning_loop_node-0.8.5.dist-info → learning_loop_node-0.8.7.dist-info}/RECORD +9 -9
- {learning_loop_node-0.8.5.dist-info → learning_loop_node-0.8.7.dist-info}/WHEEL +0 -0
|
@@ -59,10 +59,23 @@ class DataExchanger():
|
|
|
59
59
|
logging.warning('context was not set yet')
|
|
60
60
|
return
|
|
61
61
|
|
|
62
|
+
await self.delete_corrupt_images(image_folder)
|
|
62
63
|
new_image_ids = await asyncio.get_event_loop().run_in_executor(None, DataExchanger.filter_existing_images, image_ids, image_folder)
|
|
63
64
|
paths, ids = create_resource_paths(self.context.organization, self.context.project, new_image_ids)
|
|
64
65
|
await self._download_images(paths, ids, image_folder)
|
|
65
66
|
|
|
67
|
+
@staticmethod
|
|
68
|
+
async def delete_corrupt_images(image_folder: str) -> None:
|
|
69
|
+
logging.info('deleting corrupt images')
|
|
70
|
+
n_deleted = 0
|
|
71
|
+
for image in glob(f'{image_folder}/*.jpg'):
|
|
72
|
+
if not await DataExchanger.is_valid_image(image):
|
|
73
|
+
logging.debug(f' deleting image {image}')
|
|
74
|
+
os.remove(image)
|
|
75
|
+
n_deleted += 1
|
|
76
|
+
|
|
77
|
+
logging.info(f'deleted {n_deleted} images')
|
|
78
|
+
|
|
66
79
|
@staticmethod
|
|
67
80
|
def filter_existing_images(all_image_ids, image_folder) -> List[str]:
|
|
68
81
|
logging.info(f'### Going to filter {len(all_image_ids)} images ids')
|
|
@@ -131,8 +144,9 @@ class DataExchanger():
|
|
|
131
144
|
if not await self.is_valid_image(filename):
|
|
132
145
|
os.remove(filename)
|
|
133
146
|
|
|
134
|
-
|
|
135
|
-
|
|
147
|
+
@staticmethod
|
|
148
|
+
async def is_valid_image(filename: str) -> bool:
|
|
149
|
+
if not os.path.isfile(filename) or os.path.getsize(filename) == 0:
|
|
136
150
|
return False
|
|
137
151
|
if not check_jpeg:
|
|
138
152
|
return True
|
|
@@ -46,7 +46,7 @@ class DetectorLogic():
|
|
|
46
46
|
|
|
47
47
|
@abstractmethod
|
|
48
48
|
def init(self):
|
|
49
|
-
"""Initialize the model.
|
|
49
|
+
"""Called when a (new) model was loaded. Initialize the model. Model information available via `self.model_info`"""
|
|
50
50
|
|
|
51
51
|
@abstractmethod
|
|
52
52
|
def evaluate(self, image: np.ndarray) -> Detections:
|
|
@@ -142,10 +142,13 @@ class DetectorNode(Node):
|
|
|
142
142
|
|
|
143
143
|
detection_data = data.get('detections', {})
|
|
144
144
|
if detection_data and self.detector_logic.is_initialized:
|
|
145
|
-
|
|
145
|
+
try:
|
|
146
|
+
detections = from_dict(data_class=Detections, data=detection_data)
|
|
147
|
+
except Exception as e:
|
|
148
|
+
self.log.exception('could not parse detections')
|
|
149
|
+
return {'error': str(e)}
|
|
146
150
|
detections = self.add_category_id_to_detections(self.detector_logic.model_info, detections)
|
|
147
151
|
else:
|
|
148
|
-
print(f'No detections: {detection_data}', flush=True)
|
|
149
152
|
detections = Detections()
|
|
150
153
|
|
|
151
154
|
tags = data.get('tags', [])
|
|
@@ -292,6 +295,8 @@ class DetectorNode(Node):
|
|
|
292
295
|
self.log.error('could not reload app')
|
|
293
296
|
|
|
294
297
|
async def get_detections(self, raw_image: np.ndarray, camera_id: Optional[str], tags: List[str], autoupload: Optional[str] = None) -> Optional[Dict]:
|
|
298
|
+
"""Note: raw_image is a numpy array of type uint8, but not in the correrct shape!
|
|
299
|
+
It can be converted e.g. using cv2.imdecode(raw_image, cv2.IMREAD_COLOR)"""
|
|
295
300
|
loop = asyncio.get_event_loop()
|
|
296
301
|
detections: Detections = await loop.run_in_executor(None, self.detector_logic.evaluate, raw_image)
|
|
297
302
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from typing import List, Union
|
|
2
3
|
|
|
3
4
|
from learning_loop_node.data_classes import (BoxDetection,
|
|
@@ -39,7 +40,7 @@ class CamObservationHistory:
|
|
|
39
40
|
continue
|
|
40
41
|
|
|
41
42
|
self.recent_observations.append(Observation(detection))
|
|
42
|
-
if 0.3 <= detection.confidence <= 0.6:
|
|
43
|
+
if float(os.environ.get('MIN_UNCERTAIN_THRESHOLD', '0.3')) <= detection.confidence <= float(os.environ.get('MAX_UNCERTAIN_THRESHOLD', '0.6')):
|
|
43
44
|
causes.add('uncertain')
|
|
44
45
|
|
|
45
46
|
return list(causes)
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
|
|
1
4
|
from learning_loop_node.data_classes import Context
|
|
2
|
-
from learning_loop_node.data_exchanger import DataExchanger
|
|
5
|
+
from learning_loop_node.data_exchanger import DataExchanger, check_jpeg
|
|
3
6
|
from learning_loop_node.globals import GLOBALS
|
|
4
7
|
|
|
5
8
|
from . import test_helper
|
|
@@ -46,3 +49,23 @@ async def test_download_training_data(data_exchanger: DataExchanger):
|
|
|
46
49
|
image_ids = await data_exchanger.fetch_image_ids()
|
|
47
50
|
image_data = await data_exchanger.download_images_data(image_ids)
|
|
48
51
|
assert len(image_data) == 3
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def test_removal_of_corrupted_images(data_exchanger: DataExchanger):
|
|
55
|
+
image_ids = await data_exchanger.fetch_image_ids()
|
|
56
|
+
|
|
57
|
+
shutil.rmtree('/tmp/img_folder', ignore_errors=True)
|
|
58
|
+
os.makedirs('/tmp/img_folder', exist_ok=True)
|
|
59
|
+
await data_exchanger.download_images(image_ids, '/tmp/img_folder')
|
|
60
|
+
num_images = len(os.listdir('/tmp/img_folder'))
|
|
61
|
+
|
|
62
|
+
# Generate two corrupted images
|
|
63
|
+
with open('/tmp/img_folder/c0.jpg', 'w') as f:
|
|
64
|
+
f.write('')
|
|
65
|
+
with open('/tmp/img_folder/c1.jpg', 'w') as f:
|
|
66
|
+
f.write('I am no image')
|
|
67
|
+
|
|
68
|
+
await data_exchanger.delete_corrupt_images('/tmp/img_folder')
|
|
69
|
+
|
|
70
|
+
assert len(os.listdir('/tmp/img_folder')) == num_images if check_jpeg else num_images - 1
|
|
71
|
+
shutil.rmtree('/tmp/img_folder', ignore_errors=True)
|
|
@@ -54,8 +54,8 @@ class TrainerLogic():
|
|
|
54
54
|
self._training: Optional[Training] = None
|
|
55
55
|
self._active_training_io: Optional[ActiveTrainingIO] = None
|
|
56
56
|
self._node: Optional[TrainerNode] = None
|
|
57
|
-
self.restart_after_training =
|
|
58
|
-
self.keep_old_trainings =
|
|
57
|
+
self.restart_after_training = os.environ.get('RESTART_AFTER_TRAINING', 'FALSE').lower() in ['true', '1']
|
|
58
|
+
self.keep_old_trainings = os.environ.get('KEEP_OLD_TRAININGS', 'FALSE').lower() in ['true', '1']
|
|
59
59
|
self.inference_batch_size = int(os.environ.get('INFERENCE_BATCH_SIZE', '10'))
|
|
60
60
|
logging.info(f'INFERENCE_BATCH_SIZE: {self.inference_batch_size}')
|
|
61
61
|
|
|
@@ -335,7 +335,7 @@ class TrainerLogic():
|
|
|
335
335
|
try:
|
|
336
336
|
new_model_id = await self._upload_model_return_new_id(self.training.context)
|
|
337
337
|
if new_model_id is None:
|
|
338
|
-
raise Exception('could not upload model')
|
|
338
|
+
raise Exception('could not upload model - maybe training failed')
|
|
339
339
|
assert new_model_id is not None, 'uploaded_model must be set'
|
|
340
340
|
logging.info(f'successfully uploaded model and received new model id: {new_model_id}')
|
|
341
341
|
self.training.model_id_for_detecting = new_model_id
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: learning-loop-node
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.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
|
|
@@ -18,8 +18,8 @@ Requires-Dist: aiofiles (>=0.7.0,<0.8.0)
|
|
|
18
18
|
Requires-Dist: aiohttp (>=3.8.4,<4.0.0)
|
|
19
19
|
Requires-Dist: async_generator (>=1.10,<2.0)
|
|
20
20
|
Requires-Dist: dacite (>=1.8.1,<2.0.0)
|
|
21
|
-
Requires-Dist: fastapi (>=0.
|
|
22
|
-
Requires-Dist: fastapi-socketio (>=0.0.
|
|
21
|
+
Requires-Dist: fastapi (>=0.93,<1.0.0)
|
|
22
|
+
Requires-Dist: fastapi-socketio (>=0.0.10,<0.0.11)
|
|
23
23
|
Requires-Dist: fastapi-utils (>=0.2.1,<0.3.0)
|
|
24
24
|
Requires-Dist: httpx (>=0.24.1,<0.25.0)
|
|
25
25
|
Requires-Dist: icecream (>=2.1.0,<3.0.0)
|
|
@@ -33,7 +33,7 @@ Requires-Dist: python-socketio (>=5.7.2,<6.0.0)
|
|
|
33
33
|
Requires-Dist: requests (>=2.25.1,<3.0.0)
|
|
34
34
|
Requires-Dist: simplejson (>=3.17.2,<4.0.0)
|
|
35
35
|
Requires-Dist: tqdm (>=4.63.0,<5.0.0)
|
|
36
|
-
Requires-Dist: uvicorn (>=0.
|
|
36
|
+
Requires-Dist: uvicorn[standard] (>=0.22.0,<0.23.0)
|
|
37
37
|
Requires-Dist: werkzeug (>=2.0.1,<3.0.0)
|
|
38
38
|
Project-URL: Repository, https://github.com/zauberzeug/learning_loop_node
|
|
39
39
|
Description-Content-Type: text/markdown
|
|
@@ -57,16 +57,18 @@ To start a node you have to implement the logic by inheriting from the correspon
|
|
|
57
57
|
|
|
58
58
|
You can configure connection to our Learning Loop by specifying the following environment variables before starting:
|
|
59
59
|
|
|
60
|
-
| Name
|
|
61
|
-
|
|
|
62
|
-
| LOOP_HOST
|
|
63
|
-
| LOOP_USERNAME
|
|
64
|
-
| LOOP_PASSWORD
|
|
65
|
-
| LOOP_ORGANIZATION
|
|
66
|
-
| LOOP_PROJECT
|
|
67
|
-
|
|
|
68
|
-
|
|
|
69
|
-
|
|
|
60
|
+
| Name | Alias | Purpose | Required by |
|
|
61
|
+
| ----------------------- | ------------ | ------------------------------------------------------------ | -------------------- |
|
|
62
|
+
| LOOP_HOST | HOST | Learning Loop address (e.g. learning-loop.ai) | all |
|
|
63
|
+
| LOOP_USERNAME | USERNAME | Learning Loop user name | all besides Detector |
|
|
64
|
+
| LOOP_PASSWORD | PASSWORD | Learning Loop password | all besides Detector |
|
|
65
|
+
| LOOP_ORGANIZATION | ORGANIZATION | Organization name | Detector |
|
|
66
|
+
| LOOP_PROJECT | PROJECT | Project name | Detector |
|
|
67
|
+
| MIN_UNCERTAIN_THRESHOLD | PROJECT | smallest confidence (float) at which auto-upload will happen | Detector |
|
|
68
|
+
| MAX_UNCERTAIN_THRESHOLD | PROJECT | largest confidence (float) at which auto-upload will happen | Detector |
|
|
69
|
+
| INFERENCE_BATCH_SIZE | - | Batch size of trainer when calculating detections | Trainer (opt.) |
|
|
70
|
+
| RESTART_AFTER_TRAINING | - | Restart the trainer after training (set to 1) | Trainer (opt.) |
|
|
71
|
+
| KEEP_OLD_TRAININGS | - | Do not delete old trainings (set to 1) | Trainer (opt.) |
|
|
70
72
|
|
|
71
73
|
#### Testing
|
|
72
74
|
|
|
@@ -14,12 +14,12 @@ learning_loop_node/data_classes/detections.py,sha256=J-8z6OrUw_QMyaUOdgODxMF8Z97
|
|
|
14
14
|
learning_loop_node/data_classes/general.py,sha256=3LYglqOMvQIfNn0_UUwbmdyb905Fzna1yf5p55cnPmE,4635
|
|
15
15
|
learning_loop_node/data_classes/socket_response.py,sha256=tIdt-oYf6ULoJIDYQCecNM9OtWR6_wJ9tL0Ksu83Vko,655
|
|
16
16
|
learning_loop_node/data_classes/training.py,sha256=VLGnZTJXh6p7K16lO41lPNlMl6EAIO_kVPfBu8w96ug,4870
|
|
17
|
-
learning_loop_node/data_exchanger.py,sha256=
|
|
17
|
+
learning_loop_node/data_exchanger.py,sha256=YAGn7laOZpVIwcK6O0WX-6ec36ypvxlW-u7UfCgblDQ,9876
|
|
18
18
|
learning_loop_node/detector/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
19
|
-
learning_loop_node/detector/detector_logic.py,sha256=
|
|
20
|
-
learning_loop_node/detector/detector_node.py,sha256=
|
|
19
|
+
learning_loop_node/detector/detector_logic.py,sha256=qjnFzrVO020-kvA1dcI5zoZLANynMx3RR61zEIS0udo,1741
|
|
20
|
+
learning_loop_node/detector/detector_node.py,sha256=1kUFLIa_S1mZ0PqfeqsNdcrGfJYcR09mhLSp6XciDZY,16559
|
|
21
21
|
learning_loop_node/detector/inbox_filter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
-
learning_loop_node/detector/inbox_filter/cam_observation_history.py,sha256=
|
|
22
|
+
learning_loop_node/detector/inbox_filter/cam_observation_history.py,sha256=5iNR2cEPujjY83sfQug97-hZ-T8iFc44gi1s_yd7GO0,3437
|
|
23
23
|
learning_loop_node/detector/inbox_filter/relevance_filter.py,sha256=s2FuwZ-tD_5obkSutstjc8pE_hLGbrv9WjrEO9t8rJ8,1011
|
|
24
24
|
learning_loop_node/detector/inbox_filter/tests/test_observation.py,sha256=ORN08yjprqmgmtU25RsVysniyrWX-qGvqFN8ZkkYxow,1385
|
|
25
25
|
learning_loop_node/detector/inbox_filter/tests/test_relevance_group.py,sha256=RUgsk1CnKSOCRZBzNjE7AZTqk06-yelgUqvHFRLH7_I,7865
|
|
@@ -53,7 +53,7 @@ learning_loop_node/tests/test_data/file_1.txt,sha256=Lis06nfvbFPVCBZyEgQlfI_Nle2
|
|
|
53
53
|
learning_loop_node/tests/test_data/file_2.txt,sha256=Xp8EETGhZBdVAgb4URowSSpOytwwwJdV0Renkdur7R8,19
|
|
54
54
|
learning_loop_node/tests/test_data/model.json,sha256=_xNDucGOWila8gWnu8yFfrqmQ45Xq-_39eLKzjRtvpE,516
|
|
55
55
|
learning_loop_node/tests/test_data_classes.py,sha256=m8LEk1quGErxuPzNdW_ExqQjkwE4u7ribwnTdyeiHR8,788
|
|
56
|
-
learning_loop_node/tests/test_downloader.py,sha256=
|
|
56
|
+
learning_loop_node/tests/test_downloader.py,sha256=sMhpQspkzw3VrYG2ws064SRCucf_9ceD_e3qkgzJxtk,2755
|
|
57
57
|
learning_loop_node/tests/test_executor.py,sha256=ILBKfX-A2zlxfot0xoz3jpG3XcEMo3kxQKkPgoz_aT4,1948
|
|
58
58
|
learning_loop_node/tests/test_helper.py,sha256=fFcmTqgw1RJtuQHWk5_Mn9hc-wXIFcQow2JphLFwrlI,2412
|
|
59
59
|
learning_loop_node/tests/test_learning_loop_node.py,sha256=4qWi1ovBzebUAbvw8ecSa-TBGKYuJvlKe2AMnMZ-Qs8,701
|
|
@@ -79,9 +79,9 @@ learning_loop_node/trainer/tests/states/test_state_upload_model.py,sha256=3BzIpA
|
|
|
79
79
|
learning_loop_node/trainer/tests/test_errors.py,sha256=QHZK1YVSXqQskUZbl0cU1sjJeDv3dz_2iF0LFhMwe58,1918
|
|
80
80
|
learning_loop_node/trainer/tests/test_trainer_states.py,sha256=HeIFdAStf6KBT9lQGpVdeLQbLCgb0cqDO2Z_FJ1IO8Y,1144
|
|
81
81
|
learning_loop_node/trainer/tests/testing_trainer_logic.py,sha256=DCKNhBFsV2uN-MNCpkKx8XNoAwWc_7Kfrj1kRpJEEVk,3995
|
|
82
|
-
learning_loop_node/trainer/trainer_logic.py,sha256=
|
|
82
|
+
learning_loop_node/trainer/trainer_logic.py,sha256=_Z5LAVRRL9PFmWgWng3zqdrOymFQuv8AeZHGxLHcSno,31864
|
|
83
83
|
learning_loop_node/trainer/trainer_node.py,sha256=-bJ6xoOdqG9aZL5Ok2Z5IHGrQIbnZ4KRy323CngYnNI,7780
|
|
84
84
|
learning_loop_node/trainer/training_syncronizer.py,sha256=qzJbv9WqApbM1aNQNg98IjTWed2z__UKocQ_MJP9gz8,1884
|
|
85
|
-
learning_loop_node-0.8.
|
|
86
|
-
learning_loop_node-0.8.
|
|
87
|
-
learning_loop_node-0.8.
|
|
85
|
+
learning_loop_node-0.8.7.dist-info/METADATA,sha256=cQ7e5nHbiuoTU_pnRCHTaLEUeYOMF5YOlk5GyBQPvoU,9157
|
|
86
|
+
learning_loop_node-0.8.7.dist-info/WHEEL,sha256=WGfLGfLX43Ei_YORXSnT54hxFygu34kMpcQdmgmEwCQ,88
|
|
87
|
+
learning_loop_node-0.8.7.dist-info/RECORD,,
|
|
File without changes
|