learning-loop-node 0.18.2__py3-none-any.whl → 0.18.3__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.
Files changed (100) hide show
  1. {learning_loop_node-0.18.2.dist-info → learning_loop_node-0.18.3.dist-info}/METADATA +33 -36
  2. learning_loop_node-0.18.3.dist-info/RECORD +11 -0
  3. {learning_loop_node-0.18.2.dist-info → learning_loop_node-0.18.3.dist-info}/WHEEL +2 -1
  4. learning_loop_node-0.18.3.dist-info/top_level.txt +1 -0
  5. learning_loop_node/annotation/__init__.py +0 -0
  6. learning_loop_node/annotation/annotator_logic.py +0 -29
  7. learning_loop_node/annotation/annotator_node.py +0 -116
  8. learning_loop_node/data_classes/__init__.py +0 -50
  9. learning_loop_node/data_classes/annotation_data.py +0 -43
  10. learning_loop_node/data_classes/annotations.py +0 -44
  11. learning_loop_node/data_classes/detections.py +0 -136
  12. learning_loop_node/data_classes/general.py +0 -187
  13. learning_loop_node/data_classes/image_metadata.py +0 -56
  14. learning_loop_node/data_classes/socket_response.py +0 -24
  15. learning_loop_node/data_classes/training.py +0 -168
  16. learning_loop_node/detector/__init__.py +0 -0
  17. learning_loop_node/detector/detector_logic.py +0 -79
  18. learning_loop_node/detector/detector_node.py +0 -704
  19. learning_loop_node/detector/exceptions.py +0 -7
  20. learning_loop_node/detector/inbox_filter/__init__.py +0 -0
  21. learning_loop_node/detector/inbox_filter/cam_observation_history.py +0 -82
  22. learning_loop_node/detector/inbox_filter/relevance_filter.py +0 -40
  23. learning_loop_node/detector/outbox.py +0 -323
  24. learning_loop_node/detector/rest/__init__.py +0 -0
  25. learning_loop_node/detector/rest/about.py +0 -26
  26. learning_loop_node/detector/rest/backdoor_controls.py +0 -37
  27. learning_loop_node/detector/rest/detect.py +0 -59
  28. learning_loop_node/detector/rest/model_version_control.py +0 -46
  29. learning_loop_node/detector/rest/operation_mode.py +0 -54
  30. learning_loop_node/detector/rest/outbox_mode.py +0 -41
  31. learning_loop_node/detector/rest/upload.py +0 -44
  32. learning_loop_node/enums/__init__.py +0 -6
  33. learning_loop_node/enums/annotator.py +0 -11
  34. learning_loop_node/enums/detector.py +0 -18
  35. learning_loop_node/enums/general.py +0 -9
  36. learning_loop_node/enums/trainer.py +0 -22
  37. learning_loop_node/examples/novelty_score_updater.py +0 -70
  38. learning_loop_node/helpers/__init__.py +0 -0
  39. learning_loop_node/helpers/background_tasks.py +0 -77
  40. learning_loop_node/helpers/environment_reader.py +0 -49
  41. learning_loop_node/helpers/gdrive_downloader.py +0 -43
  42. learning_loop_node/helpers/log_conf.py +0 -39
  43. learning_loop_node/helpers/misc.py +0 -239
  44. learning_loop_node/helpers/run.py +0 -21
  45. learning_loop_node/tests/__init__.py +0 -0
  46. learning_loop_node/tests/annotator/__init__.py +0 -0
  47. learning_loop_node/tests/annotator/conftest.py +0 -55
  48. learning_loop_node/tests/annotator/pytest.ini +0 -10
  49. learning_loop_node/tests/annotator/test_annotator_node.py +0 -60
  50. learning_loop_node/tests/detector/__init__.py +0 -0
  51. learning_loop_node/tests/detector/conftest.py +0 -174
  52. learning_loop_node/tests/detector/inbox_filter/__init__.py +0 -0
  53. learning_loop_node/tests/detector/inbox_filter/test_observation.py +0 -31
  54. learning_loop_node/tests/detector/inbox_filter/test_relevance_group.py +0 -162
  55. learning_loop_node/tests/detector/inbox_filter/test_unexpected_observations_count.py +0 -35
  56. learning_loop_node/tests/detector/pytest.ini +0 -10
  57. learning_loop_node/tests/detector/test.jpg +0 -0
  58. learning_loop_node/tests/detector/test_client_communication.py +0 -177
  59. learning_loop_node/tests/detector/test_detector_node.py +0 -98
  60. learning_loop_node/tests/detector/test_outbox.py +0 -77
  61. learning_loop_node/tests/detector/test_relevance_filter.py +0 -43
  62. learning_loop_node/tests/detector/testing_detector.py +0 -28
  63. learning_loop_node/tests/general/__init__.py +0 -0
  64. learning_loop_node/tests/general/conftest.py +0 -68
  65. learning_loop_node/tests/general/pytest.ini +0 -10
  66. learning_loop_node/tests/general/test_data/file_1.txt +0 -1
  67. learning_loop_node/tests/general/test_data/file_2.txt +0 -1
  68. learning_loop_node/tests/general/test_data/model.json +0 -6
  69. learning_loop_node/tests/general/test_data_classes.py +0 -22
  70. learning_loop_node/tests/general/test_downloader.py +0 -84
  71. learning_loop_node/tests/general/test_learning_loop_node.py +0 -20
  72. learning_loop_node/tests/test_helper.py +0 -96
  73. learning_loop_node/tests/trainer/__init__.py +0 -0
  74. learning_loop_node/tests/trainer/conftest.py +0 -101
  75. learning_loop_node/tests/trainer/pytest.ini +0 -10
  76. learning_loop_node/tests/trainer/state_helper.py +0 -22
  77. learning_loop_node/tests/trainer/states/__init__.py +0 -0
  78. learning_loop_node/tests/trainer/states/test_state_cleanup.py +0 -27
  79. learning_loop_node/tests/trainer/states/test_state_detecting.py +0 -83
  80. learning_loop_node/tests/trainer/states/test_state_download_train_model.py +0 -67
  81. learning_loop_node/tests/trainer/states/test_state_prepare.py +0 -55
  82. learning_loop_node/tests/trainer/states/test_state_sync_confusion_matrix.py +0 -117
  83. learning_loop_node/tests/trainer/states/test_state_train.py +0 -65
  84. learning_loop_node/tests/trainer/states/test_state_upload_detections.py +0 -164
  85. learning_loop_node/tests/trainer/states/test_state_upload_model.py +0 -88
  86. learning_loop_node/tests/trainer/test_errors.py +0 -45
  87. learning_loop_node/tests/trainer/test_trainer_states.py +0 -39
  88. learning_loop_node/tests/trainer/testing_trainer_logic.py +0 -94
  89. learning_loop_node/trainer/__init__.py +0 -0
  90. learning_loop_node/trainer/downloader.py +0 -31
  91. learning_loop_node/trainer/exceptions.py +0 -12
  92. learning_loop_node/trainer/executor.py +0 -109
  93. learning_loop_node/trainer/io_helpers.py +0 -181
  94. learning_loop_node/trainer/rest/__init__.py +0 -0
  95. learning_loop_node/trainer/rest/backdoor_controls.py +0 -120
  96. learning_loop_node/trainer/test_executor.py +0 -66
  97. learning_loop_node/trainer/trainer_logic.py +0 -192
  98. learning_loop_node/trainer/trainer_logic_generic.py +0 -560
  99. learning_loop_node/trainer/trainer_node.py +0 -106
  100. learning_loop_node-0.18.2.dist-info/RECORD +0 -105
@@ -1,42 +1,40 @@
1
- Metadata-Version: 2.1
2
- Name: learning-loop-node
3
- Version: 0.18.2
1
+ Metadata-Version: 2.4
2
+ Name: learning_loop_node
3
+ Version: 0.18.3
4
4
  Summary: Python Library for Nodes which connect to the Zauberzeug Learning Loop
5
- Home-page: https://github.com/zauberzeug/learning_loop_node
5
+ Author: Zauberzeug
6
+ Author-email: Zauberzeug GmbH <info@zauberzeug.com>
6
7
  License: MIT
7
- Author: Zauberzeug GmbH
8
- Author-email: info@zauberzeug.com
9
- Requires-Python: >=3.8,<4.0
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.8
13
- Classifier: Programming Language :: Python :: 3.9
14
- Classifier: Programming Language :: Python :: 3.10
15
- Classifier: Programming Language :: Python :: 3.11
16
- Requires-Dist: Pillow (>=10.0.0,<11.0.0)
17
- Requires-Dist: aiofiles (>=0.7.0,<0.8.0)
18
- Requires-Dist: aiohttp (>=3.8.4,<4.0.0)
19
- Requires-Dist: async_generator (>=1.10,<2.0)
20
- Requires-Dist: dacite (>=1.8.1,<2.0.0)
21
- Requires-Dist: fastapi (>=0.93,<0.109)
22
- Requires-Dist: fastapi-socketio (>=0.0.10,<0.0.11)
23
- Requires-Dist: fastapi-utils (>=0.2.1,<0.3.0)
24
- Requires-Dist: httpx (>=0.28.1,<0.29.0)
25
- Requires-Dist: icecream (>=2.1.0,<3.0.0)
26
- Requires-Dist: numpy (>=1.13.3,<2.0.0)
27
- Requires-Dist: psutil (>=5.8.0,<6.0.0)
28
- Requires-Dist: pynvml (>=11.4.1,<12.0.0)
29
- Requires-Dist: pytest-mock (==3.6.1)
30
- Requires-Dist: pytest-watch (>=4.2.0,<5.0.0)
31
- Requires-Dist: python-multipart (>=0.0.5,<0.0.6)
32
- Requires-Dist: python-socketio (>=5.7.2,<6.0.0)
33
- Requires-Dist: requests (>=2.25.1,<3.0.0)
34
- Requires-Dist: simplejson (>=3.17.2,<4.0.0)
35
- Requires-Dist: tqdm (>=4.63.0,<5.0.0)
36
- Requires-Dist: uvicorn[standard] (>=0.22.0,<0.23.0)
37
- Requires-Dist: werkzeug (>=2.0.1,<3.0.0)
38
8
  Project-URL: Repository, https://github.com/zauberzeug/learning_loop_node
9
+ Requires-Python: >=3.9
39
10
  Description-Content-Type: text/markdown
11
+ Requires-Dist: fastapi<1.0,>=0.128
12
+ Requires-Dist: uvicorn[standard]<0.23.0,>=0.22.0
13
+ Requires-Dist: fastapi-utils<0.9.0,>=0.8.0
14
+ Requires-Dist: requests<3.0.0,>=2.25.1
15
+ Requires-Dist: python-socketio<6.0.0,>=5.7.2
16
+ Requires-Dist: simplejson<4.0.0,>=3.17.2
17
+ Requires-Dist: icecream<3.0.0,>=2.1.0
18
+ Requires-Dist: aiofiles<0.8.0,>=0.7.0
19
+ Requires-Dist: async_generator<2.0.0,>=1.10
20
+ Requires-Dist: pytest-watch<5.0.0,>=4.2.0
21
+ Requires-Dist: python-multipart<0.0.19,>=0.0.18
22
+ Requires-Dist: psutil<6.0.0,>=5.8.0
23
+ Requires-Dist: numpy<3.0,>=2.0
24
+ Requires-Dist: Pillow<12.0.0,>=11.0.0
25
+ Requires-Dist: tqdm<5.0.0,>=4.66.3
26
+ Requires-Dist: pytest-mock<4.0.0,>=3.6.1
27
+ Requires-Dist: pynvml<12.0.0,>=11.4.1
28
+ Requires-Dist: aiohttp<4.0.0,>=3.13.3
29
+ Requires-Dist: httpx<0.29.0,>=0.28.1
30
+ Requires-Dist: dacite<2.0.0,>=1.8.1
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest-flakefinder<2.0.0,>=1.1.0; extra == "dev"
33
+ Requires-Dist: retry<0.10.0,>=0.9.2; extra == "dev"
34
+ Requires-Dist: debugpy<2.0.0,>=1.6.7.post1; extra == "dev"
35
+ Requires-Dist: pytest-asyncio<0.22.0,>=0.21.1; extra == "dev"
36
+ Requires-Dist: autopep8<3.0.0,>=2.0.2; extra == "dev"
37
+ Dynamic: author
40
38
 
41
39
  # Learning Loop Node
42
40
 
@@ -280,4 +278,3 @@ After a short time the converted model should be available as well.
280
278
  - Nodes add properties to `model.json`, which contains all the information which are needed by subsequent nodes. These are typically the properties:
281
279
  - `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)
282
280
  - `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
283
-
@@ -0,0 +1,11 @@
1
+ learning_loop_node/__init__.py,sha256=onN5s8-x_xBsCM6NLmJO0Ym1sJHeCFaGw8qb0oQZmz8,364
2
+ learning_loop_node/data_exchanger.py,sha256=nd9JNPLn9amIeTcSIyUPpbE97ORAcb5yNphvmpgWSUQ,9095
3
+ learning_loop_node/globals.py,sha256=tgw_8RYOipPV9aYlyUhYtXfUxvJKRvfUk6u-qVAtZmY,174
4
+ learning_loop_node/loop_communication.py,sha256=d1tJZ9T_y22FrEq1XzvidgH6d-byGxFq3ovLvPBS4FA,7611
5
+ learning_loop_node/node.py,sha256=xK-xODRo7ov-dNNMcpLW2GAauvjKAK3K9RQh4P9S994,12160
6
+ learning_loop_node/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ learning_loop_node/rest.py,sha256=5X9IVW9kf1gNf8jifGW9g_gI_-9TEeoMMOW16jvwpRE,1599
8
+ learning_loop_node-0.18.3.dist-info/METADATA,sha256=fakCUdeAPgO47Y_7_CL60I54FBeYhPOv7vl_duVTQBw,15052
9
+ learning_loop_node-0.18.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
+ learning_loop_node-0.18.3.dist-info/top_level.txt,sha256=nS5qiFc4stVF_VXVCVJxZnrqVOv3IGX5-_E3sLby6OE,19
11
+ learning_loop_node-0.18.3.dist-info/RECORD,,
@@ -1,4 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.6.0
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ learning_loop_node
File without changes
@@ -1,29 +0,0 @@
1
- from abc import abstractmethod
2
- from typing import Dict, Optional
3
-
4
- from ..data_classes import ToolOutput, UserInput
5
- from ..node import Node
6
-
7
-
8
- class AnnotatorLogic():
9
-
10
- def __init__(self) -> None:
11
- self._node: Optional[Node] = None
12
-
13
- def init(self, node: Node) -> None:
14
- self._node = node
15
-
16
- @abstractmethod
17
- async def handle_user_input(self, user_input: UserInput, history: Dict) -> ToolOutput:
18
- """ This method is called when a user input is received from the client.
19
- The function should return a ToolOutput object."""
20
-
21
- @abstractmethod
22
- def create_empty_history(self) -> Dict:
23
- """ This method is called when a new annotation session is started.
24
- The function should return an empty history object."""
25
-
26
- @abstractmethod
27
- def logout_user(self, sid: str) -> bool:
28
- """ This method is called when a user disconnects from the server.
29
- The function should return True if the user was logged out successfully."""
@@ -1,116 +0,0 @@
1
- from dataclasses import asdict
2
- from typing import Dict, Optional
3
-
4
- from dacite import from_dict
5
- from fastapi.encoders import jsonable_encoder
6
- from socketio import AsyncClient
7
-
8
- from ..data_classes import AnnotationNodeStatus, Context, NodeState, UserInput
9
- from ..data_classes.socket_response import SocketResponse
10
- from ..data_exchanger import DataExchanger
11
- from ..helpers.misc import create_image_folder, create_project_folder
12
- from ..node import Node
13
- from .annotator_logic import AnnotatorLogic
14
-
15
- # TODO: The use case 'segmentation' is hardcoded here. This should be more flexible.
16
-
17
-
18
- class AnnotatorNode(Node):
19
-
20
- def __init__(self, name: str, annotator_logic: AnnotatorLogic, uuid: Optional[str] = None):
21
- super().__init__(name, uuid=uuid, node_type='annotation_node')
22
- self.tool = annotator_logic
23
- self.histories: Dict = {}
24
- annotator_logic.init(self)
25
-
26
- def register_sio_events(self, sio_client: AsyncClient):
27
-
28
- @sio_client.event
29
- async def handle_user_input(user_input_dict):
30
- return await self._handle_user_input(user_input_dict)
31
-
32
- @sio_client.event
33
- async def user_logout(sid):
34
- self.reset_history(sid)
35
- return self.tool.logout_user(sid)
36
-
37
- async def _handle_user_input(self, user_input_dict: Dict) -> str:
38
- if not self.sio_client or not self.sio_client.connected:
39
- raise ConnectionError('SocketIO client is not connected')
40
-
41
- user_input = from_dict(data_class=UserInput, data=user_input_dict)
42
-
43
- if user_input.data.key_up == 'Escape':
44
- self.reset_history(user_input.frontend_id)
45
- return ''
46
-
47
- await self.download_image(user_input.data.context, user_input.data.image_uuid)
48
-
49
- try:
50
- tool_result = await self.tool.handle_user_input(user_input, self.get_history(user_input.frontend_id))
51
- except Exception:
52
- self.reset_history(user_input.frontend_id)
53
- raise
54
-
55
- if tool_result.annotation:
56
- await self.sio_client.call('update_segmentation_annotation', (user_input.data.context.organization,
57
- user_input.data.context.project,
58
- jsonable_encoder(asdict(tool_result.annotation))), timeout=30)
59
- return tool_result.svg
60
-
61
- def reset_history(self, frontend_id: str) -> None:
62
- """Reset the history for a given frontend_id."""
63
- if frontend_id in self.histories:
64
- del self.histories[frontend_id]
65
-
66
- def get_history(self, frontend_id: str) -> Dict:
67
- """Get the history for a given frontend_id. If no history exists, create a new one."""
68
- return self.histories.setdefault(frontend_id, self.tool.create_empty_history())
69
-
70
- async def send_status(self):
71
-
72
- if not self.sio_client or not self.sio_client.connected:
73
- raise ConnectionError('SocketIO client is not connected')
74
-
75
- status = AnnotationNodeStatus(
76
- id=self.uuid,
77
- name=self.name,
78
- state=NodeState.Online,
79
- capabilities=['segmentation']
80
- )
81
-
82
- self.log_status_on_change(status.state.value if status.state else 'None', status)
83
-
84
- try:
85
- result = await self.sio_client.call('update_annotation_node', jsonable_encoder(asdict(status)), timeout=10)
86
- except Exception:
87
- self.socket_connection_broken = True
88
- self.log.exception('Error for updating:')
89
- return
90
-
91
- if not isinstance(result, Dict):
92
- self.socket_connection_broken = True
93
- self.log.error('Expected Dict, got %s', type(result))
94
- return
95
-
96
- response = from_dict(data_class=SocketResponse, data=result)
97
-
98
- if not response.success:
99
- self.socket_connection_broken = True
100
- self.log.error('Response from loop was: %s', asdict(response))
101
-
102
- async def download_image(self, context: Context, uuid: str):
103
- project_folder = create_project_folder(context)
104
- images_folder = create_image_folder(project_folder)
105
-
106
- downloader = DataExchanger(context=context, loop_communicator=self.loop_communicator)
107
- await downloader.download_images([uuid], images_folder)
108
-
109
- async def on_startup(self):
110
- pass
111
-
112
- async def on_shutdown(self):
113
- pass
114
-
115
- async def on_repeat(self):
116
- await self.send_status()
@@ -1,50 +0,0 @@
1
- from .annotation_data import (
2
- AnnotationData,
3
- SegmentationAnnotation,
4
- ToolOutput,
5
- UserInput,
6
- )
7
- from .detections import (
8
- BoxDetection,
9
- ClassificationDetection,
10
- Detections,
11
- Observation,
12
- Point,
13
- PointDetection,
14
- SegmentationDetection,
15
- Shape,
16
- )
17
- from .general import (
18
- AboutResponse,
19
- AnnotationNodeStatus,
20
- Category,
21
- Context,
22
- DetectorStatus,
23
- ErrorConfiguration,
24
- ModelInformation,
25
- ModelVersionResponse,
26
- NodeState,
27
- NodeStatus,
28
- )
29
- from .image_metadata import ImageMetadata, ImagesMetadata
30
- from .socket_response import SocketResponse
31
- from .training import (
32
- Errors,
33
- PretrainedModel,
34
- Training,
35
- TrainingError,
36
- TrainingOut,
37
- TrainingStateData,
38
- TrainingStatus,
39
- )
40
-
41
- __all__ = [
42
- 'AboutResponse', 'AnnotationData', 'SegmentationAnnotation', 'ToolOutput', 'UserInput',
43
- 'BoxDetection', 'ClassificationDetection', 'ImageMetadata', 'Observation', 'Point', 'PointDetection',
44
- 'SegmentationDetection', 'Shape', 'Detections',
45
- 'AnnotationNodeStatus', 'Category', 'Context', 'DetectorStatus', 'ErrorConfiguration',
46
- 'ModelInformation', 'NodeState', 'NodeStatus', 'ModelVersionResponse', 'ImagesMetadata',
47
- 'SocketResponse',
48
- 'Errors', 'PretrainedModel', 'Training',
49
- 'TrainingError', 'TrainingOut', 'TrainingStateData', 'TrainingStatus',
50
- ]
@@ -1,43 +0,0 @@
1
- import sys
2
- from dataclasses import dataclass
3
- from typing import Optional, Union
4
-
5
- from ..enums import AnnotationEventType
6
- from .detections import Point, Shape
7
- from .general import Category, Context
8
-
9
- KWONLY_SLOTS = {'kw_only': True, 'slots': True} if sys.version_info >= (3, 10) else {}
10
-
11
-
12
- @dataclass(**KWONLY_SLOTS)
13
- class AnnotationData():
14
- coordinate: Point
15
- event_type: Union[AnnotationEventType, str]
16
- context: Context
17
- image_uuid: str
18
- category: Category
19
-
20
- key_up: Optional[str] = None
21
- key_down: Optional[str] = None
22
- epsilon: Optional[float] = None
23
- is_shift_key_pressed: Optional[bool] = None
24
-
25
-
26
- @dataclass(**KWONLY_SLOTS)
27
- class SegmentationAnnotation():
28
- id: str
29
- shape: Shape
30
- image_id: str
31
- category_id: str
32
-
33
-
34
- @dataclass(**KWONLY_SLOTS)
35
- class UserInput():
36
- frontend_id: str
37
- data: AnnotationData
38
-
39
-
40
- @dataclass(**KWONLY_SLOTS)
41
- class ToolOutput():
42
- svg: str
43
- annotation: Optional[SegmentationAnnotation] = None
@@ -1,44 +0,0 @@
1
-
2
- import sys
3
- from dataclasses import dataclass, field
4
- from typing import Optional
5
-
6
- KWONLY_SLOTS = {'kw_only': True, 'slots': True} if sys.version_info >= (3, 10) else {}
7
-
8
-
9
- @dataclass(**KWONLY_SLOTS)
10
- class BoxAnnotation():
11
- """Coordinates according to COCO format. x,y is the top left corner of the box.
12
- x increases to the right, y increases downwards.
13
- """
14
- category_name: str = field(metadata={'description': 'Category name'})
15
- x: int = field(metadata={'description': 'X coordinate (left to right)'})
16
- y: int = field(metadata={'description': 'Y coordinate (top to bottom)'})
17
- width: int = field(metadata={'description': 'Width'})
18
- height: int = field(metadata={'description': 'Height'})
19
- category_id: Optional[str] = field(default=None, metadata={'description': 'Category UUID'})
20
-
21
- def __str__(self):
22
- return f'x:{int(self.x)} y: {int(self.y)}, w: {int(self.width)} h: {int(self.height)} -> {self.category_name}'
23
-
24
-
25
- @dataclass(**KWONLY_SLOTS)
26
- class PointAnnotation():
27
- """Coordinates according to COCO format. x,y is the center of the point.
28
- x increases to the right, y increases downwards."""
29
- category_name: str = field(metadata={'description': 'Category name'})
30
- x: float = field(metadata={'description': 'X coordinate (right)'})
31
- y: float = field(metadata={'description': 'Y coordinate (down)'})
32
- category_id: Optional[str] = field(default=None, metadata={'description': 'Category UUID'})
33
-
34
- def __str__(self):
35
- return f'x:{int(self.x)} y: {int(self.y)}, -> {self.category_name}'
36
-
37
-
38
- @dataclass(**KWONLY_SLOTS)
39
- class ClassificationAnnotation():
40
- category_name: str = field(metadata={'description': 'Category name'})
41
- category_id: Optional[str] = field(default=None, metadata={'description': 'Category UUID'})
42
-
43
- def __str__(self):
44
- return f'-> {self.category_name}'
@@ -1,136 +0,0 @@
1
-
2
- import sys
3
- from dataclasses import dataclass, field
4
- from datetime import datetime, timedelta
5
- from typing import List, Optional, Union
6
-
7
- import numpy as np
8
-
9
- KWONLY_SLOTS = {'kw_only': True, 'slots': True} if sys.version_info >= (3, 10) else {}
10
-
11
-
12
- @dataclass(**KWONLY_SLOTS)
13
- class BoxDetection():
14
- """Coordinates according to COCO format. x,y is the top left corner of the box.
15
- x increases to the right, y increases downwards.
16
- """
17
- category_name: str = field(metadata={'description': 'Category name'})
18
- x: int = field(metadata={'description': 'X coordinate (left to right)'})
19
- y: int = field(metadata={'description': 'Y coordinate (top to bottom)'})
20
- width: int = field(metadata={'description': 'Width'})
21
- height: int = field(metadata={'description': 'Height'})
22
- model_name: str = field(metadata={'description': 'Model name'})
23
- confidence: float = field(metadata={'description': 'Confidence'})
24
- category_id: Optional[str] = field(default=None, metadata={'description': 'Category UUID'})
25
-
26
- def intersection_over_union(self, other_detection: 'BoxDetection') -> float:
27
- # https://www.pyimagesearch.com/2016/11/07/intersection-over-union-iou-for-object-detection/
28
- xA = max(self.x, other_detection.x)
29
- yA = max(self.y, other_detection.y)
30
- xB = min(self.x + self.width, other_detection.x + other_detection.width)
31
- yB = min(self.y + self.height, other_detection.y +
32
- other_detection.height)
33
-
34
- interArea = max(xB - xA, 0) * max(yB - yA, 0)
35
- union = float(self._get_area() + other_detection._get_area() - interArea) # pylint: disable=protected-access
36
- if union == 0:
37
- print("WARNING: something went wrong while calculating iou")
38
- return 0
39
-
40
- return interArea / union
41
-
42
- def _get_area(self) -> int:
43
- return self.width * self.height
44
-
45
- def __str__(self):
46
- return f'x:{int(self.x)} y: {int(self.y)}, w: {int(self.width)} h: {int(self.height)} c: {self.confidence:.2f} -> {self.category_name}'
47
-
48
-
49
- @dataclass(**KWONLY_SLOTS)
50
- class PointDetection():
51
- """Coordinates according to COCO format. x,y is the center of the point.
52
- x increases to the right, y increases downwards."""
53
- category_name: str = field(metadata={'description': 'Category name'})
54
- x: float = field(metadata={'description': 'X coordinate (right)'})
55
- y: float = field(metadata={'description': 'Y coordinate (down)'})
56
- model_name: str = field(metadata={'description': 'Model name'})
57
- confidence: float = field(metadata={'description': 'Confidence'})
58
- category_id: Optional[str] = field(default=None, metadata={'description': 'Category UUID'})
59
-
60
- def distance(self, other: 'PointDetection') -> float:
61
- return np.sqrt((other.x - self.x)**2 + (other.y - self.y)**2)
62
-
63
- def __str__(self):
64
- return f'x:{int(self.x)} y: {int(self.y)}, c: {self.confidence:.2f} -> {self.category_name}'
65
-
66
-
67
- @dataclass(**KWONLY_SLOTS)
68
- class ClassificationDetection():
69
- category_name: str = field(metadata={'description': 'Category name'})
70
- model_name: str = field(metadata={'description': 'Model name'})
71
- confidence: float = field(metadata={'description': 'Confidence'})
72
- category_id: Optional[str] = field(default=None, metadata={'description': 'Category UUID'})
73
-
74
- def __str__(self):
75
- return f'c: {self.confidence:.2f} -> {self.category_name}'
76
-
77
-
78
- @dataclass(**KWONLY_SLOTS)
79
- class Point():
80
- x: int
81
- y: int
82
-
83
- def __str__(self):
84
- return f'x:{self.x} y: {self.y}'
85
-
86
-
87
- @dataclass(**KWONLY_SLOTS)
88
- class Shape():
89
- points: List[Point]
90
-
91
- def __str__(self):
92
- return ', '. join([str(s) for s in self.points])
93
-
94
-
95
- @dataclass(**KWONLY_SLOTS)
96
- class SegmentationDetection():
97
- category_name: str
98
- shape: Union[Shape, str]
99
- model_name: str
100
- confidence: float
101
- category_id: Optional[str] = None
102
-
103
- def __str__(self):
104
- return f'shape:{str(self.shape)}, c: {self.confidence:.2f} -> {self.category_name}'
105
-
106
-
107
- @dataclass(**KWONLY_SLOTS)
108
- class Detections():
109
- box_detections: List[BoxDetection] = field(default_factory=list, metadata={
110
- 'description': 'List of box detections'})
111
- point_detections: List[PointDetection] = field(default_factory=list, metadata={
112
- 'description': 'List of point detections'})
113
- segmentation_detections: List[SegmentationDetection] = field(default_factory=list, metadata={
114
- 'description': 'List of segmentation detections'})
115
- classification_detections: List[ClassificationDetection] = field(default_factory=list, metadata={
116
- 'description': 'List of classification detections'})
117
-
118
- image_id: Optional[str] = field(default=None, metadata={
119
- 'description': 'Image uuid'})
120
-
121
- def __len__(self):
122
- return len(self.box_detections) + len(self.point_detections) + len(self.segmentation_detections) + len(self.classification_detections)
123
-
124
-
125
- # TODO make dataclass
126
- class Observation():
127
-
128
- def __init__(self, detection: Union[BoxDetection, PointDetection, SegmentationDetection, ClassificationDetection]):
129
- self.detection = detection
130
- self.last_seen = datetime.now()
131
-
132
- def update_last_seen(self):
133
- self.last_seen = datetime.now()
134
-
135
- def is_older_than(self, forget_time_in_seconds):
136
- return self.last_seen < datetime.now() - timedelta(seconds=forget_time_in_seconds)