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.
- {learning_loop_node-0.18.2.dist-info → learning_loop_node-0.18.3.dist-info}/METADATA +33 -36
- learning_loop_node-0.18.3.dist-info/RECORD +11 -0
- {learning_loop_node-0.18.2.dist-info → learning_loop_node-0.18.3.dist-info}/WHEEL +2 -1
- learning_loop_node-0.18.3.dist-info/top_level.txt +1 -0
- learning_loop_node/annotation/__init__.py +0 -0
- learning_loop_node/annotation/annotator_logic.py +0 -29
- learning_loop_node/annotation/annotator_node.py +0 -116
- learning_loop_node/data_classes/__init__.py +0 -50
- learning_loop_node/data_classes/annotation_data.py +0 -43
- learning_loop_node/data_classes/annotations.py +0 -44
- learning_loop_node/data_classes/detections.py +0 -136
- learning_loop_node/data_classes/general.py +0 -187
- learning_loop_node/data_classes/image_metadata.py +0 -56
- learning_loop_node/data_classes/socket_response.py +0 -24
- learning_loop_node/data_classes/training.py +0 -168
- learning_loop_node/detector/__init__.py +0 -0
- learning_loop_node/detector/detector_logic.py +0 -79
- learning_loop_node/detector/detector_node.py +0 -704
- learning_loop_node/detector/exceptions.py +0 -7
- learning_loop_node/detector/inbox_filter/__init__.py +0 -0
- learning_loop_node/detector/inbox_filter/cam_observation_history.py +0 -82
- learning_loop_node/detector/inbox_filter/relevance_filter.py +0 -40
- learning_loop_node/detector/outbox.py +0 -323
- learning_loop_node/detector/rest/__init__.py +0 -0
- learning_loop_node/detector/rest/about.py +0 -26
- learning_loop_node/detector/rest/backdoor_controls.py +0 -37
- learning_loop_node/detector/rest/detect.py +0 -59
- learning_loop_node/detector/rest/model_version_control.py +0 -46
- learning_loop_node/detector/rest/operation_mode.py +0 -54
- learning_loop_node/detector/rest/outbox_mode.py +0 -41
- learning_loop_node/detector/rest/upload.py +0 -44
- learning_loop_node/enums/__init__.py +0 -6
- learning_loop_node/enums/annotator.py +0 -11
- learning_loop_node/enums/detector.py +0 -18
- learning_loop_node/enums/general.py +0 -9
- learning_loop_node/enums/trainer.py +0 -22
- learning_loop_node/examples/novelty_score_updater.py +0 -70
- learning_loop_node/helpers/__init__.py +0 -0
- learning_loop_node/helpers/background_tasks.py +0 -77
- learning_loop_node/helpers/environment_reader.py +0 -49
- learning_loop_node/helpers/gdrive_downloader.py +0 -43
- learning_loop_node/helpers/log_conf.py +0 -39
- learning_loop_node/helpers/misc.py +0 -239
- learning_loop_node/helpers/run.py +0 -21
- learning_loop_node/tests/__init__.py +0 -0
- learning_loop_node/tests/annotator/__init__.py +0 -0
- learning_loop_node/tests/annotator/conftest.py +0 -55
- learning_loop_node/tests/annotator/pytest.ini +0 -10
- learning_loop_node/tests/annotator/test_annotator_node.py +0 -60
- learning_loop_node/tests/detector/__init__.py +0 -0
- learning_loop_node/tests/detector/conftest.py +0 -174
- learning_loop_node/tests/detector/inbox_filter/__init__.py +0 -0
- learning_loop_node/tests/detector/inbox_filter/test_observation.py +0 -31
- learning_loop_node/tests/detector/inbox_filter/test_relevance_group.py +0 -162
- learning_loop_node/tests/detector/inbox_filter/test_unexpected_observations_count.py +0 -35
- learning_loop_node/tests/detector/pytest.ini +0 -10
- learning_loop_node/tests/detector/test.jpg +0 -0
- learning_loop_node/tests/detector/test_client_communication.py +0 -177
- learning_loop_node/tests/detector/test_detector_node.py +0 -98
- learning_loop_node/tests/detector/test_outbox.py +0 -77
- learning_loop_node/tests/detector/test_relevance_filter.py +0 -43
- learning_loop_node/tests/detector/testing_detector.py +0 -28
- learning_loop_node/tests/general/__init__.py +0 -0
- learning_loop_node/tests/general/conftest.py +0 -68
- learning_loop_node/tests/general/pytest.ini +0 -10
- learning_loop_node/tests/general/test_data/file_1.txt +0 -1
- learning_loop_node/tests/general/test_data/file_2.txt +0 -1
- learning_loop_node/tests/general/test_data/model.json +0 -6
- learning_loop_node/tests/general/test_data_classes.py +0 -22
- learning_loop_node/tests/general/test_downloader.py +0 -84
- learning_loop_node/tests/general/test_learning_loop_node.py +0 -20
- learning_loop_node/tests/test_helper.py +0 -96
- learning_loop_node/tests/trainer/__init__.py +0 -0
- learning_loop_node/tests/trainer/conftest.py +0 -101
- learning_loop_node/tests/trainer/pytest.ini +0 -10
- learning_loop_node/tests/trainer/state_helper.py +0 -22
- learning_loop_node/tests/trainer/states/__init__.py +0 -0
- learning_loop_node/tests/trainer/states/test_state_cleanup.py +0 -27
- learning_loop_node/tests/trainer/states/test_state_detecting.py +0 -83
- learning_loop_node/tests/trainer/states/test_state_download_train_model.py +0 -67
- learning_loop_node/tests/trainer/states/test_state_prepare.py +0 -55
- learning_loop_node/tests/trainer/states/test_state_sync_confusion_matrix.py +0 -117
- learning_loop_node/tests/trainer/states/test_state_train.py +0 -65
- learning_loop_node/tests/trainer/states/test_state_upload_detections.py +0 -164
- learning_loop_node/tests/trainer/states/test_state_upload_model.py +0 -88
- learning_loop_node/tests/trainer/test_errors.py +0 -45
- learning_loop_node/tests/trainer/test_trainer_states.py +0 -39
- learning_loop_node/tests/trainer/testing_trainer_logic.py +0 -94
- learning_loop_node/trainer/__init__.py +0 -0
- learning_loop_node/trainer/downloader.py +0 -31
- learning_loop_node/trainer/exceptions.py +0 -12
- learning_loop_node/trainer/executor.py +0 -109
- learning_loop_node/trainer/io_helpers.py +0 -181
- learning_loop_node/trainer/rest/__init__.py +0 -0
- learning_loop_node/trainer/rest/backdoor_controls.py +0 -120
- learning_loop_node/trainer/test_executor.py +0 -66
- learning_loop_node/trainer/trainer_logic.py +0 -192
- learning_loop_node/trainer/trainer_logic_generic.py +0 -560
- learning_loop_node/trainer/trainer_node.py +0 -106
- learning_loop_node-0.18.2.dist-info/RECORD +0 -105
|
@@ -1,42 +1,40 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
2
|
-
Name:
|
|
3
|
-
Version: 0.18.
|
|
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
|
-
|
|
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,,
|
|
@@ -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)
|