learning-loop-node 0.10.13__tar.gz → 0.10.15rc1__tar.gz

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.

Files changed (99) hide show
  1. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/PKG-INFO +1 -1
  2. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/annotation/annotator_node.py +11 -10
  3. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/data_classes/detections.py +34 -26
  4. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/data_classes/general.py +27 -17
  5. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/data_exchanger.py +6 -5
  6. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/detector/detector_logic.py +3 -3
  7. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/detector/detector_node.py +21 -15
  8. learning_loop_node-0.10.15rc1/learning_loop_node/detector/rest/about.py +50 -0
  9. learning_loop_node-0.10.15rc1/learning_loop_node/detector/rest/backdoor_controls.py +37 -0
  10. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/detector/rest/detect.py +17 -16
  11. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/detector/rest/model_version_control.py +30 -13
  12. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/detector/rest/operation_mode.py +11 -5
  13. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/detector/rest/outbox_mode.py +7 -1
  14. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/loop_communication.py +7 -4
  15. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/node.py +100 -47
  16. learning_loop_node-0.10.15rc1/learning_loop_node/rest.py +55 -0
  17. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/detector/conftest.py +4 -4
  18. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/detector/test_client_communication.py +21 -19
  19. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/detector/test_detector_node.py +3 -3
  20. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/trainer/conftest.py +4 -4
  21. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/trainer/states/test_state_detecting.py +8 -9
  22. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/trainer/states/test_state_download_train_model.py +8 -8
  23. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/trainer/states/test_state_prepare.py +6 -7
  24. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/trainer/states/test_state_sync_confusion_matrix.py +21 -18
  25. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/trainer/states/test_state_train.py +6 -8
  26. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/trainer/states/test_state_upload_detections.py +7 -9
  27. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/trainer/states/test_state_upload_model.py +7 -8
  28. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/trainer/test_errors.py +2 -2
  29. learning_loop_node-0.10.15rc1/learning_loop_node/trainer/exceptions.py +12 -0
  30. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/trainer/rest/backdoor_controls.py +19 -40
  31. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/trainer/trainer_logic_generic.py +11 -5
  32. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/trainer/trainer_node.py +4 -3
  33. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/pyproject.toml +1 -1
  34. learning_loop_node-0.10.13/learning_loop_node/detector/rest/about.py +0 -26
  35. learning_loop_node-0.10.13/learning_loop_node/detector/rest/backdoor_controls.py +0 -54
  36. learning_loop_node-0.10.13/learning_loop_node/rest.py +0 -32
  37. learning_loop_node-0.10.13/learning_loop_node/trainer/exceptions.py +0 -2
  38. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/README.md +0 -0
  39. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/__init__.py +0 -0
  40. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/annotation/__init__.py +0 -0
  41. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/annotation/annotator_logic.py +0 -0
  42. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/data_classes/__init__.py +0 -0
  43. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/data_classes/annotations.py +0 -0
  44. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/data_classes/socket_response.py +0 -0
  45. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/data_classes/training.py +0 -0
  46. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/detector/__init__.py +0 -0
  47. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/detector/inbox_filter/__init__.py +0 -0
  48. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/detector/inbox_filter/cam_observation_history.py +0 -0
  49. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/detector/inbox_filter/relevance_filter.py +0 -0
  50. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/detector/outbox.py +0 -0
  51. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/detector/rest/__init__.py +0 -0
  52. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/detector/rest/upload.py +0 -0
  53. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/examples/novelty_score_updater.py +0 -0
  54. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/globals.py +0 -0
  55. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/helpers/__init__.py +0 -0
  56. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/helpers/environment_reader.py +0 -0
  57. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/helpers/gdrive_downloader.py +0 -0
  58. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/helpers/log_conf.py +0 -0
  59. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/helpers/misc.py +0 -0
  60. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/py.typed +0 -0
  61. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/__init__.py +0 -0
  62. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/annotator/__init__.py +0 -0
  63. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/annotator/conftest.py +0 -0
  64. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/annotator/pytest.ini +0 -0
  65. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/annotator/test_annotator_node.py +0 -0
  66. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/detector/__init__.py +0 -0
  67. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/detector/inbox_filter/__init__.py +0 -0
  68. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/detector/inbox_filter/test_observation.py +0 -0
  69. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/detector/inbox_filter/test_relevance_group.py +0 -0
  70. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/detector/inbox_filter/test_unexpected_observations_count.py +0 -0
  71. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/detector/pytest.ini +0 -0
  72. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/detector/test.jpg +0 -0
  73. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/detector/test_outbox.py +0 -0
  74. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/detector/test_relevance_filter.py +0 -0
  75. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/detector/testing_detector.py +0 -0
  76. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/general/__init__.py +0 -0
  77. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/general/conftest.py +0 -0
  78. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/general/pytest.ini +0 -0
  79. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/general/test_data/file_1.txt +0 -0
  80. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/general/test_data/file_2.txt +0 -0
  81. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/general/test_data/model.json +0 -0
  82. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/general/test_data_classes.py +0 -0
  83. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/general/test_downloader.py +0 -0
  84. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/general/test_learning_loop_node.py +0 -0
  85. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/test_helper.py +0 -0
  86. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/trainer/__init__.py +0 -0
  87. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/trainer/pytest.ini +0 -0
  88. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/trainer/state_helper.py +0 -0
  89. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/trainer/states/__init__.py +0 -0
  90. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/trainer/states/test_state_cleanup.py +0 -0
  91. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/trainer/test_trainer_states.py +0 -0
  92. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/tests/trainer/testing_trainer_logic.py +0 -0
  93. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/trainer/__init__.py +0 -0
  94. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/trainer/downloader.py +0 -0
  95. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/trainer/executor.py +0 -0
  96. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/trainer/io_helpers.py +0 -0
  97. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/trainer/rest/__init__.py +0 -0
  98. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/trainer/test_executor.py +0 -0
  99. {learning_loop_node-0.10.13 → learning_loop_node-0.10.15rc1}/learning_loop_node/trainer/trainer_logic.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: learning-loop-node
3
- Version: 0.10.13
3
+ Version: 0.10.15rc1
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
@@ -22,7 +22,6 @@ class AnnotatorNode(Node):
22
22
  self.tool = annotator_logic
23
23
  self.histories: Dict = {}
24
24
  annotator_logic.init(self)
25
- self.status_sent = False
26
25
 
27
26
  def register_sio_events(self, sio_client: AsyncClient):
28
27
 
@@ -66,8 +65,6 @@ class AnnotatorNode(Node):
66
65
  return self.histories.setdefault(frontend_id, self.tool.create_empty_history())
67
66
 
68
67
  async def send_status(self):
69
- if self.status_sent:
70
- return
71
68
 
72
69
  status = AnnotationNodeStatus(
73
70
  id=self.uuid,
@@ -76,20 +73,24 @@ class AnnotatorNode(Node):
76
73
  capabilities=['segmentation']
77
74
  )
78
75
 
79
- self.log.info(f'Sending status {status}')
76
+ self.log.debug('Sending status %s', status)
80
77
  try:
81
78
  result = await self.sio_client.call('update_annotation_node', jsonable_encoder(asdict(status)), timeout=10)
82
- except Exception as e:
83
- self.log.error(f'Error for updating: {str(e)}')
79
+ except Exception:
80
+ self.socket_connection_broken = True
81
+ self.log.exception('Error for updating:')
82
+ return
83
+
84
+ if not isinstance(result, Dict):
85
+ self.socket_connection_broken = True
86
+ self.log.error('Expected Dict, got %s', type(result))
84
87
  return
85
88
 
86
- assert isinstance(result, Dict)
87
89
  response = from_dict(data_class=SocketResponse, data=result)
88
90
 
89
91
  if not response.success:
90
- self.log.error(f'Error for updating: Response from loop was : {asdict(response)}')
91
- else:
92
- self.status_sent = True
92
+ self.socket_connection_broken = True
93
+ self.log.error('Response from loop was: %s', asdict(response))
93
94
 
94
95
  async def download_image(self, context: Context, uuid: str):
95
96
  project_folder = create_project_folder(context)
@@ -16,14 +16,14 @@ class BoxDetection():
16
16
  """Coordinates according to COCO format. x,y is the top left corner of the box.
17
17
  x increases to the right, y increases downwards.
18
18
  """
19
- category_name: str
20
- x: int
21
- y: int
22
- width: int
23
- height: int
24
- model_name: str
25
- confidence: float
26
- category_id: Optional[str] = None
19
+ category_name: str = field(metadata={'description': 'Category name'})
20
+ x: int = field(metadata={'description': 'X coordinate (left to right)'})
21
+ y: int = field(metadata={'description': 'Y coordinate (top to bottom)'})
22
+ width: int = field(metadata={'description': 'Width'})
23
+ height: int = field(metadata={'description': 'Height'})
24
+ model_name: str = field(metadata={'description': 'Model name'})
25
+ confidence: float = field(metadata={'description': 'Confidence'})
26
+ category_id: Optional[str] = field(default=None, metadata={'description': 'Category ID'})
27
27
 
28
28
  def intersection_over_union(self, other_detection: 'BoxDetection') -> float:
29
29
  # https://www.pyimagesearch.com/2016/11/07/intersection-over-union-iou-for-object-detection/
@@ -52,12 +52,12 @@ class BoxDetection():
52
52
  class PointDetection():
53
53
  """Coordinates according to COCO format. x,y is the center of the point.
54
54
  x increases to the right, y increases downwards."""
55
- category_name: str
56
- x: float
57
- y: float
58
- model_name: str
59
- confidence: float
60
- category_id: Optional[str] = None
55
+ category_name: str = field(metadata={'description': 'Category name'})
56
+ x: float = field(metadata={'description': 'X coordinate (right)'})
57
+ y: float = field(metadata={'description': 'Y coordinate (down)'})
58
+ model_name: str = field(metadata={'description': 'Model name'})
59
+ confidence: float = field(metadata={'description': 'Confidence'})
60
+ category_id: Optional[str] = field(default=None, metadata={'description': 'Category ID'})
61
61
 
62
62
  def distance(self, other: 'PointDetection') -> float:
63
63
  return np.sqrt((other.x - self.x)**2 + (other.y - self.y)**2)
@@ -68,10 +68,10 @@ class PointDetection():
68
68
 
69
69
  @dataclass(**KWONLY_SLOTS)
70
70
  class ClassificationDetection():
71
- category_name: str
72
- model_name: str
73
- confidence: float
74
- category_id: Optional[str] = None
71
+ category_name: str = field(metadata={'description': 'Category name'})
72
+ model_name: str = field(metadata={'description': 'Model name'})
73
+ confidence: float = field(metadata={'description': 'Confidence'})
74
+ category_id: Optional[str] = field(default=None, metadata={'description': 'Category ID'})
75
75
 
76
76
  def __str__(self):
77
77
  return f'c: {self.confidence:.2f} -> {self.category_name}'
@@ -112,14 +112,22 @@ def current_datetime():
112
112
 
113
113
  @dataclass(**KWONLY_SLOTS)
114
114
  class Detections():
115
- box_detections: List[BoxDetection] = field(default_factory=list)
116
- point_detections: List[PointDetection] = field(default_factory=list)
117
- segmentation_detections: List[SegmentationDetection] = field(default_factory=list)
118
- classification_detections: List[ClassificationDetection] = field(default_factory=list)
119
- tags: List[str] = field(default_factory=list)
120
- date: Optional[str] = field(default_factory=current_datetime)
121
- image_id: Optional[str] = None # (actually UUID) used for detection of trainers
122
- source: Optional[str] = None
115
+ box_detections: List[BoxDetection] = field(default_factory=list, metadata={
116
+ 'description': 'List of box detections'})
117
+ point_detections: List[PointDetection] = field(default_factory=list, metadata={
118
+ 'description': 'List of point detections'})
119
+ segmentation_detections: List[SegmentationDetection] = field(default_factory=list, metadata={
120
+ 'description': 'List of segmentation detections'})
121
+ classification_detections: List[ClassificationDetection] = field(default_factory=list, metadata={
122
+ 'description': 'List of classification detections'})
123
+ tags: List[str] = field(default_factory=list, metadata={
124
+ 'description': 'List of tags'})
125
+ date: Optional[str] = field(default_factory=current_datetime, metadata={
126
+ 'description': 'Date of the detections'})
127
+ image_id: Optional[str] = field(default=None, metadata={
128
+ 'description': 'Image uuid'})
129
+ source: Optional[str] = field(default=None, metadata={
130
+ 'description': 'Source of the detections'})
123
131
 
124
132
  def __len__(self):
125
133
  return len(self.box_detections) + len(self.point_detections) + len(self.segmentation_detections) + len(self.classification_detections)
@@ -20,14 +20,19 @@ class CategoryType(str, Enum):
20
20
 
21
21
  @dataclass(**KWONLY_SLOTS)
22
22
  class Category():
23
- id: str # TODO: rename to identifier or uuid (cannot be changed because of database / loop communication)
24
- name: str
25
- description: Optional[str] = None
26
- hotkey: Optional[str] = None
27
- color: Optional[str] = None
28
- point_size: Optional[int] = None
29
- # TODO: rename to ctype (cannot be changed because of database / loop communication)
30
- type: Optional[Union[CategoryType, str]] = None
23
+ id: str = field(metadata={"description": "The uuid of the category."})
24
+ name: str = field(metadata={"description": "The name of the category."})
25
+ description: Optional[str] = field(default=None, metadata={
26
+ "description": "An optional description of the category."})
27
+ hotkey: Optional[str] = field(default=None, metadata={
28
+ "description": "The key shortcut of the category when annotating in the Learning Loop UI."})
29
+ color: Optional[str] = field(default=None, metadata={
30
+ "description": "The color of the category when displayed in the Learning Loop UI."})
31
+ point_size: Optional[int] = field(default=None, metadata={
32
+ "description": "The point size of the category in pixels. Represents the uncertainty of the category."})
33
+ type: Optional[Union[CategoryType, str]] = field(default=None, metadata={
34
+ "description": "The type of the category",
35
+ "example": "box, point, segmentation, classification"})
31
36
 
32
37
  @staticmethod
33
38
  def from_list(values: List[dict]) -> List['Category']:
@@ -45,15 +50,20 @@ class Context():
45
50
 
46
51
  @dataclass(**KWONLY_SLOTS)
47
52
  class ModelInformation():
48
- id: str
49
- host: Optional[str]
50
- organization: str
51
- project: str
52
- version: str
53
- categories: List[Category] = field(default_factory=list)
54
- resolution: Optional[int] = None
55
- model_root_path: Optional[str] = None
56
- model_size: Optional[str] = None
53
+ id: str = field(metadata={"description": "The uuid of the model."})
54
+ host: Optional[str] = field(metadata={"description": "The hostname that started the training.",
55
+ "example": "learning-loop.ai"})
56
+ organization: str = field(metadata={"description": "The owner organization of the model."})
57
+ project: str = field(metadata={"description": "The project of the model."})
58
+ version: str = field(metadata={"description": "The version of the model."})
59
+ categories: List[Category] = field(default_factory=list, metadata={
60
+ "description": "The categories used in the model."})
61
+ resolution: Optional[int] = field(default=None, metadata={
62
+ "description": "The resolution of the model (width and height of the image after preprocessing in pixels)."})
63
+ model_root_path: Optional[str] = field(
64
+ default=None, metadata={"description": "The path of the parent directory of the model in the file system."})
65
+ model_size: Optional[str] = field(default=None, metadata={
66
+ "description": "The size of the model (i.e. the specification or variant of the model architecture)."})
57
67
 
58
68
  @property
59
69
  def context(self):
@@ -140,20 +140,21 @@ class DataExchanger():
140
140
  """Downloads a model (and additional meta data like model.json) and returns the paths of the downloaded files.
141
141
  Used before training a model (when continuing a finished training) or before detecting images.
142
142
  """
143
- logging.info(f'Downloading model data for uuid {model_uuid} from the loop to {target_folder}..')
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
146
  response = await self.loop_communicator.get(path, requires_login=False)
147
147
  if response.status_code != 200:
148
- content = response.json()
149
- logging.error(f'could not download loop/{path}: {response.status_code}, content: {content}')
150
- raise DownloadError(content['detail'])
148
+ decoded_content = response.content.decode('utf-8')
149
+ logging.error('could not download loop/%s: %s, content: %s', path,
150
+ response.status_code, decoded_content)
151
+ raise DownloadError(decoded_content)
151
152
  try:
152
153
  provided_filename = response.headers.get(
153
154
  "Content-Disposition").split("filename=")[1].strip('"')
154
155
  content = response.content
155
156
  except:
156
- logging.exception(f'Error during downloading model {path}:')
157
+ logging.exception('Error while downloading model %s:', path)
157
158
  raise
158
159
 
159
160
  tmp_path = f'/tmp/{os.path.splitext(provided_filename)[0]}'
@@ -28,7 +28,7 @@ class DetectorLogic():
28
28
  return self._model_info is not None
29
29
 
30
30
  def load_model(self):
31
- logging.info(f'Loading model from {GLOBALS.data_folder}/model')
31
+ logging.info('Loading model from %s', GLOBALS.data_folder)
32
32
  model_info = ModelInformation.load_from_disk(f'{GLOBALS.data_folder}/model')
33
33
  if model_info is None:
34
34
  logging.warning('No model found')
@@ -37,9 +37,9 @@ class DetectorLogic():
37
37
  try:
38
38
  self._model_info = model_info
39
39
  self.init()
40
- logging.info(f'Successfully loaded model {self._model_info}')
40
+ logging.info('Successfully loaded model %s', self._model_info)
41
41
  except Exception:
42
- logging.error(f'Could not init model {model_info}')
42
+ logging.error('Could not init model %s', model_info)
43
43
  raise
44
44
 
45
45
  @abstractmethod
@@ -93,7 +93,8 @@ class DetectorNode(Node):
93
93
  # simulate super().startup
94
94
  await self.loop_communicator.backend_ready()
95
95
  # await self.loop_communicator.ensure_login()
96
- await self.create_sio_client()
96
+ self.set_skip_repeat_loop(False)
97
+ self.socket_connection_broken = True
97
98
  await self.on_startup()
98
99
 
99
100
  # simulate startup
@@ -151,8 +152,9 @@ class DetectorNode(Node):
151
152
  )
152
153
  if det is None:
153
154
  return {'error': 'no model loaded'}
155
+ detection_dict = jsonable_encoder(asdict(det))
154
156
  self.log.debug('detect via socketio finished')
155
- return det
157
+ return detection_dict
156
158
  except Exception as e:
157
159
  self.log.exception('could not detect via socketio')
158
160
  with open('/tmp/bad_img_from_socket_io.jpg', 'wb') as f:
@@ -198,23 +200,21 @@ class DetectorNode(Node):
198
200
  self.connected_clients.append(sid)
199
201
 
200
202
  async def _check_for_update(self) -> None:
201
- if self.operation_mode == OperationMode.Startup:
202
- return
203
203
  try:
204
- self.log.info('Current operation mode is %s', self.operation_mode)
204
+ self.log.debug('Current operation mode is %s', self.operation_mode)
205
205
  try:
206
206
  await self.sync_status_with_learning_loop()
207
- except Exception as e:
208
- self.log.error('Could not check for updates: %s', e)
207
+ except Exception:
208
+ self.log.exception('Sync with learning loop failed (could not check for updates):')
209
209
  return
210
210
 
211
211
  if self.operation_mode != OperationMode.Idle:
212
- self.log.info('not checking for updates; operation mode is %s', self.operation_mode)
212
+ self.log.debug('not checking for updates; operation mode is %s', self.operation_mode)
213
213
  return
214
214
 
215
215
  self.status.reset_error('update_model')
216
216
  if self.target_model is None:
217
- self.log.info('not checking for updates; no target model selected')
217
+ self.log.debug('not checking for updates; no target model selected')
218
218
  return
219
219
 
220
220
  current_version = self.detector_logic._model_info.version if self.detector_logic._model_info is not None else None # pylint: disable=protected-access
@@ -290,12 +290,15 @@ class DetectorNode(Node):
290
290
  model_format=self.detector_logic.model_format,
291
291
  )
292
292
 
293
- self.log.info('sending status %s', status)
293
+ self.log.debug('sending status %s', status)
294
294
  response = await self.sio_client.call('update_detector', (self.organization, self.project, jsonable_encoder(asdict(status))))
295
+ if not response:
296
+ self.socket_connection_broken = True
297
+ return
295
298
 
296
- assert response is not None
297
299
  socket_response = from_dict(data_class=SocketResponse, data=response)
298
300
  if not socket_response.success:
301
+ self.socket_connection_broken = True
299
302
  self.log.error('Statusupdate failed: %s', response)
300
303
  raise Exception(f'Statusupdate failed: {response}')
301
304
 
@@ -308,9 +311,12 @@ class DetectorNode(Node):
308
311
  id=deployment_target_model_id,
309
312
  version=deployment_target_model_version)
310
313
 
311
- if self.version_control == rest_version_control.VersionMode.FollowLoop:
314
+ if (self.version_control == rest_version_control.VersionMode.FollowLoop and
315
+ self.target_model != self.loop_deployment_target):
316
+ old_target_model_version = self.target_model.version if self.target_model else None
312
317
  self.target_model = self.loop_deployment_target
313
- self.log.info('After sending status. Target_model is %s', self.target_model.version)
318
+ self.log.info('After sending status. Target_model changed from %s to %s',
319
+ old_target_model_version, self.target_model.version)
314
320
 
315
321
  async def set_operation_mode(self, mode: OperationMode):
316
322
  self.operation_mode = mode
@@ -337,7 +343,7 @@ class DetectorNode(Node):
337
343
  camera_id: Optional[str],
338
344
  tags: List[str],
339
345
  source: Optional[str] = None,
340
- autoupload: Optional[str] = None) -> Optional[Dict]:
346
+ autoupload: Optional[str] = None) -> Detections:
341
347
  """ Main processing function for the detector node when an image is received via REST or SocketIO.
342
348
  This function infers the detections from the image, cares about uploading to the loop and returns the detections as a dictionary.
343
349
  Note: raw_image is a numpy array of type uint8, but not in the correct shape!
@@ -362,7 +368,7 @@ class DetectorNode(Node):
362
368
  pass
363
369
  else:
364
370
  self.log.error('unknown autoupload value %s', autoupload)
365
- return jsonable_encoder(asdict(detections))
371
+ return detections
366
372
 
367
373
  async def upload_images(self, images: List[bytes]):
368
374
  loop = asyncio.get_event_loop()
@@ -0,0 +1,50 @@
1
+
2
+ import sys
3
+ from dataclasses import dataclass, field
4
+ from typing import TYPE_CHECKING, Optional
5
+
6
+ from fastapi import APIRouter, Request
7
+
8
+ from ...data_classes import ModelInformation
9
+
10
+ if TYPE_CHECKING:
11
+ from ..detector_node import DetectorNode
12
+ KWONLY_SLOTS = {'kw_only': True, 'slots': True} if sys.version_info >= (3, 10) else {}
13
+
14
+ router = APIRouter()
15
+
16
+
17
+ @dataclass(**KWONLY_SLOTS)
18
+ class AboutResponse:
19
+ operation_mode: str = field(metadata={"description": "The operation mode of the detector node"})
20
+ state: Optional[str] = field(metadata={
21
+ "description": "The state of the detector node",
22
+ "example": "idle, online, detecting"})
23
+ model_info: Optional[ModelInformation] = field(metadata={
24
+ "description": "Information about the model of the detector node"})
25
+ target_model: Optional[str] = field(metadata={"description": "The target model of the detector node"})
26
+ version_control: str = field(metadata={
27
+ "description": "The version control mode of the detector node",
28
+ "example": "follow_loop, specific_version, pause"})
29
+
30
+
31
+ @router.get("/about", response_model=AboutResponse)
32
+ async def get_about(request: Request):
33
+ '''
34
+ Get information about the detector node.
35
+
36
+ Example Usage
37
+
38
+ curl http://hosturl/about
39
+ '''
40
+ app: 'DetectorNode' = request.app
41
+
42
+ response = AboutResponse(
43
+ operation_mode=app.operation_mode.value,
44
+ state=app.status.state,
45
+ model_info=app.detector_logic._model_info, # pylint: disable=protected-access
46
+ target_model=app.target_model.version if app.target_model is not None else None,
47
+ version_control=app.version_control.value
48
+ )
49
+
50
+ return response
@@ -0,0 +1,37 @@
1
+ """These restful endpoints are only to be used for testing purposes and are not part of the 'offical' trainer behavior."""
2
+ import logging
3
+ import os
4
+ import shutil
5
+ from typing import TYPE_CHECKING
6
+
7
+ from fastapi import APIRouter, Request
8
+
9
+ from ...globals import GLOBALS
10
+
11
+ if TYPE_CHECKING:
12
+ from ..detector_node import DetectorNode
13
+
14
+ router = APIRouter()
15
+
16
+
17
+ @router.post("/reset")
18
+ async def _reset(request: Request):
19
+ '''
20
+ Soft-Reset the detector node.
21
+
22
+ Example Usage
23
+
24
+ curl -X POST http://hosturl/reset
25
+ '''
26
+ logging.info('BC: reset')
27
+ detector_node: 'DetectorNode' = request.app
28
+
29
+ try:
30
+ shutil.rmtree(GLOBALS.data_folder, ignore_errors=True)
31
+ os.makedirs(GLOBALS.data_folder, exist_ok=True)
32
+
33
+ await detector_node.soft_reload()
34
+ except Exception:
35
+ logging.exception('BC: could not reset:')
36
+ return False
37
+ return True
@@ -5,38 +5,39 @@ import numpy as np
5
5
  from fastapi import APIRouter, File, Header, Request, UploadFile
6
6
  from fastapi.responses import JSONResponse
7
7
 
8
+ from ...data_classes.detections import Detections
9
+
8
10
  if TYPE_CHECKING:
9
11
  from ..detector_node import DetectorNode
10
12
 
11
-
12
13
  router = APIRouter()
13
14
 
14
15
 
15
- @router.post("/detect")
16
+ @router.post("/detect", response_model=Detections)
16
17
  async def http_detect(
17
18
  request: Request,
18
- file: UploadFile = File(...),
19
- camera_id: Optional[str] = Header(None),
20
- mac: Optional[str] = Header(None),
21
- tags: Optional[str] = Header(None),
22
- source: Optional[str] = Header(None),
23
- autoupload: Optional[str] = Header(None),
19
+ file: UploadFile = File(..., description='The image file to run detection on'),
20
+ camera_id: Optional[str] = Header(None, description='The camera id (used by learning loop)'),
21
+ mac: Optional[str] = Header(None, description='The camera mac address (used by learning loop)'),
22
+ tags: Optional[str] = Header(None, description='Tags to add to the image (used by learning loop)'),
23
+ source: Optional[str] = Header(None, description='The source of the image (used by learning loop)'),
24
+ autoupload: Optional[str] = Header(None, description='Mode to decide whether to upload the image to the learning loop',
25
+ examples=['filtered', 'all', 'disabled']),
24
26
  ):
25
27
  """
26
- Example Usage
28
+ Single image example:
29
+
30
+ curl --request POST -F 'file=@test.jpg' localhost:8004/detect -H 'autoupload: all' -H 'camera-id: front_cam' -H 'source: test' -H 'tags: test,test2'
27
31
 
28
- curl --request POST -F 'file=@test.jpg' localhost:8004/detect
32
+ Multiple images example:
29
33
 
30
34
  for i in `seq 1 10`; do time curl --request POST -F 'file=@test.jpg' localhost:8004/detect; done
31
35
 
32
- You can additionally provide the following camera parameters:
33
- - `autoupload`: configures auto-submission to the learning loop; `filtered` (default), `all`, `disabled` (example curl parameter `-H 'autoupload: all'`)
34
- - `camera-id`: a string which groups images for submission together (example curl parameter `-H 'camera-id: front_cam'`)
35
36
  """
36
37
  try:
37
38
  np_image = np.fromfile(file.file, np.uint8)
38
39
  except Exception as exc:
39
- logging.exception(f'Error during reading of image {file.filename}.')
40
+ logging.exception('Error during reading of image %s.', file.filename)
40
41
  raise Exception(f'Uploaded file {file.filename} is no image file.') from exc
41
42
 
42
43
  try:
@@ -47,6 +48,6 @@ async def http_detect(
47
48
  source=source,
48
49
  autoupload=autoupload)
49
50
  except Exception as exc:
50
- logging.exception(f'Error during detection of image {file.filename}.')
51
+ logging.exception('Error during detection of image %s.', file.filename)
51
52
  raise Exception(f'Error during detection of image {file.filename}.') from exc
52
- return JSONResponse(detections)
53
+ return detections
@@ -1,7 +1,9 @@
1
1
 
2
2
  import os
3
+ import sys
4
+ from dataclasses import dataclass, field
3
5
  from enum import Enum
4
- from typing import TYPE_CHECKING
6
+ from typing import TYPE_CHECKING, List
5
7
 
6
8
  from fastapi import APIRouter, HTTPException, Request
7
9
 
@@ -10,6 +12,7 @@ from ...globals import GLOBALS
10
12
 
11
13
  if TYPE_CHECKING:
12
14
  from ..detector_node import DetectorNode
15
+ KWONLY_SLOTS = {'kw_only': True, 'slots': True} if sys.version_info >= (3, 10) else {}
13
16
 
14
17
  router = APIRouter()
15
18
 
@@ -20,9 +23,20 @@ class VersionMode(str, Enum):
20
23
  Pause = 'pause' # will pause the updates
21
24
 
22
25
 
26
+ @dataclass(**KWONLY_SLOTS)
27
+ class ModelVersionResponse:
28
+ current_version: str = field(metadata={"description": "The version of the model currently used by the detector."})
29
+ target_version: str = field(metadata={"description": "The target model version set in the detector."})
30
+ loop_version: str = field(metadata={"description": "The target model version specified by the loop."})
31
+ local_versions: List[str] = field(metadata={"description": "The locally available versions of the model."})
32
+ version_control: str = field(metadata={"description": "The version control mode."})
33
+
34
+
23
35
  @router.get("/model_version")
24
36
  async def get_version(request: Request):
25
37
  '''
38
+ Get information about the model version control and the current model version.
39
+
26
40
  Example Usage
27
41
  curl http://localhost/model_version
28
42
  '''
@@ -35,28 +49,31 @@ async def get_version(request: Request):
35
49
  loop_version = app.loop_deployment_target.version if app.loop_deployment_target is not None else 'None'
36
50
 
37
51
  local_versions: list[str] = []
38
-
39
- local_models = os.listdir(os.path.join(GLOBALS.data_folder, 'models'))
52
+ models_path = os.path.join(GLOBALS.data_folder, 'models')
53
+ local_models = os.listdir(models_path) if os.path.exists(models_path) else []
40
54
  for model in local_models:
41
55
  if model.replace('.', '').isdigit():
42
56
  local_versions.append(model)
43
57
 
44
- return {
45
- 'current_version': current_version,
46
- 'target_version': target_version,
47
- 'loop_version': loop_version,
48
- 'local_versions': local_versions,
49
- 'version_control': app.version_control.value,
50
- }
58
+ response = ModelVersionResponse(
59
+ current_version=current_version,
60
+ target_version=target_version,
61
+ loop_version=loop_version,
62
+ local_versions=local_versions,
63
+ version_control=app.version_control.value,
64
+ )
65
+ return response
51
66
 
52
67
 
53
68
  @router.put("/model_version")
54
69
  async def put_version(request: Request):
55
70
  '''
71
+ Set the model version control mode.
72
+
56
73
  Example Usage
57
- curl -X PUT -d "follow_loop" http://localhost/model_version
58
- curl -X PUT -d "pause" http://localhost/model_version
59
- curl -X PUT -d "13.6" http://localhost/model_version
74
+ curl -X PUT -d "follow_loop" http://hosturl/model_version
75
+ curl -X PUT -d "pause" http://hosturl/model_version
76
+ curl -X PUT -d "13.6" http://hosturl/model_version
60
77
  '''
61
78
  app: 'DetectorNode' = request.app
62
79
  content = str(await request.body(), 'utf-8')
@@ -22,7 +22,10 @@ class OperationMode(str, Enum):
22
22
  @router.put("/operation_mode")
23
23
  async def put_operation_mode(request: Request):
24
24
  '''
25
+ Set the operation mode of the detector node.
26
+
25
27
  Example Usage
28
+
26
29
  curl -X PUT -d "check_for_updates" http://localhost/operation_mode
27
30
  curl -X PUT -d "detecting" http://localhost/operation_mode
28
31
  '''
@@ -34,22 +37,25 @@ async def put_operation_mode(request: Request):
34
37
  raise HTTPException(422, str(exc)) from exc
35
38
  node: DetectorNode = request.app
36
39
 
37
- logging.info(f'current node state : {node.status.state}')
38
- logging.info(f'current operation mode : {node.operation_mode.value}')
39
- logging.info(f'target operation mode : {target_mode}')
40
+ logging.info('current node state : %s', node.status.state)
41
+ logging.info('current operation mode : %s', node.operation_mode.value)
42
+ logging.info('target operation mode : %s', target_mode)
40
43
  if target_mode == node.operation_mode:
41
44
  logging.info('operation mode already set')
42
45
  return "OK"
43
46
 
44
47
  await node.set_operation_mode(target_mode)
45
- logging.info(f'operation mode set to : {target_mode}')
48
+ logging.info('operation mode set to : %s', target_mode)
46
49
  return "OK"
47
50
 
48
51
 
49
- @router.get("/operation_mode")
52
+ @router.get("/operation_mode", response_class=PlainTextResponse)
50
53
  async def get_operation_mode(request: Request):
51
54
  '''
55
+ Get the operation mode of the detector node.
56
+
52
57
  Example Usage
58
+
53
59
  curl http://localhost/operation_mode
54
60
  '''
55
61
  return PlainTextResponse(request.app.operation_mode.value)