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,321 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DM Schema V2 → V1 Converter
|
|
3
|
+
|
|
4
|
+
Created: 2025-12-11
|
|
5
|
+
|
|
6
|
+
V2→V1 conversion:
|
|
7
|
+
- If both annotation_data and annotation_meta exist, complete V1 restoration
|
|
8
|
+
- If only annotation_data exists, convert to V1 using defaults
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from .base import BaseDMConverter
|
|
14
|
+
from .types import (
|
|
15
|
+
MEDIA_TYPE_REVERSE_MAP,
|
|
16
|
+
AnnotationMeta,
|
|
17
|
+
V2ConversionResult,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DMV2ToV1Converter(BaseDMConverter):
|
|
22
|
+
"""Converter from DM Schema V2 to V1
|
|
23
|
+
|
|
24
|
+
V2→V1 conversion:
|
|
25
|
+
- If both annotation_data and annotation_meta exist, complete V1 restoration
|
|
26
|
+
- If only annotation_data exists, convert to V1 using defaults
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
>>> converter = DMV2ToV1Converter()
|
|
30
|
+
>>> # Complete conversion
|
|
31
|
+
>>> v1_data = converter.convert(v2_result)
|
|
32
|
+
>>> # Convert with annotation_data only
|
|
33
|
+
>>> v1_data = converter.convert({"annotation_data": annotation_data})
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def _setup_tool_processors(self) -> None:
|
|
37
|
+
"""Register tool processors"""
|
|
38
|
+
from .tools.bounding_box import BoundingBoxProcessor
|
|
39
|
+
|
|
40
|
+
self.register_processor(BoundingBoxProcessor())
|
|
41
|
+
|
|
42
|
+
# polygon to be added later
|
|
43
|
+
try:
|
|
44
|
+
from .tools.polygon import PolygonProcessor
|
|
45
|
+
|
|
46
|
+
self.register_processor(PolygonProcessor())
|
|
47
|
+
except ImportError:
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
from .tools.polyline import PolylineProcessor
|
|
52
|
+
|
|
53
|
+
self.register_processor(PolylineProcessor())
|
|
54
|
+
except ImportError:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
from .tools.keypoint import KeypointProcessor
|
|
59
|
+
|
|
60
|
+
self.register_processor(KeypointProcessor())
|
|
61
|
+
except ImportError:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
from .tools.bounding_box_3d import BoundingBox3DProcessor
|
|
66
|
+
|
|
67
|
+
self.register_processor(BoundingBox3DProcessor())
|
|
68
|
+
except ImportError:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
from .tools.segmentation import SegmentationProcessor
|
|
73
|
+
|
|
74
|
+
self.register_processor(SegmentationProcessor())
|
|
75
|
+
except ImportError:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
from .tools.named_entity import NamedEntityProcessor
|
|
80
|
+
|
|
81
|
+
self.register_processor(NamedEntityProcessor())
|
|
82
|
+
except ImportError:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
from .tools.segmentation_3d import Segmentation3DProcessor
|
|
87
|
+
|
|
88
|
+
self.register_processor(Segmentation3DProcessor())
|
|
89
|
+
except ImportError:
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
from .tools.classification import ClassificationProcessor
|
|
94
|
+
|
|
95
|
+
self.register_processor(ClassificationProcessor())
|
|
96
|
+
except ImportError:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
from .tools.relation import RelationProcessor
|
|
101
|
+
|
|
102
|
+
self.register_processor(RelationProcessor())
|
|
103
|
+
except ImportError:
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
from .tools.prompt import PromptProcessor
|
|
108
|
+
|
|
109
|
+
self.register_processor(PromptProcessor())
|
|
110
|
+
except ImportError:
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
from .tools.answer import AnswerProcessor
|
|
115
|
+
|
|
116
|
+
self.register_processor(AnswerProcessor())
|
|
117
|
+
except ImportError:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
def convert(
|
|
121
|
+
self,
|
|
122
|
+
v2_data: V2ConversionResult | dict[str, Any],
|
|
123
|
+
annotation_meta: AnnotationMeta | None = None,
|
|
124
|
+
) -> dict[str, Any]:
|
|
125
|
+
"""Convert V2 data to V1 format
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
v2_data: DM Schema V2 format data
|
|
129
|
+
annotation_meta: Optional V1 top-level structure passed separately
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
DM Schema V1 format data
|
|
133
|
+
|
|
134
|
+
Raises:
|
|
135
|
+
ValueError: Missing required fields or invalid format
|
|
136
|
+
"""
|
|
137
|
+
# Extract annotation_data
|
|
138
|
+
if 'annotation_data' in v2_data:
|
|
139
|
+
annotation_data = v2_data['annotation_data']
|
|
140
|
+
# Extract annotation_meta (use from v2_data if present, else use parameter)
|
|
141
|
+
meta = v2_data.get('annotation_meta') or annotation_meta
|
|
142
|
+
else:
|
|
143
|
+
# annotation_data passed directly
|
|
144
|
+
annotation_data = v2_data
|
|
145
|
+
meta = annotation_meta
|
|
146
|
+
|
|
147
|
+
# Input validation
|
|
148
|
+
if not annotation_data:
|
|
149
|
+
raise ValueError("V2 data requires 'annotation_data'")
|
|
150
|
+
|
|
151
|
+
# Build V1 data
|
|
152
|
+
return self._merge_data_and_meta(annotation_data, meta)
|
|
153
|
+
|
|
154
|
+
def _merge_data_and_meta(
|
|
155
|
+
self,
|
|
156
|
+
annotation_data: dict[str, Any],
|
|
157
|
+
annotation_meta: AnnotationMeta | None,
|
|
158
|
+
) -> dict[str, Any]:
|
|
159
|
+
"""Merge annotation_data and annotation_meta to create V1 format
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
annotation_data: V2 common annotation structure
|
|
163
|
+
annotation_meta: V1 top-level structure (restores meta info if present)
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Merged V1 format data
|
|
167
|
+
"""
|
|
168
|
+
annotations: dict[str, list[dict[str, Any]]] = {}
|
|
169
|
+
annotations_data: dict[str, list[dict[str, Any]]] = {}
|
|
170
|
+
|
|
171
|
+
# Process by media type
|
|
172
|
+
media_index_by_type: dict[str, int] = {}
|
|
173
|
+
|
|
174
|
+
for plural_type in ['images', 'videos', 'pcds', 'texts', 'audios', 'prompts']:
|
|
175
|
+
if plural_type not in annotation_data:
|
|
176
|
+
continue
|
|
177
|
+
|
|
178
|
+
singular_type = MEDIA_TYPE_REVERSE_MAP.get(plural_type, plural_type.rstrip('s'))
|
|
179
|
+
media_index_by_type[singular_type] = 0
|
|
180
|
+
|
|
181
|
+
for media_item in annotation_data[plural_type]:
|
|
182
|
+
# Generate media ID
|
|
183
|
+
media_index_by_type[singular_type] += 1
|
|
184
|
+
media_id = f'{singular_type}_{media_index_by_type[singular_type]}'
|
|
185
|
+
|
|
186
|
+
# Convert by tool
|
|
187
|
+
ann_list, data_list = self._convert_media_item(media_item, media_id, annotation_meta)
|
|
188
|
+
|
|
189
|
+
if ann_list:
|
|
190
|
+
annotations[media_id] = ann_list
|
|
191
|
+
if data_list:
|
|
192
|
+
annotations_data[media_id] = data_list
|
|
193
|
+
|
|
194
|
+
# Build V1 result
|
|
195
|
+
result: dict[str, Any] = {
|
|
196
|
+
'annotations': annotations,
|
|
197
|
+
'annotationsData': annotations_data,
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
# Restore additional fields if annotation_meta exists
|
|
201
|
+
if annotation_meta:
|
|
202
|
+
result['extra'] = annotation_meta.get('extra', {})
|
|
203
|
+
result['relations'] = annotation_meta.get('relations', {})
|
|
204
|
+
result['annotationGroups'] = annotation_meta.get('annotationGroups', {})
|
|
205
|
+
result['assignmentId'] = annotation_meta.get('assignmentId')
|
|
206
|
+
else:
|
|
207
|
+
# Default values
|
|
208
|
+
result['extra'] = {}
|
|
209
|
+
result['relations'] = {}
|
|
210
|
+
result['annotationGroups'] = {}
|
|
211
|
+
result['assignmentId'] = None
|
|
212
|
+
|
|
213
|
+
return result
|
|
214
|
+
|
|
215
|
+
def _convert_media_item(
|
|
216
|
+
self,
|
|
217
|
+
media_item: dict[str, Any],
|
|
218
|
+
media_id: str,
|
|
219
|
+
annotation_meta: AnnotationMeta | None,
|
|
220
|
+
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
|
|
221
|
+
"""Convert V2 media item to V1 annotations/annotationsData
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
media_item: V2 media item
|
|
225
|
+
media_id: Media ID to generate
|
|
226
|
+
annotation_meta: V1 top-level structure (for meta info restoration)
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
(V1 annotations list, V1 annotationsData list)
|
|
230
|
+
"""
|
|
231
|
+
annotations: list[dict[str, Any]] = []
|
|
232
|
+
annotations_data: list[dict[str, Any]] = []
|
|
233
|
+
|
|
234
|
+
# Process by tool
|
|
235
|
+
for tool_name, v2_annotations in media_item.items():
|
|
236
|
+
processor = self.get_processor(tool_name)
|
|
237
|
+
if not processor:
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
for v2_ann in v2_annotations:
|
|
241
|
+
# Convert to V1
|
|
242
|
+
v1_ann, v1_data = processor.to_v1(v2_ann)
|
|
243
|
+
|
|
244
|
+
# Restore meta info from annotation_meta
|
|
245
|
+
if annotation_meta:
|
|
246
|
+
v1_ann = self._restore_meta_fields(v1_ann, annotation_meta, v2_ann.get('id', ''), media_id)
|
|
247
|
+
else:
|
|
248
|
+
# Set default values
|
|
249
|
+
v1_ann.setdefault('isLocked', False)
|
|
250
|
+
v1_ann.setdefault('isVisible', True)
|
|
251
|
+
v1_ann.setdefault('isValid', False)
|
|
252
|
+
v1_ann.setdefault('isDrawCompleted', True)
|
|
253
|
+
v1_ann.setdefault('label', [])
|
|
254
|
+
|
|
255
|
+
annotations.append(v1_ann)
|
|
256
|
+
annotations_data.append(v1_data)
|
|
257
|
+
|
|
258
|
+
return annotations, annotations_data
|
|
259
|
+
|
|
260
|
+
def _restore_meta_fields(
|
|
261
|
+
self,
|
|
262
|
+
v1_annotation: dict[str, Any],
|
|
263
|
+
annotation_meta: AnnotationMeta,
|
|
264
|
+
annotation_id: str,
|
|
265
|
+
media_id: str,
|
|
266
|
+
) -> dict[str, Any]:
|
|
267
|
+
"""Restore V1 annotation meta fields from annotation_meta
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
v1_annotation: Base converted V1 annotation
|
|
271
|
+
annotation_meta: V1 top-level structure
|
|
272
|
+
annotation_id: Annotation ID
|
|
273
|
+
media_id: Media ID
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
V1 annotation with restored meta fields
|
|
277
|
+
"""
|
|
278
|
+
# Find annotation in annotation_meta
|
|
279
|
+
meta_annotations = annotation_meta.get('annotations', {})
|
|
280
|
+
|
|
281
|
+
# Try to find by media_id
|
|
282
|
+
source_media_id = None
|
|
283
|
+
for mid in meta_annotations:
|
|
284
|
+
for ann in meta_annotations[mid]:
|
|
285
|
+
if ann.get('id') == annotation_id:
|
|
286
|
+
source_media_id = mid
|
|
287
|
+
break
|
|
288
|
+
if source_media_id:
|
|
289
|
+
break
|
|
290
|
+
|
|
291
|
+
if not source_media_id:
|
|
292
|
+
# Use defaults if not found
|
|
293
|
+
v1_annotation.setdefault('isLocked', False)
|
|
294
|
+
v1_annotation.setdefault('isVisible', True)
|
|
295
|
+
v1_annotation.setdefault('isValid', False)
|
|
296
|
+
v1_annotation.setdefault('isDrawCompleted', True)
|
|
297
|
+
v1_annotation.setdefault('label', [])
|
|
298
|
+
return v1_annotation
|
|
299
|
+
|
|
300
|
+
# Restore meta info from the found annotation
|
|
301
|
+
for meta_ann in meta_annotations[source_media_id]:
|
|
302
|
+
if meta_ann.get('id') == annotation_id:
|
|
303
|
+
# Restore meta fields
|
|
304
|
+
v1_annotation['isLocked'] = meta_ann.get('isLocked', False)
|
|
305
|
+
v1_annotation['isVisible'] = meta_ann.get('isVisible', True)
|
|
306
|
+
v1_annotation['isValid'] = meta_ann.get('isValid', False)
|
|
307
|
+
v1_annotation['isDrawCompleted'] = meta_ann.get('isDrawCompleted', True)
|
|
308
|
+
v1_annotation['label'] = meta_ann.get('label', [])
|
|
309
|
+
|
|
310
|
+
# Merge classification if present in meta
|
|
311
|
+
meta_classification = meta_ann.get('classification')
|
|
312
|
+
if meta_classification:
|
|
313
|
+
# Keep existing class from classification and merge other fields
|
|
314
|
+
current_class = v1_annotation.get('classification', {}).get('class')
|
|
315
|
+
v1_annotation['classification'] = meta_classification.copy()
|
|
316
|
+
if current_class:
|
|
317
|
+
v1_annotation['classification']['class'] = current_class
|
|
318
|
+
|
|
319
|
+
break
|
|
320
|
+
|
|
321
|
+
return v1_annotation
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DM Schema V1/V2 Tool-Specific Processors
|
|
3
|
+
|
|
4
|
+
Created: 2025-12-11
|
|
5
|
+
|
|
6
|
+
Implement and register processors in this module when adding new tool support.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Protocol, runtime_checkable
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@runtime_checkable
|
|
13
|
+
class ToolProcessor(Protocol):
|
|
14
|
+
"""Tool-Specific Conversion Processor Interface
|
|
15
|
+
|
|
16
|
+
Implement this protocol when adding new tool support.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
tool_name: Tool name (e.g., 'bounding_box', 'polygon')
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
>>> class KeypointProcessor:
|
|
23
|
+
... tool_name = "keypoint"
|
|
24
|
+
...
|
|
25
|
+
... def to_v2(self, v1_annotation, v1_data):
|
|
26
|
+
... # V1 → V2 conversion logic
|
|
27
|
+
... return {...}
|
|
28
|
+
...
|
|
29
|
+
... def to_v1(self, v2_annotation):
|
|
30
|
+
... # V2 → V1 conversion logic
|
|
31
|
+
... return ({...}, {...})
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def tool_name(self) -> str:
|
|
36
|
+
"""Tool name (e.g., 'bounding_box', 'polygon')"""
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
def to_v2(self, v1_annotation: dict[str, Any], v1_data: dict[str, Any]) -> dict[str, Any]:
|
|
40
|
+
"""Convert V1 annotation to V2 format
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
v1_annotation: V1 annotations[] item
|
|
44
|
+
v1_data: V1 annotationsData[] item (same ID)
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
V2 format annotation object (id, classification, attrs, data)
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
ValueError: Data cannot be converted
|
|
51
|
+
"""
|
|
52
|
+
...
|
|
53
|
+
|
|
54
|
+
def to_v1(self, v2_annotation: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
55
|
+
"""Convert V2 annotation to V1 format
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
v2_annotation: V2 annotation object
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
(V1 annotation, V1 annotationData) tuple
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
ValueError: Data cannot be converted
|
|
65
|
+
"""
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# =============================================================================
|
|
70
|
+
# Processor Registry
|
|
71
|
+
# =============================================================================
|
|
72
|
+
|
|
73
|
+
# Global registry storing registered processors
|
|
74
|
+
_PROCESSOR_REGISTRY: dict[str, ToolProcessor] = {}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def register_tool_processor(processor: ToolProcessor) -> None:
|
|
78
|
+
"""Register a tool processor to the global registry
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
processor: ToolProcessor implementation
|
|
82
|
+
"""
|
|
83
|
+
_PROCESSOR_REGISTRY[processor.tool_name] = processor
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_tool_processor(tool_name: str) -> ToolProcessor | None:
|
|
87
|
+
"""Get a tool processor from the global registry
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
tool_name: Tool name
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Registered processor or None
|
|
94
|
+
"""
|
|
95
|
+
return _PROCESSOR_REGISTRY.get(tool_name)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_all_processors() -> dict[str, ToolProcessor]:
|
|
99
|
+
"""Return all registered processors
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Tool name → processor dictionary
|
|
103
|
+
"""
|
|
104
|
+
return _PROCESSOR_REGISTRY.copy()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# =============================================================================
|
|
108
|
+
# Processor Import and Auto-Registration
|
|
109
|
+
# =============================================================================
|
|
110
|
+
|
|
111
|
+
# Auto-registered when processor modules are imported
|
|
112
|
+
# Note: Actual processors are imported later to prevent circular imports
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _register_default_processors() -> None:
|
|
116
|
+
"""Register default processors
|
|
117
|
+
|
|
118
|
+
Called on module load to register default processors.
|
|
119
|
+
"""
|
|
120
|
+
try:
|
|
121
|
+
from .bounding_box import BoundingBoxProcessor
|
|
122
|
+
|
|
123
|
+
register_tool_processor(BoundingBoxProcessor())
|
|
124
|
+
except ImportError:
|
|
125
|
+
pass # Not yet implemented
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
from .polygon import PolygonProcessor
|
|
129
|
+
|
|
130
|
+
register_tool_processor(PolygonProcessor())
|
|
131
|
+
except ImportError:
|
|
132
|
+
pass # Not yet implemented
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
from .polyline import PolylineProcessor
|
|
136
|
+
|
|
137
|
+
register_tool_processor(PolylineProcessor())
|
|
138
|
+
except ImportError:
|
|
139
|
+
pass # Not yet implemented
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
from .keypoint import KeypointProcessor
|
|
143
|
+
|
|
144
|
+
register_tool_processor(KeypointProcessor())
|
|
145
|
+
except ImportError:
|
|
146
|
+
pass # Not yet implemented
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
from .bounding_box_3d import BoundingBox3DProcessor
|
|
150
|
+
|
|
151
|
+
register_tool_processor(BoundingBox3DProcessor())
|
|
152
|
+
except ImportError:
|
|
153
|
+
pass # Not yet implemented
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
from .segmentation import SegmentationProcessor
|
|
157
|
+
|
|
158
|
+
register_tool_processor(SegmentationProcessor())
|
|
159
|
+
except ImportError:
|
|
160
|
+
pass # Not yet implemented
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
from .named_entity import NamedEntityProcessor
|
|
164
|
+
|
|
165
|
+
register_tool_processor(NamedEntityProcessor())
|
|
166
|
+
except ImportError:
|
|
167
|
+
pass # Not yet implemented
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
from .segmentation_3d import Segmentation3DProcessor
|
|
171
|
+
|
|
172
|
+
register_tool_processor(Segmentation3DProcessor())
|
|
173
|
+
except ImportError:
|
|
174
|
+
pass # Not yet implemented
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
from .classification import ClassificationProcessor
|
|
178
|
+
|
|
179
|
+
register_tool_processor(ClassificationProcessor())
|
|
180
|
+
except ImportError:
|
|
181
|
+
pass # Not yet implemented
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
from .relation import RelationProcessor
|
|
185
|
+
|
|
186
|
+
register_tool_processor(RelationProcessor())
|
|
187
|
+
except ImportError:
|
|
188
|
+
pass # Not yet implemented
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
from .prompt import PromptProcessor
|
|
192
|
+
|
|
193
|
+
register_tool_processor(PromptProcessor())
|
|
194
|
+
except ImportError:
|
|
195
|
+
pass # Not yet implemented
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
from .answer import AnswerProcessor
|
|
199
|
+
|
|
200
|
+
register_tool_processor(AnswerProcessor())
|
|
201
|
+
except ImportError:
|
|
202
|
+
pass # Not yet implemented
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# Attempt to register default processors on module load
|
|
206
|
+
_register_default_processors()
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
__all__ = [
|
|
210
|
+
'ToolProcessor',
|
|
211
|
+
'register_tool_processor',
|
|
212
|
+
'get_tool_processor',
|
|
213
|
+
'get_all_processors',
|
|
214
|
+
]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Answer Tool Processor
|
|
3
|
+
|
|
4
|
+
Created: 2025-12-12
|
|
5
|
+
|
|
6
|
+
Conversion Rules (see data-model.md 9.11):
|
|
7
|
+
V1 → V2:
|
|
8
|
+
- output → data.output
|
|
9
|
+
- model, displayName, generatedBy, promptAnnotationId → preserved in data
|
|
10
|
+
- classification.class → classification
|
|
11
|
+
- classification.{other} → attrs[{name, value}]
|
|
12
|
+
|
|
13
|
+
V2 → V1:
|
|
14
|
+
- data.output → output
|
|
15
|
+
- Other fields from data → preserved in annotationsData
|
|
16
|
+
- classification → classification.class
|
|
17
|
+
- attrs[{name, value}] → classification.{name: value}
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AnswerProcessor:
|
|
24
|
+
"""Answer Tool Processor
|
|
25
|
+
|
|
26
|
+
V1 annotationsData: {id, tool, model, output: [{type, value, primaryKey, changeHistory}],
|
|
27
|
+
displayName, generatedBy, promptAnnotationId}
|
|
28
|
+
V2 data: {output: [...], model, displayName, generatedBy, promptAnnotationId, timestamp?}
|
|
29
|
+
|
|
30
|
+
Answer annotation data conversion.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
tool_name = 'answer'
|
|
34
|
+
|
|
35
|
+
_META_FIELDS = {'isLocked', 'isVisible', 'isValid', 'isDrawCompleted', 'label', 'id', 'tool'}
|
|
36
|
+
_INTERNAL_ATTR_PREFIX = '_'
|
|
37
|
+
# Fields to copy from annotationsData to V2 data
|
|
38
|
+
_DATA_FIELDS = {'output', 'model', 'displayName', 'generatedBy', 'promptAnnotationId', 'timestamp'}
|
|
39
|
+
|
|
40
|
+
def to_v2(self, v1_annotation: dict[str, Any], v1_data: dict[str, Any]) -> dict[str, Any]:
|
|
41
|
+
"""Convert V1 answer to V2"""
|
|
42
|
+
classification_obj = v1_annotation.get('classification') or {}
|
|
43
|
+
|
|
44
|
+
# Build V2 data (output and other fields)
|
|
45
|
+
data: dict[str, Any] = {}
|
|
46
|
+
for key in self._DATA_FIELDS:
|
|
47
|
+
if key in v1_data:
|
|
48
|
+
data[key] = v1_data[key]
|
|
49
|
+
|
|
50
|
+
# Build V2 attrs (all classification properties excluding class)
|
|
51
|
+
attrs: list[dict[str, Any]] = []
|
|
52
|
+
for key, value in classification_obj.items():
|
|
53
|
+
if key != 'class':
|
|
54
|
+
attrs.append({'name': key, 'value': value})
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
'id': v1_annotation.get('id', ''),
|
|
58
|
+
'classification': classification_obj.get('class', ''),
|
|
59
|
+
'attrs': attrs,
|
|
60
|
+
'data': data,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
def to_v1(self, v2_annotation: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
64
|
+
"""Convert V2 answer to V1"""
|
|
65
|
+
annotation_id = v2_annotation.get('id', '')
|
|
66
|
+
classification_str = v2_annotation.get('classification', '')
|
|
67
|
+
attrs = v2_annotation.get('attrs', [])
|
|
68
|
+
data = v2_annotation.get('data', {})
|
|
69
|
+
|
|
70
|
+
# Build V1 classification
|
|
71
|
+
classification: dict[str, Any] = {'class': classification_str}
|
|
72
|
+
for attr in attrs:
|
|
73
|
+
name = attr.get('name', '')
|
|
74
|
+
value = attr.get('value')
|
|
75
|
+
if not name.startswith(self._INTERNAL_ATTR_PREFIX):
|
|
76
|
+
classification[name] = value
|
|
77
|
+
|
|
78
|
+
v1_annotation: dict[str, Any] = {
|
|
79
|
+
'id': annotation_id,
|
|
80
|
+
'tool': self.tool_name,
|
|
81
|
+
'classification': classification,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# Build V1 annotationsData
|
|
85
|
+
v1_data: dict[str, Any] = {
|
|
86
|
+
'id': annotation_id,
|
|
87
|
+
'tool': self.tool_name,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Copy fields from data
|
|
91
|
+
for key in self._DATA_FIELDS:
|
|
92
|
+
if key in data:
|
|
93
|
+
v1_data[key] = data[key]
|
|
94
|
+
|
|
95
|
+
return v1_annotation, v1_data
|