synapse-sdk 1.0.0a11__py3-none-any.whl → 2026.1.1b2__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.

Potentially problematic release.


This version of synapse-sdk might be problematic. Click here for more details.

Files changed (261) hide show
  1. synapse_sdk/__init__.py +24 -0
  2. synapse_sdk/cli/__init__.py +9 -8
  3. synapse_sdk/cli/agent/__init__.py +25 -0
  4. synapse_sdk/cli/agent/config.py +104 -0
  5. synapse_sdk/cli/agent/select.py +197 -0
  6. synapse_sdk/cli/auth.py +104 -0
  7. synapse_sdk/cli/main.py +1025 -0
  8. synapse_sdk/cli/plugin/__init__.py +58 -0
  9. synapse_sdk/cli/plugin/create.py +566 -0
  10. synapse_sdk/cli/plugin/job.py +196 -0
  11. synapse_sdk/cli/plugin/publish.py +322 -0
  12. synapse_sdk/cli/plugin/run.py +131 -0
  13. synapse_sdk/cli/plugin/test.py +200 -0
  14. synapse_sdk/clients/README.md +239 -0
  15. synapse_sdk/clients/__init__.py +5 -0
  16. synapse_sdk/clients/_template.py +266 -0
  17. synapse_sdk/clients/agent/__init__.py +84 -29
  18. synapse_sdk/clients/agent/async_ray.py +289 -0
  19. synapse_sdk/clients/agent/container.py +83 -0
  20. synapse_sdk/clients/agent/plugin.py +101 -0
  21. synapse_sdk/clients/agent/ray.py +296 -39
  22. synapse_sdk/clients/backend/__init__.py +152 -12
  23. synapse_sdk/clients/backend/annotation.py +164 -22
  24. synapse_sdk/clients/backend/core.py +101 -0
  25. synapse_sdk/clients/backend/data_collection.py +292 -0
  26. synapse_sdk/clients/backend/hitl.py +87 -0
  27. synapse_sdk/clients/backend/integration.py +374 -46
  28. synapse_sdk/clients/backend/ml.py +134 -22
  29. synapse_sdk/clients/backend/models.py +247 -0
  30. synapse_sdk/clients/base.py +538 -59
  31. synapse_sdk/clients/exceptions.py +35 -7
  32. synapse_sdk/clients/pipeline/__init__.py +5 -0
  33. synapse_sdk/clients/pipeline/client.py +636 -0
  34. synapse_sdk/clients/protocols.py +178 -0
  35. synapse_sdk/clients/utils.py +86 -8
  36. synapse_sdk/clients/validation.py +58 -0
  37. synapse_sdk/enums.py +76 -0
  38. synapse_sdk/exceptions.py +168 -0
  39. synapse_sdk/integrations/__init__.py +74 -0
  40. synapse_sdk/integrations/_base.py +119 -0
  41. synapse_sdk/integrations/_context.py +53 -0
  42. synapse_sdk/integrations/ultralytics/__init__.py +78 -0
  43. synapse_sdk/integrations/ultralytics/_callbacks.py +126 -0
  44. synapse_sdk/integrations/ultralytics/_patches.py +124 -0
  45. synapse_sdk/loggers.py +476 -95
  46. synapse_sdk/mcp/MCP.md +69 -0
  47. synapse_sdk/mcp/__init__.py +48 -0
  48. synapse_sdk/mcp/__main__.py +6 -0
  49. synapse_sdk/mcp/config.py +349 -0
  50. synapse_sdk/mcp/prompts/__init__.py +4 -0
  51. synapse_sdk/mcp/resources/__init__.py +4 -0
  52. synapse_sdk/mcp/server.py +1352 -0
  53. synapse_sdk/mcp/tools/__init__.py +6 -0
  54. synapse_sdk/plugins/__init__.py +133 -9
  55. synapse_sdk/plugins/action.py +229 -0
  56. synapse_sdk/plugins/actions/__init__.py +82 -0
  57. synapse_sdk/plugins/actions/dataset/__init__.py +37 -0
  58. synapse_sdk/plugins/actions/dataset/action.py +471 -0
  59. synapse_sdk/plugins/actions/export/__init__.py +55 -0
  60. synapse_sdk/plugins/actions/export/action.py +183 -0
  61. synapse_sdk/plugins/actions/export/context.py +59 -0
  62. synapse_sdk/plugins/actions/inference/__init__.py +84 -0
  63. synapse_sdk/plugins/actions/inference/action.py +285 -0
  64. synapse_sdk/plugins/actions/inference/context.py +81 -0
  65. synapse_sdk/plugins/actions/inference/deployment.py +322 -0
  66. synapse_sdk/plugins/actions/inference/serve.py +252 -0
  67. synapse_sdk/plugins/actions/train/__init__.py +54 -0
  68. synapse_sdk/plugins/actions/train/action.py +326 -0
  69. synapse_sdk/plugins/actions/train/context.py +57 -0
  70. synapse_sdk/plugins/actions/upload/__init__.py +49 -0
  71. synapse_sdk/plugins/actions/upload/action.py +165 -0
  72. synapse_sdk/plugins/actions/upload/context.py +61 -0
  73. synapse_sdk/plugins/config.py +98 -0
  74. synapse_sdk/plugins/context/__init__.py +109 -0
  75. synapse_sdk/plugins/context/env.py +113 -0
  76. synapse_sdk/plugins/datasets/__init__.py +113 -0
  77. synapse_sdk/plugins/datasets/converters/__init__.py +76 -0
  78. synapse_sdk/plugins/datasets/converters/base.py +347 -0
  79. synapse_sdk/plugins/datasets/converters/yolo/__init__.py +9 -0
  80. synapse_sdk/plugins/datasets/converters/yolo/from_dm.py +468 -0
  81. synapse_sdk/plugins/datasets/converters/yolo/to_dm.py +381 -0
  82. synapse_sdk/plugins/datasets/formats/__init__.py +82 -0
  83. synapse_sdk/plugins/datasets/formats/dm.py +351 -0
  84. synapse_sdk/plugins/datasets/formats/yolo.py +240 -0
  85. synapse_sdk/plugins/decorators.py +83 -0
  86. synapse_sdk/plugins/discovery.py +790 -0
  87. synapse_sdk/plugins/docs/ACTION_DEV_GUIDE.md +933 -0
  88. synapse_sdk/plugins/docs/ARCHITECTURE.md +1225 -0
  89. synapse_sdk/plugins/docs/LOGGING_SYSTEM.md +683 -0
  90. synapse_sdk/plugins/docs/OVERVIEW.md +531 -0
  91. synapse_sdk/plugins/docs/PIPELINE_GUIDE.md +145 -0
  92. synapse_sdk/plugins/docs/README.md +513 -0
  93. synapse_sdk/plugins/docs/STEP.md +656 -0
  94. synapse_sdk/plugins/enums.py +70 -10
  95. synapse_sdk/plugins/errors.py +92 -0
  96. synapse_sdk/plugins/executors/__init__.py +43 -0
  97. synapse_sdk/plugins/executors/local.py +99 -0
  98. synapse_sdk/plugins/executors/ray/__init__.py +18 -0
  99. synapse_sdk/plugins/executors/ray/base.py +282 -0
  100. synapse_sdk/plugins/executors/ray/job.py +298 -0
  101. synapse_sdk/plugins/executors/ray/jobs_api.py +511 -0
  102. synapse_sdk/plugins/executors/ray/packaging.py +137 -0
  103. synapse_sdk/plugins/executors/ray/pipeline.py +792 -0
  104. synapse_sdk/plugins/executors/ray/task.py +257 -0
  105. synapse_sdk/plugins/models/__init__.py +26 -0
  106. synapse_sdk/plugins/models/logger.py +173 -0
  107. synapse_sdk/plugins/models/pipeline.py +25 -0
  108. synapse_sdk/plugins/pipelines/__init__.py +81 -0
  109. synapse_sdk/plugins/pipelines/action_pipeline.py +417 -0
  110. synapse_sdk/plugins/pipelines/context.py +107 -0
  111. synapse_sdk/plugins/pipelines/display.py +311 -0
  112. synapse_sdk/plugins/runner.py +114 -0
  113. synapse_sdk/plugins/schemas/__init__.py +19 -0
  114. synapse_sdk/plugins/schemas/results.py +152 -0
  115. synapse_sdk/plugins/steps/__init__.py +63 -0
  116. synapse_sdk/plugins/steps/base.py +128 -0
  117. synapse_sdk/plugins/steps/context.py +90 -0
  118. synapse_sdk/plugins/steps/orchestrator.py +128 -0
  119. synapse_sdk/plugins/steps/registry.py +103 -0
  120. synapse_sdk/plugins/steps/utils/__init__.py +20 -0
  121. synapse_sdk/plugins/steps/utils/logging.py +85 -0
  122. synapse_sdk/plugins/steps/utils/timing.py +71 -0
  123. synapse_sdk/plugins/steps/utils/validation.py +68 -0
  124. synapse_sdk/plugins/templates/__init__.py +50 -0
  125. synapse_sdk/plugins/templates/base/.gitignore.j2 +26 -0
  126. synapse_sdk/plugins/templates/base/.synapseignore.j2 +11 -0
  127. synapse_sdk/plugins/templates/base/README.md.j2 +26 -0
  128. synapse_sdk/plugins/templates/base/plugin/__init__.py.j2 +1 -0
  129. synapse_sdk/plugins/templates/base/pyproject.toml.j2 +14 -0
  130. synapse_sdk/plugins/templates/base/requirements.txt.j2 +1 -0
  131. synapse_sdk/plugins/templates/custom/plugin/main.py.j2 +18 -0
  132. synapse_sdk/plugins/templates/data_validation/plugin/validate.py.j2 +32 -0
  133. synapse_sdk/plugins/templates/export/plugin/export.py.j2 +36 -0
  134. synapse_sdk/plugins/templates/neural_net/plugin/inference.py.j2 +36 -0
  135. synapse_sdk/plugins/templates/neural_net/plugin/train.py.j2 +33 -0
  136. synapse_sdk/plugins/templates/post_annotation/plugin/post_annotate.py.j2 +32 -0
  137. synapse_sdk/plugins/templates/pre_annotation/plugin/pre_annotate.py.j2 +32 -0
  138. synapse_sdk/plugins/templates/smart_tool/plugin/auto_label.py.j2 +44 -0
  139. synapse_sdk/plugins/templates/upload/plugin/upload.py.j2 +35 -0
  140. synapse_sdk/plugins/testing/__init__.py +25 -0
  141. synapse_sdk/plugins/testing/sample_actions.py +98 -0
  142. synapse_sdk/plugins/types.py +206 -0
  143. synapse_sdk/plugins/upload.py +595 -64
  144. synapse_sdk/plugins/utils.py +325 -37
  145. synapse_sdk/shared/__init__.py +25 -0
  146. synapse_sdk/utils/__init__.py +1 -0
  147. synapse_sdk/utils/auth.py +74 -0
  148. synapse_sdk/utils/file/__init__.py +58 -0
  149. synapse_sdk/utils/file/archive.py +449 -0
  150. synapse_sdk/utils/file/checksum.py +167 -0
  151. synapse_sdk/utils/file/download.py +286 -0
  152. synapse_sdk/utils/file/io.py +129 -0
  153. synapse_sdk/utils/file/requirements.py +36 -0
  154. synapse_sdk/utils/network.py +168 -0
  155. synapse_sdk/utils/storage/__init__.py +238 -0
  156. synapse_sdk/utils/storage/config.py +188 -0
  157. synapse_sdk/utils/storage/errors.py +52 -0
  158. synapse_sdk/utils/storage/providers/__init__.py +13 -0
  159. synapse_sdk/utils/storage/providers/base.py +76 -0
  160. synapse_sdk/utils/storage/providers/gcs.py +168 -0
  161. synapse_sdk/utils/storage/providers/http.py +250 -0
  162. synapse_sdk/utils/storage/providers/local.py +126 -0
  163. synapse_sdk/utils/storage/providers/s3.py +177 -0
  164. synapse_sdk/utils/storage/providers/sftp.py +208 -0
  165. synapse_sdk/utils/storage/registry.py +125 -0
  166. synapse_sdk/utils/websocket.py +99 -0
  167. synapse_sdk-2026.1.1b2.dist-info/METADATA +715 -0
  168. synapse_sdk-2026.1.1b2.dist-info/RECORD +172 -0
  169. {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/WHEEL +1 -1
  170. synapse_sdk-2026.1.1b2.dist-info/licenses/LICENSE +201 -0
  171. locale/en/LC_MESSAGES/messages.mo +0 -0
  172. locale/en/LC_MESSAGES/messages.po +0 -39
  173. locale/ko/LC_MESSAGES/messages.mo +0 -0
  174. locale/ko/LC_MESSAGES/messages.po +0 -34
  175. synapse_sdk/cli/create_plugin.py +0 -10
  176. synapse_sdk/clients/agent/core.py +0 -7
  177. synapse_sdk/clients/agent/service.py +0 -15
  178. synapse_sdk/clients/backend/dataset.py +0 -51
  179. synapse_sdk/clients/ray/__init__.py +0 -6
  180. synapse_sdk/clients/ray/core.py +0 -22
  181. synapse_sdk/clients/ray/serve.py +0 -20
  182. synapse_sdk/i18n.py +0 -35
  183. synapse_sdk/plugins/categories/__init__.py +0 -0
  184. synapse_sdk/plugins/categories/base.py +0 -235
  185. synapse_sdk/plugins/categories/data_validation/__init__.py +0 -0
  186. synapse_sdk/plugins/categories/data_validation/actions/__init__.py +0 -0
  187. synapse_sdk/plugins/categories/data_validation/actions/validation.py +0 -10
  188. synapse_sdk/plugins/categories/data_validation/templates/config.yaml +0 -3
  189. synapse_sdk/plugins/categories/data_validation/templates/plugin/__init__.py +0 -0
  190. synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +0 -5
  191. synapse_sdk/plugins/categories/decorators.py +0 -13
  192. synapse_sdk/plugins/categories/export/__init__.py +0 -0
  193. synapse_sdk/plugins/categories/export/actions/__init__.py +0 -0
  194. synapse_sdk/plugins/categories/export/actions/export.py +0 -10
  195. synapse_sdk/plugins/categories/import/__init__.py +0 -0
  196. synapse_sdk/plugins/categories/import/actions/__init__.py +0 -0
  197. synapse_sdk/plugins/categories/import/actions/import.py +0 -10
  198. synapse_sdk/plugins/categories/neural_net/__init__.py +0 -0
  199. synapse_sdk/plugins/categories/neural_net/actions/__init__.py +0 -0
  200. synapse_sdk/plugins/categories/neural_net/actions/deployment.py +0 -45
  201. synapse_sdk/plugins/categories/neural_net/actions/inference.py +0 -18
  202. synapse_sdk/plugins/categories/neural_net/actions/test.py +0 -10
  203. synapse_sdk/plugins/categories/neural_net/actions/train.py +0 -143
  204. synapse_sdk/plugins/categories/neural_net/templates/config.yaml +0 -12
  205. synapse_sdk/plugins/categories/neural_net/templates/plugin/__init__.py +0 -0
  206. synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +0 -4
  207. synapse_sdk/plugins/categories/neural_net/templates/plugin/test.py +0 -2
  208. synapse_sdk/plugins/categories/neural_net/templates/plugin/train.py +0 -14
  209. synapse_sdk/plugins/categories/post_annotation/__init__.py +0 -0
  210. synapse_sdk/plugins/categories/post_annotation/actions/__init__.py +0 -0
  211. synapse_sdk/plugins/categories/post_annotation/actions/post_annotation.py +0 -10
  212. synapse_sdk/plugins/categories/post_annotation/templates/config.yaml +0 -3
  213. synapse_sdk/plugins/categories/post_annotation/templates/plugin/__init__.py +0 -0
  214. synapse_sdk/plugins/categories/post_annotation/templates/plugin/post_annotation.py +0 -3
  215. synapse_sdk/plugins/categories/pre_annotation/__init__.py +0 -0
  216. synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +0 -0
  217. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation.py +0 -10
  218. synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +0 -3
  219. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/__init__.py +0 -0
  220. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/pre_annotation.py +0 -3
  221. synapse_sdk/plugins/categories/registry.py +0 -16
  222. synapse_sdk/plugins/categories/smart_tool/__init__.py +0 -0
  223. synapse_sdk/plugins/categories/smart_tool/actions/__init__.py +0 -0
  224. synapse_sdk/plugins/categories/smart_tool/actions/auto_label.py +0 -37
  225. synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +0 -7
  226. synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py +0 -0
  227. synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py +0 -11
  228. synapse_sdk/plugins/categories/templates.py +0 -32
  229. synapse_sdk/plugins/cli/__init__.py +0 -21
  230. synapse_sdk/plugins/cli/publish.py +0 -37
  231. synapse_sdk/plugins/cli/run.py +0 -67
  232. synapse_sdk/plugins/exceptions.py +0 -22
  233. synapse_sdk/plugins/models.py +0 -121
  234. synapse_sdk/plugins/templates/cookiecutter.json +0 -11
  235. synapse_sdk/plugins/templates/hooks/post_gen_project.py +0 -3
  236. synapse_sdk/plugins/templates/hooks/pre_prompt.py +0 -21
  237. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
  238. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
  239. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.gitignore +0 -27
  240. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.pre-commit-config.yaml +0 -7
  241. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/README.md +0 -5
  242. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +0 -6
  243. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
  244. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/plugin/__init__.py +0 -0
  245. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/pyproject.toml +0 -13
  246. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +0 -1
  247. synapse_sdk/shared/enums.py +0 -8
  248. synapse_sdk/utils/debug.py +0 -5
  249. synapse_sdk/utils/file.py +0 -87
  250. synapse_sdk/utils/module_loading.py +0 -29
  251. synapse_sdk/utils/pydantic/__init__.py +0 -0
  252. synapse_sdk/utils/pydantic/config.py +0 -4
  253. synapse_sdk/utils/pydantic/errors.py +0 -33
  254. synapse_sdk/utils/pydantic/validators.py +0 -7
  255. synapse_sdk/utils/storage.py +0 -91
  256. synapse_sdk/utils/string.py +0 -11
  257. synapse_sdk-1.0.0a11.dist-info/LICENSE +0 -21
  258. synapse_sdk-1.0.0a11.dist-info/METADATA +0 -43
  259. synapse_sdk-1.0.0a11.dist-info/RECORD +0 -111
  260. {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/entry_points.txt +0 -0
  261. {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,381 @@
1
+ """Convert YOLO format to Datamaker format.
2
+
3
+ Supports:
4
+ - DMv1 and DMv2 output schemas
5
+ - Bounding box, polygon (segmentation), and keypoint annotations
6
+ - Categorized (train/valid/test splits) and non-categorized datasets
7
+ - Single file conversion mode
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path
13
+ from typing import IO, Any
14
+
15
+ import yaml
16
+
17
+ from synapse_sdk.plugins.datasets.converters.base import DatasetFormat, ToDMConverter
18
+ from synapse_sdk.plugins.datasets.formats.dm import DMVersion
19
+
20
+
21
+ class YOLOToDMConverter(ToDMConverter):
22
+ """Convert YOLO dataset format to Datamaker format.
23
+
24
+ Supports bounding boxes, polygons (YOLO segmentation), and keypoints.
25
+ Outputs either DMv1 or DMv2 schema.
26
+
27
+ Example:
28
+ >>> # Directory conversion
29
+ >>> converter = YOLOToDMConverter(
30
+ ... root_dir='/data/yolo_dataset',
31
+ ... is_categorized=True,
32
+ ... dm_version=DMVersion.V2,
33
+ ... )
34
+ >>> converter.convert()
35
+ >>> converter.save_to_folder('/data/dm_output')
36
+
37
+ >>> # Single file conversion
38
+ >>> converter = YOLOToDMConverter(
39
+ ... is_single_conversion=True,
40
+ ... class_names=['person', 'car'],
41
+ ... )
42
+ >>> result = converter.convert_single_file(label_lines, image_file)
43
+ """
44
+
45
+ source_format = DatasetFormat.YOLO
46
+
47
+ def __init__(
48
+ self,
49
+ root_dir: str | Path | None = None,
50
+ is_categorized: bool = False,
51
+ is_single_conversion: bool = False,
52
+ dm_version: DMVersion = DMVersion.V2,
53
+ class_names: list[str] | None = None,
54
+ ) -> None:
55
+ """Initialize converter.
56
+
57
+ Args:
58
+ root_dir: Root directory containing YOLO data.
59
+ is_categorized: Whether dataset has train/valid/test splits.
60
+ is_single_conversion: Whether converting single files only.
61
+ dm_version: Target Datamaker schema version (V1 or V2).
62
+ class_names: Class names. If not provided, loaded from dataset.yaml.
63
+ """
64
+ super().__init__(root_dir, is_categorized, is_single_conversion, dm_version)
65
+ self.class_names = class_names or []
66
+
67
+ # Load class names from dataset.yaml if not provided and not single conversion
68
+ if not class_names and not is_single_conversion and root_dir:
69
+ self._load_class_names()
70
+
71
+ def _load_class_names(self) -> None:
72
+ """Load class names from dataset.yaml."""
73
+ yaml_path = self.root_dir / 'dataset.yaml'
74
+ if yaml_path.exists():
75
+ with open(yaml_path, encoding='utf-8') as f:
76
+ config = yaml.safe_load(f)
77
+ self.class_names = config.get('names', [])
78
+ else:
79
+ # Try classes.txt as fallback
80
+ classes_path = self.root_dir / 'classes.txt'
81
+ if classes_path.exists():
82
+ self.class_names = [line.strip() for line in classes_path.read_text().splitlines() if line.strip()]
83
+
84
+ if not self.class_names:
85
+ raise FileNotFoundError(
86
+ f'No dataset.yaml or classes.txt found in {self.root_dir}. Provide class_names parameter.'
87
+ )
88
+
89
+ def _find_image_dir(self, split_dir: Path) -> Path | None:
90
+ """Find the images directory within a split."""
91
+ for candidate in ['images', 'img', 'imgs']:
92
+ candidate_path = split_dir / candidate
93
+ if candidate_path.is_dir():
94
+ return candidate_path
95
+ return None
96
+
97
+ def _parse_yolo_line(
98
+ self,
99
+ line: str,
100
+ img_width: int,
101
+ img_height: int,
102
+ ) -> dict[str, Any] | None:
103
+ """Parse a single YOLO label line.
104
+
105
+ Detects format based on number of values:
106
+ - 5 values: bounding box (class cx cy w h)
107
+ - Even values > 5: polygon/segmentation (class x1 y1 x2 y2 ...)
108
+ - 5 + 3*n values: keypoints (class cx cy w h x1 y1 v1 x2 y2 v2 ...)
109
+
110
+ Args:
111
+ line: YOLO label line.
112
+ img_width: Image width in pixels.
113
+ img_height: Image height in pixels.
114
+
115
+ Returns:
116
+ Parsed annotation dict or None if invalid.
117
+ """
118
+ parts = line.strip().split()
119
+ if len(parts) < 5:
120
+ return None
121
+
122
+ class_idx = int(parts[0])
123
+ class_name = self.class_names[class_idx] if class_idx < len(self.class_names) else f'class_{class_idx}'
124
+
125
+ num_coords = len(parts) - 1
126
+
127
+ # Check if polygon: more than 4 values and even number of coordinates
128
+ if num_coords > 4 and num_coords % 2 == 0:
129
+ # Polygon format: class_id x1 y1 x2 y2 x3 y3 ...
130
+ coords = []
131
+ for i in range(1, len(parts), 2):
132
+ x_norm = float(parts[i])
133
+ y_norm = float(parts[i + 1])
134
+ x_abs = x_norm * img_width
135
+ y_abs = y_norm * img_height
136
+ coords.append([x_abs, y_abs])
137
+
138
+ return {
139
+ 'type': 'polygon',
140
+ 'classification': class_name,
141
+ 'data': coords,
142
+ }
143
+
144
+ # Check if keypoints: 4 bbox values + 3*n keypoint values
145
+ if num_coords > 4 and (num_coords - 4) % 3 == 0:
146
+ # Keypoint format: class cx cy w h x1 y1 v1 x2 y2 v2 ...
147
+ cx, cy, w, h = map(float, parts[1:5])
148
+
149
+ # Denormalize bounding box
150
+ abs_w = w * img_width
151
+ abs_h = h * img_height
152
+ left = (cx - w / 2) * img_width
153
+ top = (cy - h / 2) * img_height
154
+
155
+ # Parse keypoints
156
+ keypoints = []
157
+ for i in range(5, len(parts), 3):
158
+ xk = float(parts[i]) * img_width
159
+ yk = float(parts[i + 1]) * img_height
160
+ vk = int(parts[i + 2])
161
+ keypoints.append([xk, yk, vk])
162
+
163
+ return {
164
+ 'type': 'keypoint',
165
+ 'classification': class_name,
166
+ 'data': keypoints,
167
+ 'bounding_box': [left, top, abs_w, abs_h],
168
+ }
169
+
170
+ # Standard bounding box: 5 values
171
+ if num_coords == 4:
172
+ cx, cy, w, h = map(float, parts[1:5])
173
+
174
+ # Denormalize: YOLO (cx, cy, w, h) -> (left, top, w, h)
175
+ abs_w = w * img_width
176
+ abs_h = h * img_height
177
+ left = (cx - w / 2) * img_width
178
+ top = (cy - h / 2) * img_height
179
+
180
+ return {
181
+ 'type': 'bounding_box',
182
+ 'classification': class_name,
183
+ 'data': [left, top, abs_w, abs_h],
184
+ }
185
+
186
+ return None
187
+
188
+ def _convert_yolo_split_to_dm(self, split_dir: Path) -> dict[str, tuple[dict, Path]]:
189
+ """Convert a YOLO split directory to DM format.
190
+
191
+ Args:
192
+ split_dir: Directory containing images/ and labels/.
193
+
194
+ Returns:
195
+ Dict mapping image filename to (dm_json, image_path).
196
+ """
197
+ images_dir = self._find_image_dir(split_dir)
198
+ if not images_dir:
199
+ raise FileNotFoundError(f"No images directory found in {split_dir}. Expected 'images', 'img', or 'imgs'.")
200
+
201
+ labels_dir = split_dir / 'labels'
202
+ if not labels_dir.is_dir():
203
+ raise FileNotFoundError(f"No 'labels' directory found in {split_dir}.")
204
+
205
+ result: dict[str, tuple[dict, Path]] = {}
206
+
207
+ for label_file in labels_dir.glob('*.txt'):
208
+ base = label_file.stem
209
+
210
+ # Find corresponding image
211
+ img_path = self.find_image_for_label(base, images_dir)
212
+ if not img_path:
213
+ print(f'[WARNING] Image not found for {label_file.name}, skipping.')
214
+ continue
215
+
216
+ img_width, img_height = self.get_image_size(img_path)
217
+
218
+ # Parse label file
219
+ label_lines = [line.strip() for line in label_file.read_text().splitlines() if line.strip()]
220
+
221
+ # Build DM annotation structure
222
+ dm_json = self._build_dm_json(label_lines, img_width, img_height)
223
+ result[img_path.name] = (dm_json, img_path)
224
+
225
+ return result
226
+
227
+ def _build_dm_json(
228
+ self,
229
+ label_lines: list[str],
230
+ img_width: int,
231
+ img_height: int,
232
+ ) -> dict[str, Any]:
233
+ """Build DM JSON from YOLO label lines.
234
+
235
+ Args:
236
+ label_lines: List of YOLO label lines.
237
+ img_width: Image width.
238
+ img_height: Image height.
239
+
240
+ Returns:
241
+ DM format JSON dict.
242
+ """
243
+ if self.dm_version == DMVersion.V2:
244
+ return self._build_dm_v2_json(label_lines, img_width, img_height)
245
+ return self._build_dm_v1_json(label_lines, img_width, img_height)
246
+
247
+ def _build_dm_v2_json(
248
+ self,
249
+ label_lines: list[str],
250
+ img_width: int,
251
+ img_height: int,
252
+ ) -> dict[str, Any]:
253
+ """Build DMv2 JSON from YOLO label lines."""
254
+ dm_img: dict[str, list] = {
255
+ 'bounding_box': [],
256
+ 'polygon': [],
257
+ 'keypoint': [],
258
+ 'polyline': [],
259
+ 'relation': [],
260
+ 'group': [],
261
+ }
262
+
263
+ for line in label_lines:
264
+ ann = self._parse_yolo_line(line, img_width, img_height)
265
+ if not ann:
266
+ continue
267
+
268
+ ann_type = ann['type']
269
+ base_ann = {
270
+ 'id': self._generate_unique_id(),
271
+ 'classification': ann['classification'],
272
+ 'attrs': [],
273
+ 'data': ann['data'],
274
+ }
275
+
276
+ if ann_type == 'bounding_box':
277
+ dm_img['bounding_box'].append(base_ann)
278
+ elif ann_type == 'polygon':
279
+ dm_img['polygon'].append(base_ann)
280
+ elif ann_type == 'keypoint':
281
+ base_ann['bounding_box'] = ann['bounding_box']
282
+ dm_img['keypoint'].append(base_ann)
283
+
284
+ return {
285
+ 'classification': {'bounding_box': self.class_names},
286
+ 'images': [dm_img],
287
+ }
288
+
289
+ def _build_dm_v1_json(
290
+ self,
291
+ label_lines: list[str],
292
+ img_width: int,
293
+ img_height: int,
294
+ ) -> dict[str, Any]:
295
+ """Build DMv1 JSON from YOLO label lines."""
296
+ annotations = []
297
+
298
+ for line in label_lines:
299
+ ann = self._parse_yolo_line(line, img_width, img_height)
300
+ if not ann:
301
+ continue
302
+
303
+ base_ann = {
304
+ 'id': self._generate_unique_id(),
305
+ 'tool': ann['type'] if ann['type'] != 'bounding_box' else 'boundingBox',
306
+ 'isLocked': False,
307
+ 'isVisible': True,
308
+ 'classification': {'class': ann['classification']},
309
+ 'data': ann['data'],
310
+ }
311
+
312
+ if ann['type'] == 'keypoint':
313
+ base_ann['bounding_box'] = ann['bounding_box']
314
+
315
+ annotations.append(base_ann)
316
+
317
+ return {'annotations': {'image': annotations}}
318
+
319
+ def convert(self) -> dict[str, dict] | dict[str, tuple]:
320
+ """Convert YOLO dataset to DM format.
321
+
322
+ Returns:
323
+ If categorized: dict mapping split names to image dicts.
324
+ If not categorized: dict mapping image filename to (dm_json, path).
325
+ """
326
+ if self.is_categorized:
327
+ splits = self._validate_splits(['train', 'valid'], ['test'])
328
+ result = {}
329
+ for split, split_dir in splits.items():
330
+ result[split] = self._convert_yolo_split_to_dm(split_dir)
331
+ self.converted_data = result
332
+ else:
333
+ # For non-categorized YOLO, expect images/ and labels/ in root
334
+ result = self._convert_yolo_split_to_dm(self.root_dir)
335
+ self.converted_data = result
336
+
337
+ return self.converted_data
338
+
339
+ def convert_single_file(
340
+ self,
341
+ data: list[str],
342
+ original_file: IO,
343
+ class_names: list[str] | None = None,
344
+ ) -> dict[str, Any]:
345
+ """Convert a single YOLO label and image to DM format.
346
+
347
+ Args:
348
+ data: List of YOLO label lines (from .txt file).
349
+ original_file: Image file object.
350
+ class_names: Optional class names override.
351
+
352
+ Returns:
353
+ Dictionary with dm_json, image_path, image_name.
354
+ """
355
+ if not self.is_single_conversion:
356
+ raise RuntimeError('convert_single_file only available when is_single_conversion=True')
357
+
358
+ if class_names:
359
+ self.class_names = class_names
360
+
361
+ if not self.class_names:
362
+ raise ValueError('class_names must be provided for single file conversion')
363
+
364
+ img_path = getattr(original_file, 'name', None)
365
+ if not img_path:
366
+ raise ValueError('original_file must have a "name" attribute.')
367
+
368
+ img_width, img_height = self.get_image_size(original_file)
369
+
370
+ # Parse label lines
371
+ label_lines = [line.strip() for line in data if line.strip()]
372
+ dm_json = self._build_dm_json(label_lines, img_width, img_height)
373
+
374
+ return {
375
+ 'dm_json': dm_json,
376
+ 'image_path': img_path,
377
+ 'image_name': Path(img_path).name,
378
+ }
379
+
380
+
381
+ __all__ = ['YOLOToDMConverter']
@@ -0,0 +1,82 @@
1
+ """Dataset format Pydantic models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from synapse_sdk.plugins.datasets.formats.dm import (
6
+ # Shared
7
+ DMAttribute,
8
+ # Aliases (default to V2)
9
+ DMBoundingBox,
10
+ DMDataset,
11
+ DMGroup,
12
+ DMImageItem,
13
+ DMKeypoint,
14
+ DMPolygon,
15
+ DMPolyline,
16
+ DMRelation,
17
+ # V1 Models
18
+ DMv1AnnotationBase,
19
+ DMv1AnnotationDataItem,
20
+ DMv1AnnotationGroupItem,
21
+ DMv1Classification,
22
+ DMv1Dataset,
23
+ DMv1GroupMemberItem,
24
+ DMv1RelationItem,
25
+ # V2 Models
26
+ DMv2AnnotationBase,
27
+ DMv2BoundingBox,
28
+ DMv2Dataset,
29
+ DMv2Group,
30
+ DMv2ImageItem,
31
+ DMv2Keypoint,
32
+ DMv2Polygon,
33
+ DMv2Polyline,
34
+ DMv2Relation,
35
+ # Version enum
36
+ DMVersion,
37
+ )
38
+ from synapse_sdk.plugins.datasets.formats.yolo import (
39
+ YOLOAnnotation,
40
+ YOLODataset,
41
+ YOLODatasetConfig,
42
+ YOLOImage,
43
+ )
44
+
45
+ __all__ = [
46
+ # DM Version
47
+ 'DMVersion',
48
+ # DM Shared
49
+ 'DMAttribute',
50
+ # DM V1
51
+ 'DMv1AnnotationBase',
52
+ 'DMv1AnnotationDataItem',
53
+ 'DMv1AnnotationGroupItem',
54
+ 'DMv1Classification',
55
+ 'DMv1Dataset',
56
+ 'DMv1GroupMemberItem',
57
+ 'DMv1RelationItem',
58
+ # DM V2
59
+ 'DMv2AnnotationBase',
60
+ 'DMv2BoundingBox',
61
+ 'DMv2Dataset',
62
+ 'DMv2Group',
63
+ 'DMv2ImageItem',
64
+ 'DMv2Keypoint',
65
+ 'DMv2Polygon',
66
+ 'DMv2Polyline',
67
+ 'DMv2Relation',
68
+ # DM Aliases (V2)
69
+ 'DMBoundingBox',
70
+ 'DMDataset',
71
+ 'DMGroup',
72
+ 'DMImageItem',
73
+ 'DMKeypoint',
74
+ 'DMPolygon',
75
+ 'DMPolyline',
76
+ 'DMRelation',
77
+ # YOLO
78
+ 'YOLOAnnotation',
79
+ 'YOLODataset',
80
+ 'YOLODatasetConfig',
81
+ 'YOLOImage',
82
+ ]