learning-loop-node 0.27.1__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.
@@ -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: Optional[float] = field(default=None, metadata={
63
- "description": "The minimum confidence the trainer chose for the detections of this model, if it chose one."})
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) -> None:
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
 
@@ -498,10 +498,11 @@ class TrainerLogicGeneric(ABC):
498
498
  raise CriticalError('The model is uploaded, but the loop never answered with its id') from e
499
499
 
500
500
  def _dump_model_json(self) -> str:
501
- """Dumps the categories and the trainer's model metadata to a json file and returns the path to the file.
502
- """
503
- content = {'categories': [asdict(c) for c in self._training.categories],
504
- **self._get_model_metadata()} if self._training else None
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}}
505
506
  json_path = '/tmp/model.json'
506
507
  with open(json_path, 'w') as f:
507
508
  json.dump(content, f)
@@ -620,9 +621,11 @@ class TrainerLogicGeneric(ABC):
620
621
  raise NotImplementedError
621
622
 
622
623
  def _get_model_metadata(self) -> dict[str, Any]:
623
- """Called when the files of `_get_latest_model_files` are uploaded.
624
- Returns values the trainer determined for this model, merged into its model.json.
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.
625
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.
626
629
  """
627
630
  return {}
628
631
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: learning_loop_node
3
- Version: 0.27.1
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=LTRtA_sPUKbXkNrka01_o8qwRN6r56tQJCSHPNdg_MI,7522
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=XJaHNiw5PaHoCWnJ-qsxdUELLevvztYx54TQ8KcHl18,1479
22
- learning_loop_node/detector/detector_node.py,sha256=3e6UU1fl6IVkEgcfJTiOE-W30yebXcd5IpPOBYWV2mM,36950
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=U4vHFqgke6mXjDL-E4MND8RO7P1k2VBGwdDOOoJvD9Y,3915
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=mDTcGXXHqoXkqJNsYU8yNixBxIq9piBqHDvE9KlTC8U,31483
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.1.dist-info/METADATA,sha256=bv0ofwadIpn6gVRH8K6zFuPh64IokrH-Cxi-UvcePwE,20317
69
- learning_loop_node-0.27.1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
70
- learning_loop_node-0.27.1.dist-info/top_level.txt,sha256=nS5qiFc4stVF_VXVCVJxZnrqVOv3IGX5-_E3sLby6OE,19
71
- learning_loop_node-0.27.1.dist-info/RECORD,,
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,,