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,215 @@
1
+ import json
2
+ import os
3
+ from typing import IO, Any, Dict
4
+
5
+ from synapse_sdk.utils.converters import ToDMConverter
6
+
7
+
8
+ class COCOToDMConverter(ToDMConverter):
9
+ """Convert COCO format annotations to DM (Data Manager) format."""
10
+
11
+ def __init__(self, root_dir: str = None, is_categorized_dataset: bool = False, is_single_conversion: bool = False):
12
+ super().__init__(root_dir, is_categorized_dataset, is_single_conversion)
13
+
14
+ def convert(self):
15
+ if self.is_categorized_dataset:
16
+ splits = self._validate_splits(['train', 'valid'], ['test'])
17
+ all_split_data = {}
18
+ for split, split_dir in splits.items():
19
+ annotation_path = os.path.join(split_dir, 'annotations.json')
20
+ if not os.path.exists(annotation_path):
21
+ raise FileNotFoundError(f'annotations.json not found in {split_dir}')
22
+ with open(annotation_path, 'r', encoding='utf-8') as f:
23
+ coco_data = json.load(f)
24
+ split_data = self._convert_coco_ann_to_dm(coco_data, split_dir)
25
+ all_split_data[split] = split_data
26
+ self.converted_data = all_split_data
27
+ return all_split_data
28
+ else:
29
+ annotation_path = os.path.join(self.root_dir, 'annotations.json')
30
+ if not os.path.exists(annotation_path):
31
+ raise FileNotFoundError(f'annotations.json not found in {self.root_dir}')
32
+ with open(annotation_path, 'r', encoding='utf-8') as f:
33
+ coco_data = json.load(f)
34
+ converted_data = self._convert_coco_ann_to_dm(coco_data, self.root_dir)
35
+ self.converted_data = converted_data
36
+ return converted_data
37
+
38
+ def _convert_coco_ann_to_dm(self, coco_data, base_dir):
39
+ """Convert COCO annotations to DM format."""
40
+ dataset_type = coco_data.get('type', 'image') # Default to 'image' if type is not specified
41
+ if dataset_type == 'image':
42
+ return self._process_image_data(coco_data, base_dir)
43
+ else:
44
+ raise ValueError(f'Unsupported dataset type: {dataset_type}')
45
+
46
+ def _process_image_data(self, coco_data, img_base_dir):
47
+ """Process COCO image data and convert to DM format."""
48
+ images = coco_data.get('images', [])
49
+ annotations = coco_data.get('annotations', [])
50
+ categories = coco_data.get('categories', [])
51
+ cat_map = {cat['id']: cat for cat in categories}
52
+
53
+ # Build image_id -> annotation list
54
+ ann_by_img_id = {}
55
+ for ann in annotations:
56
+ img_id = ann['image_id']
57
+ ann_by_img_id.setdefault(img_id, []).append(ann)
58
+
59
+ result = {}
60
+ for img in images:
61
+ img_id = img['id']
62
+ img_filename = img['file_name']
63
+ img_path = os.path.join(img_base_dir, img_filename)
64
+ anns = ann_by_img_id.get(img_id, [])
65
+
66
+ # DM image structure
67
+ dm_img = {
68
+ 'bounding_box': [],
69
+ 'keypoint': [],
70
+ 'relation': [],
71
+ 'group': [],
72
+ }
73
+
74
+ # Handle bounding_box
75
+ bbox_ids = []
76
+ for ann in anns:
77
+ cat = cat_map.get(ann['category_id'], {})
78
+ if 'bbox' in ann and ann['bbox']:
79
+ bbox_id = self._generate_unique_id()
80
+ bbox_ids.append(bbox_id)
81
+ dm_img['bounding_box'].append({
82
+ 'id': bbox_id,
83
+ 'classification': cat.get('name', str(ann['category_id'])),
84
+ 'attrs': ann.get('attrs', []),
85
+ 'data': list(ann['bbox']),
86
+ })
87
+
88
+ # Handle keypoints
89
+ for ann in anns:
90
+ cat = cat_map.get(ann['category_id'], {})
91
+ attrs = ann.get('attrs', [])
92
+ if 'keypoints' in ann and ann['keypoints']:
93
+ kp_names = cat.get('keypoints', [])
94
+ kps = ann['keypoints']
95
+ keypoint_ids = []
96
+ for idx in range(min(len(kps) // 3, len(kp_names))):
97
+ x, y, v = kps[idx * 3 : idx * 3 + 3]
98
+ kp_id = self._generate_unique_id()
99
+ keypoint_ids.append(kp_id)
100
+ dm_img['keypoint'].append({
101
+ 'id': kp_id,
102
+ 'classification': kp_names[idx] if idx < len(kp_names) else f'keypoint_{idx}',
103
+ 'attrs': attrs,
104
+ 'data': [x, y],
105
+ })
106
+ group_ids = bbox_ids + keypoint_ids
107
+ if group_ids:
108
+ dm_img['group'].append({
109
+ 'id': self._generate_unique_id(),
110
+ 'classification': cat.get('name', str(ann['category_id'])),
111
+ 'attrs': attrs,
112
+ 'data': group_ids,
113
+ })
114
+
115
+ dm_json = {'images': [dm_img]}
116
+ result[img_filename] = (dm_json, img_path)
117
+ return result
118
+
119
+ def convert_single_file(self, data: Dict[str, Any], original_file: IO, original_image_name: str) -> Dict[str, Any]:
120
+ """Convert a single COCO annotation data and corresponding image to DM format.
121
+
122
+ Args:
123
+ data: COCO format data dictionary (JSON content)
124
+ original_file: File object for the corresponding original image
125
+ original_image_name: Original image name
126
+
127
+ Returns:
128
+ Dictionary containing DM format data for the single file
129
+ """
130
+ if not self.is_single_conversion:
131
+ raise RuntimeError('convert_single_file is only available when is_single_conversion=True')
132
+
133
+ images = data.get('images', [])
134
+ annotations = data.get('annotations', [])
135
+ categories = data.get('categories', [])
136
+
137
+ if not images:
138
+ raise ValueError('No images found in COCO data')
139
+
140
+ # Get file name from original_file
141
+ img_path = getattr(original_file, 'name', None)
142
+ if not img_path:
143
+ raise ValueError('original_file must have a "name" attribute representing its path or filename.')
144
+ img_basename = os.path.basename(img_path)
145
+
146
+ # Find the matching image info in COCO 'images' section by comparing file name
147
+ # COCO image dicts might use 'file_name', 'filename', or similar
148
+ matched_img = None
149
+ for img in images:
150
+ for key in ['file_name', 'filename', 'name']:
151
+ if key in img and os.path.basename(img[key]) == original_image_name:
152
+ matched_img = img
153
+ break
154
+ if matched_img:
155
+ break
156
+
157
+ if not matched_img:
158
+ raise ValueError(f'No matching image found in COCO data for file: {img_basename}')
159
+
160
+ img_id = matched_img['id']
161
+ cat_map = {cat['id']: cat for cat in categories}
162
+ anns = [ann for ann in annotations if ann['image_id'] == img_id]
163
+
164
+ dm_img = {
165
+ 'bounding_box': [],
166
+ 'keypoint': [],
167
+ 'relation': [],
168
+ 'group': [],
169
+ }
170
+
171
+ bbox_ids = []
172
+ for ann in anns:
173
+ cat = cat_map.get(ann['category_id'], {})
174
+ if 'bbox' in ann and ann['bbox']:
175
+ bbox_id = self._generate_unique_id()
176
+ bbox_ids.append(bbox_id)
177
+ dm_img['bounding_box'].append({
178
+ 'id': bbox_id,
179
+ 'classification': cat.get('name', str(ann['category_id'])),
180
+ 'attrs': ann.get('attrs', []),
181
+ 'data': list(ann['bbox']),
182
+ })
183
+
184
+ for ann in anns:
185
+ cat = cat_map.get(ann['category_id'], {})
186
+ attrs = ann.get('attrs', [])
187
+ if 'keypoints' in ann and ann['keypoints']:
188
+ kp_names = cat.get('keypoints', [])
189
+ kps = ann['keypoints']
190
+ keypoint_ids = []
191
+ for idx in range(min(len(kps) // 3, len(kp_names))):
192
+ x, y, _ = kps[idx * 3 : idx * 3 + 3]
193
+ kp_id = self._generate_unique_id()
194
+ keypoint_ids.append(kp_id)
195
+ dm_img['keypoint'].append({
196
+ 'id': kp_id,
197
+ 'classification': kp_names[idx] if idx < len(kp_names) else f'keypoint_{idx}',
198
+ 'attrs': attrs,
199
+ 'data': [x, y],
200
+ })
201
+ group_ids = bbox_ids + keypoint_ids
202
+ if group_ids:
203
+ dm_img['group'].append({
204
+ 'id': self._generate_unique_id(),
205
+ 'classification': cat.get('name', str(ann['category_id'])),
206
+ 'attrs': attrs,
207
+ 'data': group_ids,
208
+ })
209
+
210
+ dm_json = {'images': [dm_img]}
211
+ return {
212
+ 'dm_json': dm_json,
213
+ 'image_path': img_path,
214
+ 'image_name': img_basename,
215
+ }
@@ -0,0 +1,57 @@
1
+ """
2
+ DM Schema V1/V2 Bidirectional Converter
3
+
4
+ """
5
+
6
+ from typing import Any
7
+
8
+ from .types import (
9
+ AnnotationMeta,
10
+ V2AnnotationData,
11
+ V2ConversionResult,
12
+ )
13
+
14
+
15
+ def convert_v1_to_v2(v1_data: dict[str, Any]) -> V2ConversionResult:
16
+ """Convert DM Schema V1 data to V2 (separated result)
17
+
18
+ Args:
19
+ v1_data: DM Schema V1 format data
20
+
21
+ Returns:
22
+ V2ConversionResult: Separated conversion result
23
+ - annotation_data: V2 common annotation structure
24
+ - annotation_meta: Preserved V1 top-level structure
25
+ """
26
+ from .from_v1 import DMV1ToV2Converter
27
+
28
+ converter = DMV1ToV2Converter()
29
+ return converter.convert(v1_data)
30
+
31
+
32
+ def convert_v2_to_v1(
33
+ v2_data: V2ConversionResult | dict[str, Any],
34
+ annotation_meta: AnnotationMeta | None = None,
35
+ ) -> dict[str, Any]:
36
+ """Convert DM Schema V2 data to V1
37
+
38
+ Args:
39
+ v2_data: DM Schema V2 format data
40
+ annotation_meta: Optional V1 top-level structure passed separately
41
+
42
+ Returns:
43
+ DM Schema V1 format data
44
+ """
45
+ from .to_v1 import DMV2ToV1Converter
46
+
47
+ converter = DMV2ToV1Converter()
48
+ return converter.convert(v2_data, annotation_meta)
49
+
50
+
51
+ __all__ = [
52
+ 'convert_v1_to_v2',
53
+ 'convert_v2_to_v1',
54
+ 'V2ConversionResult',
55
+ 'V2AnnotationData',
56
+ 'AnnotationMeta',
57
+ ]
@@ -0,0 +1,137 @@
1
+ """
2
+ DM Schema V1/V2 Converter Base Class
3
+
4
+ Created: 2025-12-11
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ from .types import MEDIA_TYPE_MAP, SUPPORTED_FILE_TYPES
11
+ from .utils import detect_file_type, extract_media_type_info
12
+
13
+ if TYPE_CHECKING:
14
+ from .tools import ToolProcessor
15
+
16
+
17
+ class BaseDMConverter(ABC):
18
+ """DM Schema Converter Base Class
19
+
20
+ Abstract base class for all DM converters.
21
+
22
+ Attributes:
23
+ file_type: File type to process (None for auto-detection)
24
+ SUPPORTED_FILE_TYPES: Tuple of supported file types
25
+ MEDIA_TYPE_MAP: Media type mapping dictionary
26
+
27
+ Example:
28
+ >>> class MyConverter(BaseDMConverter):
29
+ ... def convert(self, data):
30
+ ... # implementation
31
+ ... pass
32
+ """
33
+
34
+ SUPPORTED_FILE_TYPES = SUPPORTED_FILE_TYPES
35
+ MEDIA_TYPE_MAP = MEDIA_TYPE_MAP
36
+
37
+ def __init__(self, file_type: str | None = None) -> None:
38
+ """
39
+ Args:
40
+ file_type: File type to process (None for auto-detection)
41
+
42
+ Raises:
43
+ ValueError: Unsupported file type
44
+ """
45
+ if file_type is not None and file_type not in self.SUPPORTED_FILE_TYPES:
46
+ raise ValueError(
47
+ f'Unsupported file type: {file_type}. Supported types: {", ".join(self.SUPPORTED_FILE_TYPES)}'
48
+ )
49
+ self.file_type = file_type
50
+ self._tool_processors: dict[str, 'ToolProcessor'] = {}
51
+ self._setup_tool_processors()
52
+
53
+ @abstractmethod
54
+ def _setup_tool_processors(self) -> None:
55
+ """Register tool processors
56
+
57
+ Subclasses implement this to register supported tool processors.
58
+
59
+ Example:
60
+ >>> def _setup_tool_processors(self):
61
+ ... from .tools import BoundingBoxProcessor, PolygonProcessor
62
+ ... self.register_processor(BoundingBoxProcessor())
63
+ ... self.register_processor(PolygonProcessor())
64
+ """
65
+ ...
66
+
67
+ def register_processor(self, processor: 'ToolProcessor') -> None:
68
+ """Register a tool processor
69
+
70
+ Use this method to register processors when adding new tool support.
71
+ Allows extension without modifying existing code (AR-001).
72
+
73
+ Args:
74
+ processor: ToolProcessor implementation
75
+
76
+ Example:
77
+ >>> class KeypointProcessor:
78
+ ... tool_name = "keypoint"
79
+ ... def to_v2(self, v1_annotation, v1_data): ...
80
+ ... def to_v1(self, v2_annotation): ...
81
+ >>> converter.register_processor(KeypointProcessor())
82
+ """
83
+ self._tool_processors[processor.tool_name] = processor
84
+
85
+ def get_processor(self, tool_name: str) -> 'ToolProcessor | None':
86
+ """Get a registered tool processor
87
+
88
+ Args:
89
+ tool_name: Tool name (e.g., 'bounding_box', 'polygon')
90
+
91
+ Returns:
92
+ Registered processor or None
93
+ """
94
+ return self._tool_processors.get(tool_name)
95
+
96
+ @abstractmethod
97
+ def convert(self, data: dict[str, Any]) -> dict[str, Any]:
98
+ """Perform data conversion
99
+
100
+ Args:
101
+ data: Input data (V1 or V2)
102
+
103
+ Returns:
104
+ Converted data (V2 or V1)
105
+
106
+ Raises:
107
+ ValueError: Data cannot be converted
108
+ """
109
+ ...
110
+
111
+ def _detect_file_type(self, data: dict[str, Any], is_v2: bool = False) -> str:
112
+ """Auto-detect file type from data
113
+
114
+ Args:
115
+ data: Input data
116
+ is_v2: Whether the format is V2
117
+
118
+ Returns:
119
+ Detected file type ('image', 'video', etc.)
120
+
121
+ Raises:
122
+ ValueError: Unable to detect file type
123
+ """
124
+ if self.file_type:
125
+ return self.file_type
126
+ return detect_file_type(data, is_v2)
127
+
128
+ def _extract_media_type_info(self, media_id: str) -> tuple[str, str]:
129
+ """Extract type information from media ID
130
+
131
+ Args:
132
+ media_id: Media ID (e.g., 'image_1', 'video_2')
133
+
134
+ Returns:
135
+ (singular, plural) tuple (e.g., ('image', 'images'))
136
+ """
137
+ return extract_media_type_info(media_id)
@@ -0,0 +1,273 @@
1
+ """
2
+ DM Schema V1 → V2 Converter
3
+
4
+ Created: 2025-12-11
5
+
6
+ V1→V2 conversion separates the result into annotation_data and annotation_meta.
7
+ """
8
+
9
+ from typing import Any
10
+
11
+ from .base import BaseDMConverter
12
+ from .types import (
13
+ AnnotationMeta,
14
+ V2AnnotationData,
15
+ V2ConversionResult,
16
+ )
17
+
18
+
19
+ class DMV1ToV2Converter(BaseDMConverter):
20
+ """Converter from DM Schema V1 to V2
21
+
22
+ V1→V2 conversion separates the result into annotation_data and annotation_meta.
23
+
24
+ Example:
25
+ >>> converter = DMV1ToV2Converter()
26
+ >>> result = converter.convert(v1_data)
27
+ >>> annotation_data = result["annotation_data"]
28
+ >>> annotation_meta = result["annotation_meta"]
29
+ """
30
+
31
+ def _setup_tool_processors(self) -> None:
32
+ """Register tool processors"""
33
+ from .tools.bounding_box import BoundingBoxProcessor
34
+
35
+ self.register_processor(BoundingBoxProcessor())
36
+
37
+ # polygon to be added later
38
+ try:
39
+ from .tools.polygon import PolygonProcessor
40
+
41
+ self.register_processor(PolygonProcessor())
42
+ except ImportError:
43
+ pass
44
+
45
+ try:
46
+ from .tools.polyline import PolylineProcessor
47
+
48
+ self.register_processor(PolylineProcessor())
49
+ except ImportError:
50
+ pass
51
+
52
+ try:
53
+ from .tools.keypoint import KeypointProcessor
54
+
55
+ self.register_processor(KeypointProcessor())
56
+ except ImportError:
57
+ pass
58
+
59
+ try:
60
+ from .tools.bounding_box_3d import BoundingBox3DProcessor
61
+
62
+ self.register_processor(BoundingBox3DProcessor())
63
+ except ImportError:
64
+ pass
65
+
66
+ try:
67
+ from .tools.segmentation import SegmentationProcessor
68
+
69
+ self.register_processor(SegmentationProcessor())
70
+ except ImportError:
71
+ pass
72
+
73
+ try:
74
+ from .tools.named_entity import NamedEntityProcessor
75
+
76
+ self.register_processor(NamedEntityProcessor())
77
+ except ImportError:
78
+ pass
79
+
80
+ try:
81
+ from .tools.segmentation_3d import Segmentation3DProcessor
82
+
83
+ self.register_processor(Segmentation3DProcessor())
84
+ except ImportError:
85
+ pass
86
+
87
+ try:
88
+ from .tools.classification import ClassificationProcessor
89
+
90
+ self.register_processor(ClassificationProcessor())
91
+ except ImportError:
92
+ pass
93
+
94
+ try:
95
+ from .tools.relation import RelationProcessor
96
+
97
+ self.register_processor(RelationProcessor())
98
+ except ImportError:
99
+ pass
100
+
101
+ try:
102
+ from .tools.prompt import PromptProcessor
103
+
104
+ self.register_processor(PromptProcessor())
105
+ except ImportError:
106
+ pass
107
+
108
+ try:
109
+ from .tools.answer import AnswerProcessor
110
+
111
+ self.register_processor(AnswerProcessor())
112
+ except ImportError:
113
+ pass
114
+
115
+ def convert(self, v1_data: dict[str, Any]) -> V2ConversionResult:
116
+ """Convert V1 data to V2 format (separated result)
117
+
118
+ Args:
119
+ v1_data: DM Schema V1 format data
120
+
121
+ Returns:
122
+ V2ConversionResult: Separated conversion result
123
+ - annotation_data: V2 common annotation structure
124
+ - annotation_meta: Preserved V1 top-level structure
125
+
126
+ Raises:
127
+ ValueError: Missing required fields or invalid format
128
+ """
129
+ # Input validation
130
+ if 'annotations' not in v1_data:
131
+ raise ValueError("V1 data requires 'annotations' field")
132
+ if 'annotationsData' not in v1_data:
133
+ raise ValueError("V1 data requires 'annotationsData' field")
134
+
135
+ # Create annotation_data
136
+ annotation_data = self._build_annotation_data(v1_data)
137
+
138
+ # Create annotation_meta (preserve V1 top-level structure)
139
+ annotation_meta = self._build_annotation_meta(v1_data)
140
+
141
+ return {
142
+ 'annotation_data': annotation_data,
143
+ 'annotation_meta': annotation_meta,
144
+ }
145
+
146
+ def _build_annotation_data(self, v1_data: dict[str, Any]) -> V2AnnotationData:
147
+ """Create annotation_data (V2 common structure) from V1 data
148
+
149
+ Args:
150
+ v1_data: V1 data
151
+
152
+ Returns:
153
+ V2 common annotation structure
154
+ """
155
+ annotations = v1_data.get('annotations', {})
156
+ annotations_data = v1_data.get('annotationsData', {})
157
+
158
+ # Build classification map
159
+ classification_map = self._build_classification_map(annotations)
160
+
161
+ # Convert annotations by media type
162
+ result: V2AnnotationData = {
163
+ 'classification': classification_map,
164
+ }
165
+
166
+ # Process by media ID
167
+ for media_id, ann_list in annotations.items():
168
+ # Detect media type
169
+ singular_type, plural_type = self._extract_media_type_info(media_id)
170
+
171
+ # Initialize media type array
172
+ if plural_type not in result:
173
+ result[plural_type] = []
174
+
175
+ # Convert media item
176
+ media_item = self._convert_media_item(media_id, ann_list, annotations_data.get(media_id, []))
177
+
178
+ result[plural_type].append(media_item)
179
+
180
+ return result
181
+
182
+ def _build_annotation_meta(self, v1_data: dict[str, Any]) -> AnnotationMeta:
183
+ """Create annotation_meta (V1 top-level structure) from V1 data
184
+
185
+ Args:
186
+ v1_data: Complete V1 data
187
+
188
+ Returns:
189
+ V1 top-level structure (preserved as-is)
190
+ """
191
+ return {
192
+ 'extra': v1_data.get('extra', {}),
193
+ 'annotations': v1_data.get('annotations', {}),
194
+ 'annotationsData': v1_data.get('annotationsData', {}),
195
+ 'relations': v1_data.get('relations', {}),
196
+ 'annotationGroups': v1_data.get('annotationGroups', {}),
197
+ 'assignmentId': v1_data.get('assignmentId'),
198
+ }
199
+
200
+ def _build_classification_map(self, annotations: dict[str, list[dict[str, Any]]]) -> dict[str, list[str]]:
201
+ """Build classification map from annotations
202
+
203
+ Args:
204
+ annotations: V1 annotations data
205
+
206
+ Returns:
207
+ Class label map by tool
208
+ e.g., {"bounding_box": ["person", "car"], "polygon": ["road"]}
209
+ """
210
+ classification_map: dict[str, set[str]] = {}
211
+
212
+ for media_id, ann_list in annotations.items():
213
+ for ann in ann_list:
214
+ tool = ann.get('tool', '')
215
+ classification_obj = ann.get('classification') or {}
216
+ class_label = classification_obj.get('class', '')
217
+
218
+ if tool and class_label:
219
+ if tool not in classification_map:
220
+ classification_map[tool] = set()
221
+ classification_map[tool].add(class_label)
222
+
223
+ # Convert set to list
224
+ return {tool: sorted(list(labels)) for tool, labels in classification_map.items()}
225
+
226
+ def _convert_media_item(
227
+ self,
228
+ media_id: str,
229
+ annotations: list[dict[str, Any]],
230
+ annotations_data: list[dict[str, Any]],
231
+ ) -> dict[str, list[dict[str, Any]]]:
232
+ """Convert annotations for a single media item
233
+
234
+ Args:
235
+ media_id: Media ID
236
+ annotations: V1 annotations for this media
237
+ annotations_data: V1 annotationsData for this media
238
+
239
+ Returns:
240
+ V2 annotations grouped by tool
241
+ """
242
+ # Create ID → annotationData mapping
243
+ data_by_id = {item['id']: item for item in annotations_data if 'id' in item}
244
+
245
+ # Group by tool
246
+ result: dict[str, list[dict[str, Any]]] = {}
247
+
248
+ for ann in annotations:
249
+ ann_id = ann.get('id', '')
250
+ tool = ann.get('tool', '')
251
+
252
+ if not tool:
253
+ continue
254
+
255
+ # Get processor
256
+ processor = self.get_processor(tool)
257
+ if not processor:
258
+ # Raise error for unsupported tool
259
+ supported_tools = list(self._tool_processors.keys())
260
+ raise ValueError(f"Unsupported tool: '{tool}'. Supported tools: {', '.join(sorted(supported_tools))}")
261
+
262
+ # Find annotationData for this ID
263
+ ann_data = data_by_id.get(ann_id, {})
264
+
265
+ # Convert to V2
266
+ v2_annotation = processor.to_v2(ann, ann_data)
267
+
268
+ # Group by tool
269
+ if tool not in result:
270
+ result[tool] = []
271
+ result[tool].append(v2_annotation)
272
+
273
+ return result