synapse-sdk 1.0.0a23__py3-none-any.whl → 2025.12.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.
- synapse_sdk/__init__.py +24 -0
- synapse_sdk/cli/__init__.py +310 -5
- synapse_sdk/cli/alias/__init__.py +22 -0
- synapse_sdk/cli/alias/create.py +36 -0
- synapse_sdk/cli/alias/dataclass.py +31 -0
- synapse_sdk/cli/alias/default.py +16 -0
- synapse_sdk/cli/alias/delete.py +15 -0
- synapse_sdk/cli/alias/list.py +19 -0
- synapse_sdk/cli/alias/read.py +15 -0
- synapse_sdk/cli/alias/update.py +17 -0
- synapse_sdk/cli/alias/utils.py +61 -0
- synapse_sdk/cli/code_server.py +687 -0
- synapse_sdk/cli/config.py +440 -0
- synapse_sdk/cli/devtools.py +90 -0
- synapse_sdk/cli/plugin/__init__.py +33 -0
- synapse_sdk/cli/{create_plugin.py → plugin/create.py} +2 -2
- synapse_sdk/{plugins/cli → cli/plugin}/publish.py +23 -15
- synapse_sdk/clients/agent/__init__.py +9 -3
- synapse_sdk/clients/agent/container.py +143 -0
- synapse_sdk/clients/agent/core.py +19 -0
- synapse_sdk/clients/agent/ray.py +298 -9
- synapse_sdk/clients/backend/__init__.py +30 -12
- synapse_sdk/clients/backend/annotation.py +13 -5
- synapse_sdk/clients/backend/core.py +31 -4
- synapse_sdk/clients/backend/data_collection.py +186 -0
- synapse_sdk/clients/backend/hitl.py +17 -0
- synapse_sdk/clients/backend/integration.py +16 -1
- synapse_sdk/clients/backend/ml.py +5 -1
- synapse_sdk/clients/backend/models.py +78 -0
- synapse_sdk/clients/base.py +384 -41
- synapse_sdk/clients/ray/serve.py +2 -0
- synapse_sdk/clients/validators/collections.py +31 -0
- synapse_sdk/devtools/config.py +94 -0
- synapse_sdk/devtools/server.py +41 -0
- synapse_sdk/devtools/streamlit_app/__init__.py +5 -0
- synapse_sdk/devtools/streamlit_app/app.py +128 -0
- synapse_sdk/devtools/streamlit_app/services/__init__.py +11 -0
- synapse_sdk/devtools/streamlit_app/services/job_service.py +233 -0
- synapse_sdk/devtools/streamlit_app/services/plugin_service.py +236 -0
- synapse_sdk/devtools/streamlit_app/services/serve_service.py +95 -0
- synapse_sdk/devtools/streamlit_app/ui/__init__.py +15 -0
- synapse_sdk/devtools/streamlit_app/ui/config_tab.py +76 -0
- synapse_sdk/devtools/streamlit_app/ui/deployment_tab.py +66 -0
- synapse_sdk/devtools/streamlit_app/ui/http_tab.py +125 -0
- synapse_sdk/devtools/streamlit_app/ui/jobs_tab.py +573 -0
- synapse_sdk/devtools/streamlit_app/ui/serve_tab.py +346 -0
- synapse_sdk/devtools/streamlit_app/ui/status_bar.py +118 -0
- synapse_sdk/devtools/streamlit_app/utils/__init__.py +40 -0
- synapse_sdk/devtools/streamlit_app/utils/json_viewer.py +197 -0
- synapse_sdk/devtools/streamlit_app/utils/log_formatter.py +38 -0
- synapse_sdk/devtools/streamlit_app/utils/styles.py +241 -0
- synapse_sdk/devtools/streamlit_app/utils/ui_components.py +289 -0
- synapse_sdk/devtools/streamlit_app.py +10 -0
- synapse_sdk/loggers.py +120 -9
- synapse_sdk/plugins/README.md +1340 -0
- synapse_sdk/plugins/__init__.py +0 -13
- synapse_sdk/plugins/categories/base.py +117 -11
- synapse_sdk/plugins/categories/data_validation/actions/validation.py +72 -0
- synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +33 -5
- synapse_sdk/plugins/categories/export/actions/__init__.py +3 -0
- synapse_sdk/plugins/categories/export/actions/export/__init__.py +28 -0
- synapse_sdk/plugins/categories/export/actions/export/action.py +165 -0
- synapse_sdk/plugins/categories/export/actions/export/enums.py +113 -0
- synapse_sdk/plugins/categories/export/actions/export/exceptions.py +53 -0
- synapse_sdk/plugins/categories/export/actions/export/models.py +74 -0
- synapse_sdk/plugins/categories/export/actions/export/run.py +195 -0
- synapse_sdk/plugins/categories/export/actions/export/utils.py +187 -0
- synapse_sdk/plugins/categories/export/templates/config.yaml +21 -0
- synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +390 -0
- synapse_sdk/plugins/categories/export/templates/plugin/export.py +160 -0
- synapse_sdk/plugins/categories/neural_net/actions/deployment.py +13 -12
- synapse_sdk/plugins/categories/neural_net/actions/train.py +1134 -31
- synapse_sdk/plugins/categories/neural_net/actions/tune.py +534 -0
- synapse_sdk/plugins/categories/neural_net/base/inference.py +1 -1
- synapse_sdk/plugins/categories/neural_net/templates/config.yaml +32 -4
- synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +26 -10
- synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
- synapse_sdk/plugins/categories/{export/actions/export.py → pre_annotation/actions/pre_annotation/action.py} +4 -4
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +148 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +100 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +248 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +265 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +92 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +243 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +19 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/to_task.py +40 -0
- synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +2 -0
- synapse_sdk/plugins/categories/upload/__init__.py +0 -0
- synapse_sdk/plugins/categories/upload/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +19 -0
- synapse_sdk/plugins/categories/upload/actions/upload/action.py +236 -0
- synapse_sdk/plugins/categories/upload/actions/upload/context.py +185 -0
- synapse_sdk/plugins/categories/upload/actions/upload/enums.py +493 -0
- synapse_sdk/plugins/categories/upload/actions/upload/exceptions.py +36 -0
- synapse_sdk/plugins/categories/upload/actions/upload/factory.py +138 -0
- synapse_sdk/plugins/categories/upload/actions/upload/models.py +214 -0
- synapse_sdk/plugins/categories/upload/actions/upload/orchestrator.py +183 -0
- synapse_sdk/plugins/categories/upload/actions/upload/registry.py +113 -0
- synapse_sdk/plugins/categories/upload/actions/upload/run.py +179 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/base.py +107 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +62 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/collection.py +63 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +91 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +82 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +235 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +201 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +104 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/validate.py +71 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/base.py +82 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/batch.py +39 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/single.py +29 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +300 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +287 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/excel.py +174 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/none.py +16 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/sync.py +84 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +60 -0
- synapse_sdk/plugins/categories/upload/actions/upload/utils.py +250 -0
- synapse_sdk/plugins/categories/upload/templates/README.md +470 -0
- synapse_sdk/plugins/categories/upload/templates/config.yaml +33 -0
- synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +310 -0
- synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +102 -0
- synapse_sdk/plugins/enums.py +3 -1
- synapse_sdk/plugins/models.py +148 -11
- synapse_sdk/plugins/templates/plugin-config-schema.json +406 -0
- synapse_sdk/plugins/templates/schema.json +491 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +1 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +1 -1
- synapse_sdk/plugins/utils/__init__.py +46 -0
- synapse_sdk/plugins/utils/actions.py +119 -0
- synapse_sdk/plugins/utils/config.py +203 -0
- synapse_sdk/plugins/{utils.py → utils/legacy.py} +26 -46
- synapse_sdk/plugins/utils/ray_gcs.py +66 -0
- synapse_sdk/plugins/utils/registry.py +58 -0
- synapse_sdk/shared/__init__.py +25 -0
- synapse_sdk/shared/enums.py +93 -0
- synapse_sdk/types.py +19 -0
- synapse_sdk/utils/converters/__init__.py +240 -0
- synapse_sdk/utils/converters/coco/__init__.py +0 -0
- synapse_sdk/utils/converters/coco/from_dm.py +322 -0
- synapse_sdk/utils/converters/coco/to_dm.py +215 -0
- synapse_sdk/utils/converters/dm/__init__.py +57 -0
- synapse_sdk/utils/converters/dm/base.py +137 -0
- synapse_sdk/utils/converters/dm/from_v1.py +273 -0
- synapse_sdk/utils/converters/dm/to_v1.py +321 -0
- synapse_sdk/utils/converters/dm/tools/__init__.py +214 -0
- synapse_sdk/utils/converters/dm/tools/answer.py +95 -0
- synapse_sdk/utils/converters/dm/tools/bounding_box.py +132 -0
- synapse_sdk/utils/converters/dm/tools/bounding_box_3d.py +121 -0
- synapse_sdk/utils/converters/dm/tools/classification.py +75 -0
- synapse_sdk/utils/converters/dm/tools/keypoint.py +117 -0
- synapse_sdk/utils/converters/dm/tools/named_entity.py +111 -0
- synapse_sdk/utils/converters/dm/tools/polygon.py +122 -0
- synapse_sdk/utils/converters/dm/tools/polyline.py +124 -0
- synapse_sdk/utils/converters/dm/tools/prompt.py +94 -0
- synapse_sdk/utils/converters/dm/tools/relation.py +86 -0
- synapse_sdk/utils/converters/dm/tools/segmentation.py +141 -0
- synapse_sdk/utils/converters/dm/tools/segmentation_3d.py +83 -0
- synapse_sdk/utils/converters/dm/types.py +168 -0
- synapse_sdk/utils/converters/dm/utils.py +162 -0
- synapse_sdk/utils/converters/dm_legacy/__init__.py +56 -0
- synapse_sdk/utils/converters/dm_legacy/from_v1.py +627 -0
- synapse_sdk/utils/converters/dm_legacy/to_v1.py +367 -0
- synapse_sdk/utils/converters/pascal/__init__.py +0 -0
- synapse_sdk/utils/converters/pascal/from_dm.py +244 -0
- synapse_sdk/utils/converters/pascal/to_dm.py +214 -0
- synapse_sdk/utils/converters/yolo/__init__.py +0 -0
- synapse_sdk/utils/converters/yolo/from_dm.py +384 -0
- synapse_sdk/utils/converters/yolo/to_dm.py +267 -0
- synapse_sdk/utils/dataset.py +46 -0
- synapse_sdk/utils/encryption.py +158 -0
- synapse_sdk/utils/file/__init__.py +58 -0
- synapse_sdk/utils/file/archive.py +32 -0
- synapse_sdk/utils/file/checksum.py +56 -0
- synapse_sdk/utils/file/chunking.py +31 -0
- synapse_sdk/utils/file/download.py +385 -0
- synapse_sdk/utils/file/encoding.py +40 -0
- synapse_sdk/utils/file/io.py +22 -0
- synapse_sdk/utils/file/upload.py +165 -0
- synapse_sdk/utils/file/video/__init__.py +29 -0
- synapse_sdk/utils/file/video/transcode.py +307 -0
- synapse_sdk/utils/file.py.backup +301 -0
- synapse_sdk/utils/http.py +138 -0
- synapse_sdk/utils/network.py +309 -0
- synapse_sdk/utils/storage/__init__.py +72 -0
- synapse_sdk/utils/storage/providers/__init__.py +183 -0
- synapse_sdk/utils/storage/providers/file_system.py +134 -0
- synapse_sdk/utils/storage/providers/gcp.py +13 -0
- synapse_sdk/utils/storage/providers/http.py +190 -0
- synapse_sdk/utils/storage/providers/s3.py +91 -0
- synapse_sdk/utils/storage/providers/sftp.py +47 -0
- synapse_sdk/utils/storage/registry.py +17 -0
- synapse_sdk-2025.12.3.dist-info/METADATA +123 -0
- synapse_sdk-2025.12.3.dist-info/RECORD +279 -0
- {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/WHEEL +1 -1
- synapse_sdk/clients/backend/dataset.py +0 -51
- synapse_sdk/plugins/categories/import/actions/import.py +0 -10
- synapse_sdk/plugins/cli/__init__.py +0 -21
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
- synapse_sdk/utils/file.py +0 -168
- synapse_sdk/utils/storage.py +0 -91
- synapse_sdk-1.0.0a23.dist-info/METADATA +0 -44
- synapse_sdk-1.0.0a23.dist-info/RECORD +0 -114
- /synapse_sdk/{plugins/cli → cli/plugin}/run.py +0 -0
- /synapse_sdk/{plugins/categories/import → clients/validators}/__init__.py +0 -0
- /synapse_sdk/{plugins/categories/import/actions → devtools}/__init__.py +0 -0
- {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info/licenses}/LICENSE +0 -0
- {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Bounding Box Tool Processor
|
|
3
|
+
|
|
4
|
+
Created: 2025-12-11
|
|
5
|
+
|
|
6
|
+
Conversion Rules (see data-model.md 4.1):
|
|
7
|
+
V1 → V2:
|
|
8
|
+
- coordinate.{x, y, width, height} → data[x, y, width, height]
|
|
9
|
+
- coordinate.rotation → attrs[{name:"rotation", value}]
|
|
10
|
+
- classification.class → classification
|
|
11
|
+
- classification.{other} → attrs[{name, value}]
|
|
12
|
+
|
|
13
|
+
V2 → V1:
|
|
14
|
+
- data[0,1,2,3] → coordinate.{x, y, width, height}
|
|
15
|
+
- attrs.rotation → coordinate.rotation
|
|
16
|
+
- classification → classification.class
|
|
17
|
+
- attrs[{name, value}] → classification.{name: value} (excluding special attrs)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BoundingBoxProcessor:
|
|
24
|
+
"""Bounding Box Tool Processor
|
|
25
|
+
|
|
26
|
+
V1 coordinate: {x, y, width, height, rotation?}
|
|
27
|
+
V2 data: [x, y, width, height]
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
tool_name = 'bounding_box'
|
|
31
|
+
|
|
32
|
+
# V1 meta fields (not stored in attrs)
|
|
33
|
+
_META_FIELDS = {'isLocked', 'isVisible', 'isValid', 'isDrawCompleted', 'label', 'id', 'tool'}
|
|
34
|
+
|
|
35
|
+
# Fields to restore from V2 attrs to coordinate
|
|
36
|
+
_COORDINATE_ATTRS = {'rotation'}
|
|
37
|
+
|
|
38
|
+
# Special attrs not restored to V1 classification (_ prefix)
|
|
39
|
+
_INTERNAL_ATTR_PREFIX = '_'
|
|
40
|
+
|
|
41
|
+
def to_v2(self, v1_annotation: dict[str, Any], v1_data: dict[str, Any]) -> dict[str, Any]:
|
|
42
|
+
"""Convert V1 bounding box to V2
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
v1_annotation: V1 annotations[] item
|
|
46
|
+
v1_data: V1 annotationsData[] item (same ID)
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
V2 format bounding box annotation
|
|
50
|
+
"""
|
|
51
|
+
coordinate = v1_data.get('coordinate', {})
|
|
52
|
+
classification_obj = v1_annotation.get('classification') or {}
|
|
53
|
+
|
|
54
|
+
# V2 data: [x, y, width, height]
|
|
55
|
+
data = [
|
|
56
|
+
coordinate.get('x', 0),
|
|
57
|
+
coordinate.get('y', 0),
|
|
58
|
+
coordinate.get('width', 0),
|
|
59
|
+
coordinate.get('height', 0),
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
# Build V2 attrs
|
|
63
|
+
attrs: list[dict[str, Any]] = []
|
|
64
|
+
|
|
65
|
+
# Add rotation to attrs
|
|
66
|
+
if 'rotation' in coordinate:
|
|
67
|
+
attrs.append({'name': 'rotation', 'value': coordinate['rotation']})
|
|
68
|
+
|
|
69
|
+
# Add other classification properties to attrs (excluding class)
|
|
70
|
+
for key, value in classification_obj.items():
|
|
71
|
+
if key != 'class':
|
|
72
|
+
attrs.append({'name': key, 'value': value})
|
|
73
|
+
|
|
74
|
+
# Build V2 annotation
|
|
75
|
+
return {
|
|
76
|
+
'id': v1_annotation.get('id', ''),
|
|
77
|
+
'classification': classification_obj.get('class', ''),
|
|
78
|
+
'attrs': attrs,
|
|
79
|
+
'data': data,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
def to_v1(self, v2_annotation: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
83
|
+
"""Convert V2 bounding box to V1
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
v2_annotation: V2 annotation object
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
(V1 annotation, V1 annotationData) tuple
|
|
90
|
+
"""
|
|
91
|
+
annotation_id = v2_annotation.get('id', '')
|
|
92
|
+
classification_str = v2_annotation.get('classification', '')
|
|
93
|
+
attrs = v2_annotation.get('attrs', [])
|
|
94
|
+
data = v2_annotation.get('data', [0, 0, 0, 0])
|
|
95
|
+
|
|
96
|
+
# Build V1 coordinate
|
|
97
|
+
coordinate: dict[str, Any] = {
|
|
98
|
+
'x': data[0] if len(data) > 0 else 0,
|
|
99
|
+
'y': data[1] if len(data) > 1 else 0,
|
|
100
|
+
'width': data[2] if len(data) > 2 else 0,
|
|
101
|
+
'height': data[3] if len(data) > 3 else 0,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Build V1 classification
|
|
105
|
+
classification: dict[str, Any] = {'class': classification_str}
|
|
106
|
+
|
|
107
|
+
# Restore properties from attrs
|
|
108
|
+
for attr in attrs:
|
|
109
|
+
name = attr.get('name', '')
|
|
110
|
+
value = attr.get('value')
|
|
111
|
+
|
|
112
|
+
if name in self._COORDINATE_ATTRS:
|
|
113
|
+
# Add rotation etc. to coordinate
|
|
114
|
+
coordinate[name] = value
|
|
115
|
+
elif not name.startswith(self._INTERNAL_ATTR_PREFIX):
|
|
116
|
+
# Add non-internal attrs to classification
|
|
117
|
+
classification[name] = value
|
|
118
|
+
|
|
119
|
+
# V1 annotation (meta info)
|
|
120
|
+
v1_annotation: dict[str, Any] = {
|
|
121
|
+
'id': annotation_id,
|
|
122
|
+
'tool': self.tool_name,
|
|
123
|
+
'classification': classification,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# V1 annotationData (coordinate info)
|
|
127
|
+
v1_data: dict[str, Any] = {
|
|
128
|
+
'id': annotation_id,
|
|
129
|
+
'coordinate': coordinate,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return v1_annotation, v1_data
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""
|
|
2
|
+
3D Bounding Box Tool Processor
|
|
3
|
+
|
|
4
|
+
Created: 2025-12-12
|
|
5
|
+
|
|
6
|
+
Conversion Rules (see data-model.md 9.6):
|
|
7
|
+
V1 → V2:
|
|
8
|
+
- psr {position, scale, rotation} → data {position, scale, rotation}
|
|
9
|
+
- classification.class → classification
|
|
10
|
+
- classification.{other} → attrs[{name, value}]
|
|
11
|
+
|
|
12
|
+
V2 → V1:
|
|
13
|
+
- data {position, scale, rotation} → psr {position, scale, rotation}
|
|
14
|
+
- classification → classification.class
|
|
15
|
+
- attrs[{name, value}] → classification.{name: value} (excluding special attrs)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BoundingBox3DProcessor:
|
|
22
|
+
"""3D Bounding Box Tool Processor
|
|
23
|
+
|
|
24
|
+
V1 psr: {position, scale, rotation} each {x, y, z}
|
|
25
|
+
V2 data: {position, scale, rotation} each {x, y, z}
|
|
26
|
+
|
|
27
|
+
PSR structure is identical, only key name changes (psr → data).
|
|
28
|
+
Used with pcd (point cloud) media type.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
tool_name = '3d_bounding_box'
|
|
32
|
+
|
|
33
|
+
# V1 meta fields (not stored in attrs)
|
|
34
|
+
_META_FIELDS = {'isLocked', 'isVisible', 'isValid', 'isDrawCompleted', 'label', 'id', 'tool'}
|
|
35
|
+
|
|
36
|
+
# Special attrs not restored to V1 classification (_ prefix)
|
|
37
|
+
_INTERNAL_ATTR_PREFIX = '_'
|
|
38
|
+
|
|
39
|
+
def to_v2(self, v1_annotation: dict[str, Any], v1_data: dict[str, Any]) -> dict[str, Any]:
|
|
40
|
+
"""Convert V1 3D bounding box to V2
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
v1_annotation: V1 annotations[] item
|
|
44
|
+
v1_data: V1 annotationsData[] item (same ID)
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
V2 format 3D bounding box annotation
|
|
48
|
+
"""
|
|
49
|
+
psr = v1_data.get('psr', {})
|
|
50
|
+
classification_obj = v1_annotation.get('classification') or {}
|
|
51
|
+
|
|
52
|
+
# V2 data: copy psr structure as-is
|
|
53
|
+
data = {
|
|
54
|
+
'position': psr.get('position', {'x': 0, 'y': 0, 'z': 0}),
|
|
55
|
+
'scale': psr.get('scale', {'x': 0, 'y': 0, 'z': 0}),
|
|
56
|
+
'rotation': psr.get('rotation', {'x': 0, 'y': 0, 'z': 0}),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Build V2 attrs
|
|
60
|
+
attrs: list[dict[str, Any]] = []
|
|
61
|
+
|
|
62
|
+
# Add other classification properties to attrs (excluding class)
|
|
63
|
+
for key, value in classification_obj.items():
|
|
64
|
+
if key != 'class':
|
|
65
|
+
attrs.append({'name': key, 'value': value})
|
|
66
|
+
|
|
67
|
+
# Build V2 annotation
|
|
68
|
+
return {
|
|
69
|
+
'id': v1_annotation.get('id', ''),
|
|
70
|
+
'classification': classification_obj.get('class', ''),
|
|
71
|
+
'attrs': attrs,
|
|
72
|
+
'data': data,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
def to_v1(self, v2_annotation: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
76
|
+
"""Convert V2 3D bounding box to V1
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
v2_annotation: V2 annotation object
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
(V1 annotation, V1 annotationData) tuple
|
|
83
|
+
"""
|
|
84
|
+
annotation_id = v2_annotation.get('id', '')
|
|
85
|
+
classification_str = v2_annotation.get('classification', '')
|
|
86
|
+
attrs = v2_annotation.get('attrs', [])
|
|
87
|
+
data = v2_annotation.get('data', {})
|
|
88
|
+
|
|
89
|
+
# Build V1 psr: copy data structure as-is
|
|
90
|
+
psr: dict[str, Any] = {
|
|
91
|
+
'position': data.get('position', {'x': 0, 'y': 0, 'z': 0}),
|
|
92
|
+
'scale': data.get('scale', {'x': 0, 'y': 0, 'z': 0}),
|
|
93
|
+
'rotation': data.get('rotation', {'x': 0, 'y': 0, 'z': 0}),
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Build V1 classification
|
|
97
|
+
classification: dict[str, Any] = {'class': classification_str}
|
|
98
|
+
|
|
99
|
+
# Restore properties from attrs
|
|
100
|
+
for attr in attrs:
|
|
101
|
+
name = attr.get('name', '')
|
|
102
|
+
value = attr.get('value')
|
|
103
|
+
|
|
104
|
+
if not name.startswith(self._INTERNAL_ATTR_PREFIX):
|
|
105
|
+
# Add non-internal attrs to classification
|
|
106
|
+
classification[name] = value
|
|
107
|
+
|
|
108
|
+
# V1 annotation (meta info)
|
|
109
|
+
v1_annotation: dict[str, Any] = {
|
|
110
|
+
'id': annotation_id,
|
|
111
|
+
'tool': self.tool_name,
|
|
112
|
+
'classification': classification,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# V1 annotationData (coordinate info)
|
|
116
|
+
v1_data: dict[str, Any] = {
|
|
117
|
+
'id': annotation_id,
|
|
118
|
+
'psr': psr,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return v1_annotation, v1_data
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Classification Tool Processor
|
|
3
|
+
|
|
4
|
+
Created: 2025-12-12
|
|
5
|
+
|
|
6
|
+
Conversion Rules (see data-model.md 9.8):
|
|
7
|
+
V1 → V2:
|
|
8
|
+
- classification.class → classification
|
|
9
|
+
- classification.{other} → attrs[{name, value}]
|
|
10
|
+
- annotationsData contains only id
|
|
11
|
+
|
|
12
|
+
V2 → V1:
|
|
13
|
+
- classification → classification.class
|
|
14
|
+
- attrs[{name, value}] → classification.{name: value}
|
|
15
|
+
- data is empty object {}
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ClassificationProcessor:
|
|
22
|
+
"""Classification Tool Processor
|
|
23
|
+
|
|
24
|
+
V1: All properties in annotations, only id in annotationsData
|
|
25
|
+
V2: data is empty object, all properties stored in attrs
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
tool_name = 'classification'
|
|
29
|
+
|
|
30
|
+
_META_FIELDS = {'isLocked', 'isVisible', 'isValid', 'isDrawCompleted', 'label', 'id', 'tool'}
|
|
31
|
+
_INTERNAL_ATTR_PREFIX = '_'
|
|
32
|
+
|
|
33
|
+
def to_v2(self, v1_annotation: dict[str, Any], v1_data: dict[str, Any]) -> dict[str, Any]:
|
|
34
|
+
"""Convert V1 classification to V2"""
|
|
35
|
+
classification_obj = v1_annotation.get('classification') or {}
|
|
36
|
+
|
|
37
|
+
# Build V2 attrs (all properties excluding class)
|
|
38
|
+
attrs: list[dict[str, Any]] = []
|
|
39
|
+
for key, value in classification_obj.items():
|
|
40
|
+
if key != 'class':
|
|
41
|
+
attrs.append({'name': key, 'value': value})
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
'id': v1_annotation.get('id', ''),
|
|
45
|
+
'classification': classification_obj.get('class', ''),
|
|
46
|
+
'attrs': attrs,
|
|
47
|
+
'data': {}, # Empty object
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
def to_v1(self, v2_annotation: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
51
|
+
"""Convert V2 classification to V1"""
|
|
52
|
+
annotation_id = v2_annotation.get('id', '')
|
|
53
|
+
classification_str = v2_annotation.get('classification', '')
|
|
54
|
+
attrs = v2_annotation.get('attrs', [])
|
|
55
|
+
|
|
56
|
+
# Build V1 classification
|
|
57
|
+
classification: dict[str, Any] = {'class': classification_str}
|
|
58
|
+
for attr in attrs:
|
|
59
|
+
name = attr.get('name', '')
|
|
60
|
+
value = attr.get('value')
|
|
61
|
+
if not name.startswith(self._INTERNAL_ATTR_PREFIX):
|
|
62
|
+
classification[name] = value
|
|
63
|
+
|
|
64
|
+
v1_annotation: dict[str, Any] = {
|
|
65
|
+
'id': annotation_id,
|
|
66
|
+
'tool': self.tool_name,
|
|
67
|
+
'classification': classification,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# annotationsData contains only id
|
|
71
|
+
v1_data: dict[str, Any] = {
|
|
72
|
+
'id': annotation_id,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return v1_annotation, v1_data
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Keypoint Tool Processor
|
|
3
|
+
|
|
4
|
+
Created: 2025-12-12
|
|
5
|
+
|
|
6
|
+
Conversion Rules (see data-model.md 9.2):
|
|
7
|
+
V1 → V2:
|
|
8
|
+
- coordinate {x, y} → data [x, y]
|
|
9
|
+
- classification.class → classification
|
|
10
|
+
- classification.{other} → attrs[{name, value}]
|
|
11
|
+
|
|
12
|
+
V2 → V1:
|
|
13
|
+
- data [x, y] → coordinate {x, y}
|
|
14
|
+
- classification → classification.class
|
|
15
|
+
- attrs[{name, value}] → classification.{name: value} (excluding special attrs)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class KeypointProcessor:
|
|
22
|
+
"""Keypoint Tool Processor
|
|
23
|
+
|
|
24
|
+
V1 coordinate: {x, y}
|
|
25
|
+
V2 data: [x, y]
|
|
26
|
+
|
|
27
|
+
Simplest single point structure.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
tool_name = 'keypoint'
|
|
31
|
+
|
|
32
|
+
# V1 meta fields (not stored in attrs)
|
|
33
|
+
_META_FIELDS = {'isLocked', 'isVisible', 'isValid', 'isDrawCompleted', 'label', 'id', 'tool'}
|
|
34
|
+
|
|
35
|
+
# Special attrs not restored to V1 classification (_ prefix)
|
|
36
|
+
_INTERNAL_ATTR_PREFIX = '_'
|
|
37
|
+
|
|
38
|
+
def to_v2(self, v1_annotation: dict[str, Any], v1_data: dict[str, Any]) -> dict[str, Any]:
|
|
39
|
+
"""Convert V1 keypoint to V2
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
v1_annotation: V1 annotations[] item
|
|
43
|
+
v1_data: V1 annotationsData[] item (same ID)
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
V2 format keypoint annotation
|
|
47
|
+
"""
|
|
48
|
+
coordinate = v1_data.get('coordinate', {})
|
|
49
|
+
classification_obj = v1_annotation.get('classification') or {}
|
|
50
|
+
|
|
51
|
+
# V2 data: [x, y] - extract x, y from coordinate object
|
|
52
|
+
data = [coordinate.get('x', 0), coordinate.get('y', 0)]
|
|
53
|
+
|
|
54
|
+
# Build V2 attrs
|
|
55
|
+
attrs: list[dict[str, Any]] = []
|
|
56
|
+
|
|
57
|
+
# Add other classification properties to attrs (excluding class)
|
|
58
|
+
for key, value in classification_obj.items():
|
|
59
|
+
if key != 'class':
|
|
60
|
+
attrs.append({'name': key, 'value': value})
|
|
61
|
+
|
|
62
|
+
# Build V2 annotation
|
|
63
|
+
return {
|
|
64
|
+
'id': v1_annotation.get('id', ''),
|
|
65
|
+
'classification': classification_obj.get('class', ''),
|
|
66
|
+
'attrs': attrs,
|
|
67
|
+
'data': data,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
def to_v1(self, v2_annotation: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
71
|
+
"""Convert V2 keypoint to V1
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
v2_annotation: V2 annotation object
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
(V1 annotation, V1 annotationData) tuple
|
|
78
|
+
"""
|
|
79
|
+
annotation_id = v2_annotation.get('id', '')
|
|
80
|
+
classification_str = v2_annotation.get('classification', '')
|
|
81
|
+
attrs = v2_annotation.get('attrs', [])
|
|
82
|
+
data = v2_annotation.get('data', [])
|
|
83
|
+
|
|
84
|
+
# Build V1 coordinate: [x, y] → {x, y}
|
|
85
|
+
coordinate: dict[str, Any] = {}
|
|
86
|
+
if isinstance(data, list) and len(data) >= 2:
|
|
87
|
+
coordinate = {
|
|
88
|
+
'x': data[0],
|
|
89
|
+
'y': data[1],
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# Build V1 classification
|
|
93
|
+
classification: dict[str, Any] = {'class': classification_str}
|
|
94
|
+
|
|
95
|
+
# Restore properties from attrs
|
|
96
|
+
for attr in attrs:
|
|
97
|
+
name = attr.get('name', '')
|
|
98
|
+
value = attr.get('value')
|
|
99
|
+
|
|
100
|
+
if not name.startswith(self._INTERNAL_ATTR_PREFIX):
|
|
101
|
+
# Add non-internal attrs to classification
|
|
102
|
+
classification[name] = value
|
|
103
|
+
|
|
104
|
+
# V1 annotation (meta info)
|
|
105
|
+
v1_annotation: dict[str, Any] = {
|
|
106
|
+
'id': annotation_id,
|
|
107
|
+
'tool': self.tool_name,
|
|
108
|
+
'classification': classification,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# V1 annotationData (coordinate info)
|
|
112
|
+
v1_data: dict[str, Any] = {
|
|
113
|
+
'id': annotation_id,
|
|
114
|
+
'coordinate': coordinate,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return v1_annotation, v1_data
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Named Entity Tool Processor
|
|
3
|
+
|
|
4
|
+
Created: 2025-12-12
|
|
5
|
+
|
|
6
|
+
Conversion Rules (see data-model.md 9.5):
|
|
7
|
+
V1 → V2:
|
|
8
|
+
- ranges, content → data {ranges, content}
|
|
9
|
+
- classification.class → classification
|
|
10
|
+
- classification.{other} → attrs[{name, value}]
|
|
11
|
+
|
|
12
|
+
V2 → V1:
|
|
13
|
+
- data {ranges, content} → ranges, content
|
|
14
|
+
- classification → classification.class
|
|
15
|
+
- attrs[{name, value}] → classification.{name: value} (excluding special attrs)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NamedEntityProcessor:
|
|
22
|
+
"""Named Entity Tool Processor
|
|
23
|
+
|
|
24
|
+
V1 annotationData: {ranges: [...], content: str}
|
|
25
|
+
V2 data: {ranges: [...], content: str}
|
|
26
|
+
|
|
27
|
+
Used with text media type.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
tool_name = 'named_entity'
|
|
31
|
+
|
|
32
|
+
# V1 meta fields (not stored in attrs)
|
|
33
|
+
_META_FIELDS = {'isLocked', 'isVisible', 'isValid', 'isDrawCompleted', 'label', 'id', 'tool'}
|
|
34
|
+
|
|
35
|
+
# Special attrs not restored to V1 classification (_ prefix)
|
|
36
|
+
_INTERNAL_ATTR_PREFIX = '_'
|
|
37
|
+
|
|
38
|
+
def to_v2(self, v1_annotation: dict[str, Any], v1_data: dict[str, Any]) -> dict[str, Any]:
|
|
39
|
+
"""Convert V1 named entity to V2
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
v1_annotation: V1 annotations[] item
|
|
43
|
+
v1_data: V1 annotationsData[] item (same ID)
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
V2 format named entity annotation
|
|
47
|
+
"""
|
|
48
|
+
classification_obj = v1_annotation.get('classification') or {}
|
|
49
|
+
|
|
50
|
+
# V2 data: {ranges, content}
|
|
51
|
+
data = {
|
|
52
|
+
'ranges': v1_data.get('ranges', []),
|
|
53
|
+
'content': v1_data.get('content', ''),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Build V2 attrs
|
|
57
|
+
attrs: list[dict[str, Any]] = []
|
|
58
|
+
|
|
59
|
+
# Add other classification properties to attrs (excluding class)
|
|
60
|
+
for key, value in classification_obj.items():
|
|
61
|
+
if key != 'class':
|
|
62
|
+
attrs.append({'name': key, 'value': value})
|
|
63
|
+
|
|
64
|
+
# Build V2 annotation
|
|
65
|
+
return {
|
|
66
|
+
'id': v1_annotation.get('id', ''),
|
|
67
|
+
'classification': classification_obj.get('class', ''),
|
|
68
|
+
'attrs': attrs,
|
|
69
|
+
'data': data,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
def to_v1(self, v2_annotation: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
73
|
+
"""Convert V2 named entity to V1
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
v2_annotation: V2 annotation object
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
(V1 annotation, V1 annotationData) tuple
|
|
80
|
+
"""
|
|
81
|
+
annotation_id = v2_annotation.get('id', '')
|
|
82
|
+
classification_str = v2_annotation.get('classification', '')
|
|
83
|
+
attrs = v2_annotation.get('attrs', [])
|
|
84
|
+
data = v2_annotation.get('data', {})
|
|
85
|
+
|
|
86
|
+
# Build V1 classification
|
|
87
|
+
classification: dict[str, Any] = {'class': classification_str}
|
|
88
|
+
|
|
89
|
+
# Restore properties from attrs
|
|
90
|
+
for attr in attrs:
|
|
91
|
+
name = attr.get('name', '')
|
|
92
|
+
value = attr.get('value')
|
|
93
|
+
|
|
94
|
+
if not name.startswith(self._INTERNAL_ATTR_PREFIX):
|
|
95
|
+
classification[name] = value
|
|
96
|
+
|
|
97
|
+
# V1 annotation (meta info)
|
|
98
|
+
v1_annotation: dict[str, Any] = {
|
|
99
|
+
'id': annotation_id,
|
|
100
|
+
'tool': self.tool_name,
|
|
101
|
+
'classification': classification,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# V1 annotationData (ranges, content)
|
|
105
|
+
v1_data: dict[str, Any] = {
|
|
106
|
+
'id': annotation_id,
|
|
107
|
+
'ranges': data.get('ranges', []),
|
|
108
|
+
'content': data.get('content', ''),
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return v1_annotation, v1_data
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Polygon Tool Processor
|
|
3
|
+
|
|
4
|
+
Created: 2025-12-12
|
|
5
|
+
|
|
6
|
+
Conversion Rules (see data-model.md 4.2):
|
|
7
|
+
V1 → V2:
|
|
8
|
+
- coordinate [{x, y, id}, ...] → data [[x, y], ...]
|
|
9
|
+
- classification.class → classification
|
|
10
|
+
- classification.{other} → attrs[{name, value}]
|
|
11
|
+
|
|
12
|
+
V2 → V1:
|
|
13
|
+
- data [[x, y], ...] → coordinate [{x, y, id}, ...]
|
|
14
|
+
- classification → classification.class
|
|
15
|
+
- attrs[{name, value}] → classification.{name: value} (excluding special attrs)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from ..utils import generate_random_id
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PolygonProcessor:
|
|
24
|
+
"""Polygon Tool Processor
|
|
25
|
+
|
|
26
|
+
V1 coordinate: [{x, y, id}, ...]
|
|
27
|
+
V2 data: [[x, y], ...]
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
tool_name = 'polygon'
|
|
31
|
+
|
|
32
|
+
# V1 meta fields (not stored in attrs)
|
|
33
|
+
_META_FIELDS = {'isLocked', 'isVisible', 'isValid', 'isDrawCompleted', 'label', 'id', 'tool'}
|
|
34
|
+
|
|
35
|
+
# Special attrs not restored to V1 classification (_ prefix)
|
|
36
|
+
_INTERNAL_ATTR_PREFIX = '_'
|
|
37
|
+
|
|
38
|
+
def to_v2(self, v1_annotation: dict[str, Any], v1_data: dict[str, Any]) -> dict[str, Any]:
|
|
39
|
+
"""Convert V1 polygon to V2
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
v1_annotation: V1 annotations[] item
|
|
43
|
+
v1_data: V1 annotationsData[] item (same ID)
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
V2 format polygon annotation
|
|
47
|
+
"""
|
|
48
|
+
coordinate = v1_data.get('coordinate', [])
|
|
49
|
+
classification_obj = v1_annotation.get('classification') or {}
|
|
50
|
+
|
|
51
|
+
# V2 data: [[x, y], ...] - extract only x, y from coordinate array
|
|
52
|
+
data = []
|
|
53
|
+
for point in coordinate:
|
|
54
|
+
if isinstance(point, dict):
|
|
55
|
+
data.append([point.get('x', 0), point.get('y', 0)])
|
|
56
|
+
|
|
57
|
+
# Build V2 attrs
|
|
58
|
+
attrs: list[dict[str, Any]] = []
|
|
59
|
+
|
|
60
|
+
# Add other classification properties to attrs (excluding class)
|
|
61
|
+
for key, value in classification_obj.items():
|
|
62
|
+
if key != 'class':
|
|
63
|
+
attrs.append({'name': key, 'value': value})
|
|
64
|
+
|
|
65
|
+
# Build V2 annotation
|
|
66
|
+
return {
|
|
67
|
+
'id': v1_annotation.get('id', ''),
|
|
68
|
+
'classification': classification_obj.get('class', ''),
|
|
69
|
+
'attrs': attrs,
|
|
70
|
+
'data': data,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
def to_v1(self, v2_annotation: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
74
|
+
"""Convert V2 polygon to V1
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
v2_annotation: V2 annotation object
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
(V1 annotation, V1 annotationData) tuple
|
|
81
|
+
"""
|
|
82
|
+
annotation_id = v2_annotation.get('id', '')
|
|
83
|
+
classification_str = v2_annotation.get('classification', '')
|
|
84
|
+
attrs = v2_annotation.get('attrs', [])
|
|
85
|
+
data = v2_annotation.get('data', [])
|
|
86
|
+
|
|
87
|
+
# Build V1 coordinate: [[x, y], ...] → [{x, y, id}, ...]
|
|
88
|
+
coordinate: list[dict[str, Any]] = []
|
|
89
|
+
for point in data:
|
|
90
|
+
if isinstance(point, list) and len(point) >= 2:
|
|
91
|
+
coordinate.append({
|
|
92
|
+
'x': point[0],
|
|
93
|
+
'y': point[1],
|
|
94
|
+
'id': generate_random_id(),
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
# Build V1 classification
|
|
98
|
+
classification: dict[str, Any] = {'class': classification_str}
|
|
99
|
+
|
|
100
|
+
# Restore properties from attrs
|
|
101
|
+
for attr in attrs:
|
|
102
|
+
name = attr.get('name', '')
|
|
103
|
+
value = attr.get('value')
|
|
104
|
+
|
|
105
|
+
if not name.startswith(self._INTERNAL_ATTR_PREFIX):
|
|
106
|
+
# Add non-internal attrs to classification
|
|
107
|
+
classification[name] = value
|
|
108
|
+
|
|
109
|
+
# V1 annotation (meta info)
|
|
110
|
+
v1_annotation: dict[str, Any] = {
|
|
111
|
+
'id': annotation_id,
|
|
112
|
+
'tool': self.tool_name,
|
|
113
|
+
'classification': classification,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# V1 annotationData (coordinate info)
|
|
117
|
+
v1_data: dict[str, Any] = {
|
|
118
|
+
'id': annotation_id,
|
|
119
|
+
'coordinate': coordinate,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return v1_annotation, v1_data
|