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.
Files changed (228) hide show
  1. synapse_sdk/__init__.py +24 -0
  2. synapse_sdk/cli/__init__.py +310 -5
  3. synapse_sdk/cli/alias/__init__.py +22 -0
  4. synapse_sdk/cli/alias/create.py +36 -0
  5. synapse_sdk/cli/alias/dataclass.py +31 -0
  6. synapse_sdk/cli/alias/default.py +16 -0
  7. synapse_sdk/cli/alias/delete.py +15 -0
  8. synapse_sdk/cli/alias/list.py +19 -0
  9. synapse_sdk/cli/alias/read.py +15 -0
  10. synapse_sdk/cli/alias/update.py +17 -0
  11. synapse_sdk/cli/alias/utils.py +61 -0
  12. synapse_sdk/cli/code_server.py +687 -0
  13. synapse_sdk/cli/config.py +440 -0
  14. synapse_sdk/cli/devtools.py +90 -0
  15. synapse_sdk/cli/plugin/__init__.py +33 -0
  16. synapse_sdk/cli/{create_plugin.py → plugin/create.py} +2 -2
  17. synapse_sdk/{plugins/cli → cli/plugin}/publish.py +23 -15
  18. synapse_sdk/clients/agent/__init__.py +9 -3
  19. synapse_sdk/clients/agent/container.py +143 -0
  20. synapse_sdk/clients/agent/core.py +19 -0
  21. synapse_sdk/clients/agent/ray.py +298 -9
  22. synapse_sdk/clients/backend/__init__.py +30 -12
  23. synapse_sdk/clients/backend/annotation.py +13 -5
  24. synapse_sdk/clients/backend/core.py +31 -4
  25. synapse_sdk/clients/backend/data_collection.py +186 -0
  26. synapse_sdk/clients/backend/hitl.py +17 -0
  27. synapse_sdk/clients/backend/integration.py +16 -1
  28. synapse_sdk/clients/backend/ml.py +5 -1
  29. synapse_sdk/clients/backend/models.py +78 -0
  30. synapse_sdk/clients/base.py +384 -41
  31. synapse_sdk/clients/ray/serve.py +2 -0
  32. synapse_sdk/clients/validators/collections.py +31 -0
  33. synapse_sdk/devtools/config.py +94 -0
  34. synapse_sdk/devtools/server.py +41 -0
  35. synapse_sdk/devtools/streamlit_app/__init__.py +5 -0
  36. synapse_sdk/devtools/streamlit_app/app.py +128 -0
  37. synapse_sdk/devtools/streamlit_app/services/__init__.py +11 -0
  38. synapse_sdk/devtools/streamlit_app/services/job_service.py +233 -0
  39. synapse_sdk/devtools/streamlit_app/services/plugin_service.py +236 -0
  40. synapse_sdk/devtools/streamlit_app/services/serve_service.py +95 -0
  41. synapse_sdk/devtools/streamlit_app/ui/__init__.py +15 -0
  42. synapse_sdk/devtools/streamlit_app/ui/config_tab.py +76 -0
  43. synapse_sdk/devtools/streamlit_app/ui/deployment_tab.py +66 -0
  44. synapse_sdk/devtools/streamlit_app/ui/http_tab.py +125 -0
  45. synapse_sdk/devtools/streamlit_app/ui/jobs_tab.py +573 -0
  46. synapse_sdk/devtools/streamlit_app/ui/serve_tab.py +346 -0
  47. synapse_sdk/devtools/streamlit_app/ui/status_bar.py +118 -0
  48. synapse_sdk/devtools/streamlit_app/utils/__init__.py +40 -0
  49. synapse_sdk/devtools/streamlit_app/utils/json_viewer.py +197 -0
  50. synapse_sdk/devtools/streamlit_app/utils/log_formatter.py +38 -0
  51. synapse_sdk/devtools/streamlit_app/utils/styles.py +241 -0
  52. synapse_sdk/devtools/streamlit_app/utils/ui_components.py +289 -0
  53. synapse_sdk/devtools/streamlit_app.py +10 -0
  54. synapse_sdk/loggers.py +120 -9
  55. synapse_sdk/plugins/README.md +1340 -0
  56. synapse_sdk/plugins/__init__.py +0 -13
  57. synapse_sdk/plugins/categories/base.py +117 -11
  58. synapse_sdk/plugins/categories/data_validation/actions/validation.py +72 -0
  59. synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +33 -5
  60. synapse_sdk/plugins/categories/export/actions/__init__.py +3 -0
  61. synapse_sdk/plugins/categories/export/actions/export/__init__.py +28 -0
  62. synapse_sdk/plugins/categories/export/actions/export/action.py +165 -0
  63. synapse_sdk/plugins/categories/export/actions/export/enums.py +113 -0
  64. synapse_sdk/plugins/categories/export/actions/export/exceptions.py +53 -0
  65. synapse_sdk/plugins/categories/export/actions/export/models.py +74 -0
  66. synapse_sdk/plugins/categories/export/actions/export/run.py +195 -0
  67. synapse_sdk/plugins/categories/export/actions/export/utils.py +187 -0
  68. synapse_sdk/plugins/categories/export/templates/config.yaml +21 -0
  69. synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +390 -0
  70. synapse_sdk/plugins/categories/export/templates/plugin/export.py +160 -0
  71. synapse_sdk/plugins/categories/neural_net/actions/deployment.py +13 -12
  72. synapse_sdk/plugins/categories/neural_net/actions/train.py +1134 -31
  73. synapse_sdk/plugins/categories/neural_net/actions/tune.py +534 -0
  74. synapse_sdk/plugins/categories/neural_net/base/inference.py +1 -1
  75. synapse_sdk/plugins/categories/neural_net/templates/config.yaml +32 -4
  76. synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +26 -10
  77. synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
  78. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
  79. synapse_sdk/plugins/categories/{export/actions/export.py → pre_annotation/actions/pre_annotation/action.py} +4 -4
  80. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
  81. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +148 -0
  82. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
  83. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
  84. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
  85. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +100 -0
  86. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +248 -0
  87. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
  88. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
  89. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +265 -0
  90. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
  91. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
  92. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +92 -0
  93. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +243 -0
  94. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
  95. synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +19 -0
  96. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/to_task.py +40 -0
  97. synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +2 -0
  98. synapse_sdk/plugins/categories/upload/__init__.py +0 -0
  99. synapse_sdk/plugins/categories/upload/actions/__init__.py +0 -0
  100. synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +19 -0
  101. synapse_sdk/plugins/categories/upload/actions/upload/action.py +236 -0
  102. synapse_sdk/plugins/categories/upload/actions/upload/context.py +185 -0
  103. synapse_sdk/plugins/categories/upload/actions/upload/enums.py +493 -0
  104. synapse_sdk/plugins/categories/upload/actions/upload/exceptions.py +36 -0
  105. synapse_sdk/plugins/categories/upload/actions/upload/factory.py +138 -0
  106. synapse_sdk/plugins/categories/upload/actions/upload/models.py +214 -0
  107. synapse_sdk/plugins/categories/upload/actions/upload/orchestrator.py +183 -0
  108. synapse_sdk/plugins/categories/upload/actions/upload/registry.py +113 -0
  109. synapse_sdk/plugins/categories/upload/actions/upload/run.py +179 -0
  110. synapse_sdk/plugins/categories/upload/actions/upload/steps/__init__.py +1 -0
  111. synapse_sdk/plugins/categories/upload/actions/upload/steps/base.py +107 -0
  112. synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +62 -0
  113. synapse_sdk/plugins/categories/upload/actions/upload/steps/collection.py +63 -0
  114. synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +91 -0
  115. synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +82 -0
  116. synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +235 -0
  117. synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +201 -0
  118. synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +104 -0
  119. synapse_sdk/plugins/categories/upload/actions/upload/steps/validate.py +71 -0
  120. synapse_sdk/plugins/categories/upload/actions/upload/strategies/__init__.py +1 -0
  121. synapse_sdk/plugins/categories/upload/actions/upload/strategies/base.py +82 -0
  122. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/__init__.py +1 -0
  123. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/batch.py +39 -0
  124. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/single.py +29 -0
  125. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/__init__.py +1 -0
  126. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +300 -0
  127. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +287 -0
  128. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/__init__.py +1 -0
  129. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/excel.py +174 -0
  130. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/none.py +16 -0
  131. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/__init__.py +1 -0
  132. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/sync.py +84 -0
  133. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/__init__.py +1 -0
  134. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +60 -0
  135. synapse_sdk/plugins/categories/upload/actions/upload/utils.py +250 -0
  136. synapse_sdk/plugins/categories/upload/templates/README.md +470 -0
  137. synapse_sdk/plugins/categories/upload/templates/config.yaml +33 -0
  138. synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +310 -0
  139. synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +102 -0
  140. synapse_sdk/plugins/enums.py +3 -1
  141. synapse_sdk/plugins/models.py +148 -11
  142. synapse_sdk/plugins/templates/plugin-config-schema.json +406 -0
  143. synapse_sdk/plugins/templates/schema.json +491 -0
  144. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +1 -0
  145. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +1 -1
  146. synapse_sdk/plugins/utils/__init__.py +46 -0
  147. synapse_sdk/plugins/utils/actions.py +119 -0
  148. synapse_sdk/plugins/utils/config.py +203 -0
  149. synapse_sdk/plugins/{utils.py → utils/legacy.py} +26 -46
  150. synapse_sdk/plugins/utils/ray_gcs.py +66 -0
  151. synapse_sdk/plugins/utils/registry.py +58 -0
  152. synapse_sdk/shared/__init__.py +25 -0
  153. synapse_sdk/shared/enums.py +93 -0
  154. synapse_sdk/types.py +19 -0
  155. synapse_sdk/utils/converters/__init__.py +240 -0
  156. synapse_sdk/utils/converters/coco/__init__.py +0 -0
  157. synapse_sdk/utils/converters/coco/from_dm.py +322 -0
  158. synapse_sdk/utils/converters/coco/to_dm.py +215 -0
  159. synapse_sdk/utils/converters/dm/__init__.py +57 -0
  160. synapse_sdk/utils/converters/dm/base.py +137 -0
  161. synapse_sdk/utils/converters/dm/from_v1.py +273 -0
  162. synapse_sdk/utils/converters/dm/to_v1.py +321 -0
  163. synapse_sdk/utils/converters/dm/tools/__init__.py +214 -0
  164. synapse_sdk/utils/converters/dm/tools/answer.py +95 -0
  165. synapse_sdk/utils/converters/dm/tools/bounding_box.py +132 -0
  166. synapse_sdk/utils/converters/dm/tools/bounding_box_3d.py +121 -0
  167. synapse_sdk/utils/converters/dm/tools/classification.py +75 -0
  168. synapse_sdk/utils/converters/dm/tools/keypoint.py +117 -0
  169. synapse_sdk/utils/converters/dm/tools/named_entity.py +111 -0
  170. synapse_sdk/utils/converters/dm/tools/polygon.py +122 -0
  171. synapse_sdk/utils/converters/dm/tools/polyline.py +124 -0
  172. synapse_sdk/utils/converters/dm/tools/prompt.py +94 -0
  173. synapse_sdk/utils/converters/dm/tools/relation.py +86 -0
  174. synapse_sdk/utils/converters/dm/tools/segmentation.py +141 -0
  175. synapse_sdk/utils/converters/dm/tools/segmentation_3d.py +83 -0
  176. synapse_sdk/utils/converters/dm/types.py +168 -0
  177. synapse_sdk/utils/converters/dm/utils.py +162 -0
  178. synapse_sdk/utils/converters/dm_legacy/__init__.py +56 -0
  179. synapse_sdk/utils/converters/dm_legacy/from_v1.py +627 -0
  180. synapse_sdk/utils/converters/dm_legacy/to_v1.py +367 -0
  181. synapse_sdk/utils/converters/pascal/__init__.py +0 -0
  182. synapse_sdk/utils/converters/pascal/from_dm.py +244 -0
  183. synapse_sdk/utils/converters/pascal/to_dm.py +214 -0
  184. synapse_sdk/utils/converters/yolo/__init__.py +0 -0
  185. synapse_sdk/utils/converters/yolo/from_dm.py +384 -0
  186. synapse_sdk/utils/converters/yolo/to_dm.py +267 -0
  187. synapse_sdk/utils/dataset.py +46 -0
  188. synapse_sdk/utils/encryption.py +158 -0
  189. synapse_sdk/utils/file/__init__.py +58 -0
  190. synapse_sdk/utils/file/archive.py +32 -0
  191. synapse_sdk/utils/file/checksum.py +56 -0
  192. synapse_sdk/utils/file/chunking.py +31 -0
  193. synapse_sdk/utils/file/download.py +385 -0
  194. synapse_sdk/utils/file/encoding.py +40 -0
  195. synapse_sdk/utils/file/io.py +22 -0
  196. synapse_sdk/utils/file/upload.py +165 -0
  197. synapse_sdk/utils/file/video/__init__.py +29 -0
  198. synapse_sdk/utils/file/video/transcode.py +307 -0
  199. synapse_sdk/utils/file.py.backup +301 -0
  200. synapse_sdk/utils/http.py +138 -0
  201. synapse_sdk/utils/network.py +309 -0
  202. synapse_sdk/utils/storage/__init__.py +72 -0
  203. synapse_sdk/utils/storage/providers/__init__.py +183 -0
  204. synapse_sdk/utils/storage/providers/file_system.py +134 -0
  205. synapse_sdk/utils/storage/providers/gcp.py +13 -0
  206. synapse_sdk/utils/storage/providers/http.py +190 -0
  207. synapse_sdk/utils/storage/providers/s3.py +91 -0
  208. synapse_sdk/utils/storage/providers/sftp.py +47 -0
  209. synapse_sdk/utils/storage/registry.py +17 -0
  210. synapse_sdk-2025.12.3.dist-info/METADATA +123 -0
  211. synapse_sdk-2025.12.3.dist-info/RECORD +279 -0
  212. {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/WHEEL +1 -1
  213. synapse_sdk/clients/backend/dataset.py +0 -51
  214. synapse_sdk/plugins/categories/import/actions/import.py +0 -10
  215. synapse_sdk/plugins/cli/__init__.py +0 -21
  216. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
  217. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
  218. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
  219. synapse_sdk/utils/file.py +0 -168
  220. synapse_sdk/utils/storage.py +0 -91
  221. synapse_sdk-1.0.0a23.dist-info/METADATA +0 -44
  222. synapse_sdk-1.0.0a23.dist-info/RECORD +0 -114
  223. /synapse_sdk/{plugins/cli → cli/plugin}/run.py +0 -0
  224. /synapse_sdk/{plugins/categories/import → clients/validators}/__init__.py +0 -0
  225. /synapse_sdk/{plugins/categories/import/actions → devtools}/__init__.py +0 -0
  226. {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/entry_points.txt +0 -0
  227. {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info/licenses}/LICENSE +0 -0
  228. {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