learning-loop-node 0.27.0__py3-none-any.whl → 0.27.2__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.
- learning_loop_node/data_classes/general.py +7 -2
- learning_loop_node/detector/detector_logic.py +1 -0
- learning_loop_node/detector/detector_node.py +8 -2
- learning_loop_node/helpers/entrypoint.py +9 -1
- learning_loop_node/trainer/trainer_logic_generic.py +14 -6
- {learning_loop_node-0.27.0.dist-info → learning_loop_node-0.27.2.dist-info}/METADATA +3 -2
- {learning_loop_node-0.27.0.dist-info → learning_loop_node-0.27.2.dist-info}/RECORD +9 -9
- {learning_loop_node-0.27.0.dist-info → learning_loop_node-0.27.2.dist-info}/WHEEL +0 -0
- {learning_loop_node-0.27.0.dist-info → learning_loop_node-0.27.2.dist-info}/top_level.txt +0 -0
|
@@ -12,6 +12,8 @@ from ..enums import CategoryType
|
|
|
12
12
|
|
|
13
13
|
KWONLY_SLOTS = {'kw_only': True, 'slots': True} if sys.version_info >= (3, 10) else {}
|
|
14
14
|
|
|
15
|
+
DEFAULT_CONF_THRESHOLD = 0.2
|
|
16
|
+
|
|
15
17
|
|
|
16
18
|
@dataclass(**KWONLY_SLOTS)
|
|
17
19
|
class Category():
|
|
@@ -59,8 +61,9 @@ class ModelInformation():
|
|
|
59
61
|
default=None, metadata={"description": "The path of the parent directory of the model in the file system."})
|
|
60
62
|
model_size: Optional[str] = field(default=None, metadata={
|
|
61
63
|
"description": "The size of the model (i.e. the specification or variant of the model architecture)."})
|
|
62
|
-
conf_threshold:
|
|
63
|
-
"description": "The minimum confidence
|
|
64
|
+
conf_threshold: float = field(default=DEFAULT_CONF_THRESHOLD, metadata={
|
|
65
|
+
"description": "The minimum confidence for the detections of this model: Loaded from the model if available. "
|
|
66
|
+
"A detector node's own setting overrides it."})
|
|
64
67
|
|
|
65
68
|
@property
|
|
66
69
|
def context(self):
|
|
@@ -80,6 +83,8 @@ class ModelInformation():
|
|
|
80
83
|
except Exception as exc:
|
|
81
84
|
raise Exception(f"could not read model information from file '{model_info_file_path}'") from exc
|
|
82
85
|
try:
|
|
86
|
+
# a null is a value the writer did not determine, so it reads as if the key were absent
|
|
87
|
+
content = {key: value for key, value in content.items() if value is not None}
|
|
83
88
|
model_information = from_dict(data_class=ModelInformation, data=content)
|
|
84
89
|
model_information.model_root_path = model_root_path
|
|
85
90
|
except Exception as exc:
|
|
@@ -10,6 +10,7 @@ class DetectorLogicFactory(Protocol):
|
|
|
10
10
|
|
|
11
11
|
The factory controls how the detector is constructed — implementations
|
|
12
12
|
can build synchronously or offload heavy work to a thread pool.
|
|
13
|
+
`model_info.conf_threshold` is the confidence the detector filters at.
|
|
13
14
|
"""
|
|
14
15
|
@property
|
|
15
16
|
def model_format(self) -> str: ...
|
|
@@ -7,7 +7,7 @@ import shutil
|
|
|
7
7
|
import subprocess
|
|
8
8
|
import sys
|
|
9
9
|
from collections.abc import Sequence
|
|
10
|
-
from dataclasses import asdict, dataclass
|
|
10
|
+
from dataclasses import asdict, dataclass, replace
|
|
11
11
|
from datetime import datetime
|
|
12
12
|
from typing import Dict, List, Literal, Optional, Union
|
|
13
13
|
|
|
@@ -50,9 +50,11 @@ from .rest import upload as rest_upload
|
|
|
50
50
|
class DetectorNode(Node):
|
|
51
51
|
|
|
52
52
|
def __init__(self, name: str, detector_factory: DetectorLogicFactory,
|
|
53
|
-
uuid: Optional[str] = None, use_backdoor_controls: bool = False
|
|
53
|
+
uuid: Optional[str] = None, use_backdoor_controls: bool = False,
|
|
54
|
+
conf_threshold: Optional[float] = None) -> None:
|
|
54
55
|
super().__init__(name, uuid=uuid, node_type='detector', needs_login=False, needs_sio=False)
|
|
55
56
|
self._detector_factory = detector_factory
|
|
57
|
+
self._conf_threshold = conf_threshold
|
|
56
58
|
self.node_version: Optional[str] = os.environ.get('NODE_VERSION')
|
|
57
59
|
self._detector: _DetectorState = _Initializing()
|
|
58
60
|
self._exclusive_model_build: bool = os.environ.get('EXCLUSIVE_MODEL_BUILD', '0').lower() in ('1', 'true')
|
|
@@ -547,6 +549,10 @@ class DetectorNode(Node):
|
|
|
547
549
|
if model_info is None:
|
|
548
550
|
logging.warning('No model.json found in %s', model_dir)
|
|
549
551
|
raise Exception('model.json not found')
|
|
552
|
+
if self._conf_threshold is not None:
|
|
553
|
+
model_info = replace(model_info, conf_threshold=self._conf_threshold)
|
|
554
|
+
self.log.info('Set confidence threshold for model %s to %s%s', model_info.version,
|
|
555
|
+
model_info.conf_threshold, ' (set on the node)' if self._conf_threshold is not None else '')
|
|
550
556
|
|
|
551
557
|
if self._exclusive_model_build and isinstance(self._detector, _ActiveDetector):
|
|
552
558
|
async with self.detection_lock:
|
|
@@ -12,13 +12,14 @@ from argparse import Action, Namespace
|
|
|
12
12
|
import configargparse
|
|
13
13
|
import uvicorn
|
|
14
14
|
|
|
15
|
+
from ..data_classes.general import DEFAULT_CONF_THRESHOLD
|
|
15
16
|
from ..trainer.batch_size import VRAM_LIMIT_GB_FLAG, VRAM_LIMIT_GB_HELP
|
|
16
17
|
|
|
17
18
|
logger = logging.getLogger(__name__)
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
def node_parser(*, description: str, legacy_env_prefix: str = '',
|
|
21
|
-
vram_limit: bool = False) -> configargparse.ArgumentParser:
|
|
22
|
+
vram_limit: bool = False, conf_threshold: bool = False) -> configargparse.ArgumentParser:
|
|
22
23
|
"""Build the parser for a node, pre-loaded with the settings every node has.
|
|
23
24
|
|
|
24
25
|
:param legacy_env_prefix: A prefix an earlier version of this node required, e.g.
|
|
@@ -26,6 +27,9 @@ def node_parser(*, description: str, legacy_env_prefix: str = '',
|
|
|
26
27
|
current name (``MY_DETECTOR_NODE_HOST``) and the prefix on the flag it was originally
|
|
27
28
|
applied to (``MY_DETECTOR_HOST``). Leave empty for a node that never used one.
|
|
28
29
|
:param vram_limit: Add :data:`~learning_loop_node.trainer.batch_size.VRAM_LIMIT_GB_FLAG`.
|
|
30
|
+
:param conf_threshold: Add ``--conf-threshold`` for the ``conf_threshold`` of a
|
|
31
|
+
:class:`~learning_loop_node.detector.detector_node.DetectorNode`. It has no default:
|
|
32
|
+
one would override the threshold the trainer chose for the model.
|
|
29
33
|
"""
|
|
30
34
|
parser = _NodeArgumentParser(description=description, legacy_env_prefix=legacy_env_prefix)
|
|
31
35
|
parser.add_argument('--host', default='0.0.0.0', env_var='NODE_HOST',
|
|
@@ -33,6 +37,10 @@ def node_parser(*, description: str, legacy_env_prefix: str = '',
|
|
|
33
37
|
parser.add_argument('--port', type=int, default=80, env_var='NODE_PORT', help='Port to bind to')
|
|
34
38
|
if vram_limit:
|
|
35
39
|
parser.add_argument(VRAM_LIMIT_GB_FLAG, type=float, default=0, help=VRAM_LIMIT_GB_HELP)
|
|
40
|
+
if conf_threshold:
|
|
41
|
+
parser.add_argument('--conf-threshold', type=float, default=None,
|
|
42
|
+
help='Minimum confidence for detections (default: the one the trainer chose for the '
|
|
43
|
+
f'model, else {DEFAULT_CONF_THRESHOLD})')
|
|
36
44
|
return parser
|
|
37
45
|
|
|
38
46
|
|
|
@@ -386,6 +386,11 @@ class TrainerLogicGeneric(ABC):
|
|
|
386
386
|
|
|
387
387
|
self.training.image_data = image_data
|
|
388
388
|
self.training.skipped_image_count = skipped_image_count
|
|
389
|
+
for set_name, images in (('train', self.training.train_images()),
|
|
390
|
+
('test', self.training.test_images()),
|
|
391
|
+
('validation', self.training.val_images())):
|
|
392
|
+
if not images:
|
|
393
|
+
raise CriticalError(f'The training has no {set_name} images')
|
|
389
394
|
|
|
390
395
|
async def _download_model(self) -> None:
|
|
391
396
|
"""If training is continued, the model is downloaded from the Learning Loop to the training_folder.
|
|
@@ -493,10 +498,11 @@ class TrainerLogicGeneric(ABC):
|
|
|
493
498
|
raise CriticalError('The model is uploaded, but the loop never answered with its id') from e
|
|
494
499
|
|
|
495
500
|
def _dump_model_json(self) -> str:
|
|
496
|
-
"""
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
501
|
+
"""Dump the categories and the trainer's model metadata to a json file and return its path."""
|
|
502
|
+
content = None
|
|
503
|
+
if self._training:
|
|
504
|
+
content = {'categories': [asdict(c) for c in self._training.categories],
|
|
505
|
+
**{key: value for key, value in self._get_model_metadata().items() if value is not None}}
|
|
500
506
|
json_path = '/tmp/model.json'
|
|
501
507
|
with open(json_path, 'w') as f:
|
|
502
508
|
json.dump(content, f)
|
|
@@ -615,9 +621,11 @@ class TrainerLogicGeneric(ABC):
|
|
|
615
621
|
raise NotImplementedError
|
|
616
622
|
|
|
617
623
|
def _get_model_metadata(self) -> dict[str, Any]:
|
|
618
|
-
"""
|
|
619
|
-
|
|
624
|
+
"""Return the values the trainer determined for this model, to be merged into its model.json.
|
|
625
|
+
|
|
626
|
+
Called when the files of `_get_latest_model_files` are uploaded.
|
|
620
627
|
The keys must be fields of `ModelInformation` (e.g. `conf_threshold`) so detectors can read them.
|
|
628
|
+
A value of `None` is left out of the file.
|
|
621
629
|
"""
|
|
622
630
|
return {}
|
|
623
631
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: learning_loop_node
|
|
3
|
-
Version: 0.27.
|
|
3
|
+
Version: 0.27.2
|
|
4
4
|
Summary: Python Library for Nodes which connect to the Zauberzeug Learning Loop
|
|
5
5
|
Author-email: Zauberzeug GmbH <info@zauberzeug.com>
|
|
6
6
|
License: MIT
|
|
@@ -61,6 +61,7 @@ You can configure connection to our Learning Loop by specifying the following en
|
|
|
61
61
|
| MIN_UNCERTAIN_THRESHOLD | - | smallest confidence (float) at which auto-upload will happen | Detector (opt.) | 0.3 |
|
|
62
62
|
| MAX_UNCERTAIN_THRESHOLD | - | largest confidence (float) at which auto-upload will happen | Detector (opt.) | 0.6 |
|
|
63
63
|
| MAX_OUTBOX_SIZE | - | max. number of non-priority images queued for upload | Detector (opt.) | 1000 |
|
|
64
|
+
| CONF_THRESHOLD | - | minimum confidence of a detection, overriding the model's (`--conf-threshold`, detectors built with `node_parser(conf_threshold=True)`) | Detector (opt.) | model's, 0.2 |
|
|
64
65
|
| EXCLUSIVE_MODEL_BUILD | - | Reject detections during update to save VRAM (set to 1) | Detector (opt.) | 0 |
|
|
65
66
|
| INFERENCE_BATCH_SIZE | - | Batch size of trainer when calculating detections | Trainer (opt.) | 10 |
|
|
66
67
|
| VRAM_LIMIT_GB | - | GPU memory (GB) a training may use; the batch size is probed against it (`--vram-limit-gb`, trainers built with `node_parser(vram_limit=True)`) | Trainer (opt.) | 0 (whole card) |
|
|
@@ -349,4 +350,4 @@ After a short time the converted model should be available as well.
|
|
|
349
350
|
- Nodes add properties to `model.json`, which contains all the information which are needed by subsequent nodes. These are typically the properties:
|
|
350
351
|
- `resolution`: resolution in which the model expects images (as `int`, since the resolution is mostly square - later, `resolution_x`` resolution_y` would also be conceivable or `resolutions` to give a list of possible resolutions)
|
|
351
352
|
- `categories`: list of categories with name, id, (later also type), in the order in which they are used by the model -- this is neccessary to be robust about renamings
|
|
352
|
-
- `conf_threshold` (optional): the minimum confidence the trainer chose for this model's detections
|
|
353
|
+
- `conf_threshold` (optional): the minimum confidence the trainer chose for this model's detections; a detector filters at it unless its `CONF_THRESHOLD` is set, and at 0.2 for a model without one
|
|
@@ -12,14 +12,14 @@ learning_loop_node/data_classes/__init__.py,sha256=_2dxfVkkI9kNM955Y7ZE8RLgfpegZ
|
|
|
12
12
|
learning_loop_node/data_classes/annotation_data.py,sha256=NfMlTv2_5AfVY_JDM4tbjETFjSN2S2I2LJJPMMcDT50,966
|
|
13
13
|
learning_loop_node/data_classes/annotations.py,sha256=ha7uuWmZqW-LU2vYrkvfnaVpvf5C2xdm826IVVBu0w0,1848
|
|
14
14
|
learning_loop_node/data_classes/detections.py,sha256=hGSKc1elk4Drp4XUjQ3F9HGeRpyLvA0q064b-S9lIug,5335
|
|
15
|
-
learning_loop_node/data_classes/general.py,sha256=
|
|
15
|
+
learning_loop_node/data_classes/general.py,sha256=0p6RhPOEVr_vBoG-TySTUKhjT2_AS9Jb8jEqSkg5ncc,7829
|
|
16
16
|
learning_loop_node/data_classes/image_metadata.py,sha256=QPLJ4gcVj33xaaCFF1n5e8G6higOJ6iIKy3YhGZMfkw,2459
|
|
17
17
|
learning_loop_node/data_classes/socket_response.py,sha256=tIdt-oYf6ULoJIDYQCecNM9OtWR6_wJ9tL0Ksu83Vko,655
|
|
18
18
|
learning_loop_node/data_classes/training.py,sha256=OlbPhm5kYHaZqgpz4_DfWnW4DQ_DK91AurReDhvWpRo,5944
|
|
19
19
|
learning_loop_node/detector/__init__.py,sha256=wroy0W1X5MX16C-peA-UHYrcdXet7GhleIlC_Pqa6hI,128
|
|
20
20
|
learning_loop_node/detector/categories.py,sha256=qgquuop3geKXXsNBurO0eC2b64h_ojL37RBYMv0ZzOg,1147
|
|
21
|
-
learning_loop_node/detector/detector_logic.py,sha256=
|
|
22
|
-
learning_loop_node/detector/detector_node.py,sha256=
|
|
21
|
+
learning_loop_node/detector/detector_logic.py,sha256=agPbkFvFuQhZ04qQleWji6iYrbXsKqVGL0fseq4OXXY,1554
|
|
22
|
+
learning_loop_node/detector/detector_node.py,sha256=AB-uXNPGxRbQwjLIN35uyk1Qww6T1bF4fqWSbwV6F1A,37395
|
|
23
23
|
learning_loop_node/detector/exceptions.py,sha256=C6KbNPlSbtfgDrZx2Hbhm7Suk9jVoR3fMRCO0CkrMsQ,196
|
|
24
24
|
learning_loop_node/detector/geometry.py,sha256=bZCF9xuK7qy6Ipv5aQYrTlwSmjaiUz5VEUYnc1zCe-w,1101
|
|
25
25
|
learning_loop_node/detector/outbox.py,sha256=mtMxdcComjRyM6qc-O1fBv9ABCau4-oCCV442hMaryQ,12351
|
|
@@ -42,7 +42,7 @@ learning_loop_node/enums/general.py,sha256=OSpcIpdFjuRByON2xBoiIXl1SgDREIqkwnaxk
|
|
|
42
42
|
learning_loop_node/enums/trainer.py,sha256=VdamD-k6Q8kMm_cwPA8iaEUfZvmTxLfsBHQpEi43Ahk,849
|
|
43
43
|
learning_loop_node/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
44
44
|
learning_loop_node/helpers/background_tasks.py,sha256=sbbS8JO1ls0ZCbNW68KOT9QLOFyFoJRXwwAq78iOmoA,3256
|
|
45
|
-
learning_loop_node/helpers/entrypoint.py,sha256=
|
|
45
|
+
learning_loop_node/helpers/entrypoint.py,sha256=ZPjbH-mM6quvnZ8-L8GxQaINX8D5qlQ-K77URSFcmc0,4540
|
|
46
46
|
learning_loop_node/helpers/environment_reader.py,sha256=zsoFysxNxBCOJzUwX4Pbm3ftYhxx5zsbOHQfD9VcfvA,1937
|
|
47
47
|
learning_loop_node/helpers/gdrive_downloader.py,sha256=zeYJciTAJVRpu_eFjwgYLCpIa6hU1d71anqEBb564Rk,1145
|
|
48
48
|
learning_loop_node/helpers/log_conf.py,sha256=hqVAa_9NnYEU6N0dcOKmph82p7MpgKqeF_eomTLYzWY,961
|
|
@@ -59,13 +59,13 @@ learning_loop_node/trainer/metrics.py,sha256=H1jQh5fHN3-cTdMTuH8mF_uODV0T9DBdqcP
|
|
|
59
59
|
learning_loop_node/trainer/subprocess.py,sha256=WxuTly0zhpClEjTd9wqoI_-7YTNMaiZpRHxu00vttKw,2647
|
|
60
60
|
learning_loop_node/trainer/test_executor.py,sha256=6BVGDN_6f5GEMMEvDLSG1yzMybSvgXaP5uYpSfsVPP0,2224
|
|
61
61
|
learning_loop_node/trainer/trainer_logic.py,sha256=x15bEy7uTfaGGHGYRGUl2olMeL0x0QW0vlu8nXZOaGg,9769
|
|
62
|
-
learning_loop_node/trainer/trainer_logic_generic.py,sha256=
|
|
62
|
+
learning_loop_node/trainer/trainer_logic_generic.py,sha256=3OcI16ijZ4q0QSKZEKah7uhoEydjxy5vKNDm-xBCuk8,31611
|
|
63
63
|
learning_loop_node/trainer/trainer_node.py,sha256=GU4LyFA-JC9s4d-PbyKhKWyGMMIzTMqKJFNxM6tDJE4,5316
|
|
64
64
|
learning_loop_node/trainer/training_slot.py,sha256=Njq6Z0T2Lqlr24ftoIp11Pw2OxCo9Z2RGn1tuFhHqLY,6737
|
|
65
65
|
learning_loop_node/trainer/validation_set.py,sha256=3gyqw88jadYRYDTUZVsaE0o5LGShmqaJykjr0sdNLJo,2907
|
|
66
66
|
learning_loop_node/trainer/rest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
67
67
|
learning_loop_node/trainer/rest/backdoor_controls.py,sha256=bCjrQvqtgd2DtyYNgftkcDe9AFHExFLtfdW_jISLwuk,5026
|
|
68
|
-
learning_loop_node-0.27.
|
|
69
|
-
learning_loop_node-0.27.
|
|
70
|
-
learning_loop_node-0.27.
|
|
71
|
-
learning_loop_node-0.27.
|
|
68
|
+
learning_loop_node-0.27.2.dist-info/METADATA,sha256=KZ4fva5K1YHTwNrGoQKVz4ooUGgNLuISoetBOE1g2_M,20629
|
|
69
|
+
learning_loop_node-0.27.2.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
70
|
+
learning_loop_node-0.27.2.dist-info/top_level.txt,sha256=nS5qiFc4stVF_VXVCVJxZnrqVOv3IGX5-_E3sLby6OE,19
|
|
71
|
+
learning_loop_node-0.27.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|