synapse-sdk 1.0.0b5__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 (167) hide show
  1. synapse_sdk/__init__.py +24 -0
  2. synapse_sdk/cli/code_server.py +305 -33
  3. synapse_sdk/clients/agent/__init__.py +2 -1
  4. synapse_sdk/clients/agent/container.py +143 -0
  5. synapse_sdk/clients/agent/ray.py +296 -38
  6. synapse_sdk/clients/backend/annotation.py +1 -1
  7. synapse_sdk/clients/backend/core.py +31 -4
  8. synapse_sdk/clients/backend/data_collection.py +82 -7
  9. synapse_sdk/clients/backend/hitl.py +1 -1
  10. synapse_sdk/clients/backend/ml.py +1 -1
  11. synapse_sdk/clients/base.py +211 -61
  12. synapse_sdk/loggers.py +46 -0
  13. synapse_sdk/plugins/README.md +1340 -0
  14. synapse_sdk/plugins/categories/base.py +59 -9
  15. synapse_sdk/plugins/categories/export/actions/__init__.py +3 -0
  16. synapse_sdk/plugins/categories/export/actions/export/__init__.py +28 -0
  17. synapse_sdk/plugins/categories/export/actions/export/action.py +165 -0
  18. synapse_sdk/plugins/categories/export/actions/export/enums.py +113 -0
  19. synapse_sdk/plugins/categories/export/actions/export/exceptions.py +53 -0
  20. synapse_sdk/plugins/categories/export/actions/export/models.py +74 -0
  21. synapse_sdk/plugins/categories/export/actions/export/run.py +195 -0
  22. synapse_sdk/plugins/categories/export/actions/export/utils.py +187 -0
  23. synapse_sdk/plugins/categories/export/templates/config.yaml +19 -1
  24. synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +390 -0
  25. synapse_sdk/plugins/categories/export/templates/plugin/export.py +153 -177
  26. synapse_sdk/plugins/categories/neural_net/actions/train.py +1130 -32
  27. synapse_sdk/plugins/categories/neural_net/actions/tune.py +157 -4
  28. synapse_sdk/plugins/categories/neural_net/templates/config.yaml +7 -4
  29. synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
  30. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
  31. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/action.py +10 -0
  32. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
  33. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +148 -0
  34. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
  35. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
  36. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
  37. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +100 -0
  38. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +248 -0
  39. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
  40. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
  41. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +265 -0
  42. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
  43. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
  44. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +92 -0
  45. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +243 -0
  46. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
  47. synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +19 -0
  48. synapse_sdk/plugins/categories/upload/actions/upload/action.py +236 -0
  49. synapse_sdk/plugins/categories/upload/actions/upload/context.py +185 -0
  50. synapse_sdk/plugins/categories/upload/actions/upload/enums.py +493 -0
  51. synapse_sdk/plugins/categories/upload/actions/upload/exceptions.py +36 -0
  52. synapse_sdk/plugins/categories/upload/actions/upload/factory.py +138 -0
  53. synapse_sdk/plugins/categories/upload/actions/upload/models.py +214 -0
  54. synapse_sdk/plugins/categories/upload/actions/upload/orchestrator.py +183 -0
  55. synapse_sdk/plugins/categories/upload/actions/upload/registry.py +113 -0
  56. synapse_sdk/plugins/categories/upload/actions/upload/run.py +179 -0
  57. synapse_sdk/plugins/categories/upload/actions/upload/steps/__init__.py +1 -0
  58. synapse_sdk/plugins/categories/upload/actions/upload/steps/base.py +107 -0
  59. synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +62 -0
  60. synapse_sdk/plugins/categories/upload/actions/upload/steps/collection.py +63 -0
  61. synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +91 -0
  62. synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +82 -0
  63. synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +235 -0
  64. synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +201 -0
  65. synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +104 -0
  66. synapse_sdk/plugins/categories/upload/actions/upload/steps/validate.py +71 -0
  67. synapse_sdk/plugins/categories/upload/actions/upload/strategies/__init__.py +1 -0
  68. synapse_sdk/plugins/categories/upload/actions/upload/strategies/base.py +82 -0
  69. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/__init__.py +1 -0
  70. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/batch.py +39 -0
  71. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/single.py +29 -0
  72. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/__init__.py +1 -0
  73. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +300 -0
  74. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +287 -0
  75. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/__init__.py +1 -0
  76. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/excel.py +174 -0
  77. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/none.py +16 -0
  78. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/__init__.py +1 -0
  79. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/sync.py +84 -0
  80. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/__init__.py +1 -0
  81. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +60 -0
  82. synapse_sdk/plugins/categories/upload/actions/upload/utils.py +250 -0
  83. synapse_sdk/plugins/categories/upload/templates/README.md +470 -0
  84. synapse_sdk/plugins/categories/upload/templates/config.yaml +28 -2
  85. synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +310 -0
  86. synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +82 -20
  87. synapse_sdk/plugins/models.py +111 -9
  88. synapse_sdk/plugins/templates/plugin-config-schema.json +7 -0
  89. synapse_sdk/plugins/templates/schema.json +7 -0
  90. synapse_sdk/plugins/utils/__init__.py +3 -0
  91. synapse_sdk/plugins/utils/ray_gcs.py +66 -0
  92. synapse_sdk/shared/__init__.py +25 -0
  93. synapse_sdk/utils/converters/dm/__init__.py +42 -41
  94. synapse_sdk/utils/converters/dm/base.py +137 -0
  95. synapse_sdk/utils/converters/dm/from_v1.py +208 -562
  96. synapse_sdk/utils/converters/dm/to_v1.py +258 -304
  97. synapse_sdk/utils/converters/dm/tools/__init__.py +214 -0
  98. synapse_sdk/utils/converters/dm/tools/answer.py +95 -0
  99. synapse_sdk/utils/converters/dm/tools/bounding_box.py +132 -0
  100. synapse_sdk/utils/converters/dm/tools/bounding_box_3d.py +121 -0
  101. synapse_sdk/utils/converters/dm/tools/classification.py +75 -0
  102. synapse_sdk/utils/converters/dm/tools/keypoint.py +117 -0
  103. synapse_sdk/utils/converters/dm/tools/named_entity.py +111 -0
  104. synapse_sdk/utils/converters/dm/tools/polygon.py +122 -0
  105. synapse_sdk/utils/converters/dm/tools/polyline.py +124 -0
  106. synapse_sdk/utils/converters/dm/tools/prompt.py +94 -0
  107. synapse_sdk/utils/converters/dm/tools/relation.py +86 -0
  108. synapse_sdk/utils/converters/dm/tools/segmentation.py +141 -0
  109. synapse_sdk/utils/converters/dm/tools/segmentation_3d.py +83 -0
  110. synapse_sdk/utils/converters/dm/types.py +168 -0
  111. synapse_sdk/utils/converters/dm/utils.py +162 -0
  112. synapse_sdk/utils/converters/dm_legacy/__init__.py +56 -0
  113. synapse_sdk/utils/converters/dm_legacy/from_v1.py +627 -0
  114. synapse_sdk/utils/converters/dm_legacy/to_v1.py +367 -0
  115. synapse_sdk/utils/file/__init__.py +58 -0
  116. synapse_sdk/utils/file/archive.py +32 -0
  117. synapse_sdk/utils/file/checksum.py +56 -0
  118. synapse_sdk/utils/file/chunking.py +31 -0
  119. synapse_sdk/utils/file/download.py +385 -0
  120. synapse_sdk/utils/file/encoding.py +40 -0
  121. synapse_sdk/utils/file/io.py +22 -0
  122. synapse_sdk/utils/file/upload.py +165 -0
  123. synapse_sdk/utils/file/video/__init__.py +29 -0
  124. synapse_sdk/utils/file/video/transcode.py +307 -0
  125. synapse_sdk/utils/{file.py → file.py.backup} +77 -0
  126. synapse_sdk/utils/network.py +272 -0
  127. synapse_sdk/utils/storage/__init__.py +6 -2
  128. synapse_sdk/utils/storage/providers/file_system.py +6 -0
  129. {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/METADATA +19 -2
  130. {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/RECORD +134 -74
  131. synapse_sdk/devtools/docs/.gitignore +0 -20
  132. synapse_sdk/devtools/docs/README.md +0 -41
  133. synapse_sdk/devtools/docs/blog/2019-05-28-first-blog-post.md +0 -12
  134. synapse_sdk/devtools/docs/blog/2019-05-29-long-blog-post.md +0 -44
  135. synapse_sdk/devtools/docs/blog/2021-08-01-mdx-blog-post.mdx +0 -24
  136. synapse_sdk/devtools/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  137. synapse_sdk/devtools/docs/blog/2021-08-26-welcome/index.md +0 -29
  138. synapse_sdk/devtools/docs/blog/authors.yml +0 -25
  139. synapse_sdk/devtools/docs/blog/tags.yml +0 -19
  140. synapse_sdk/devtools/docs/docusaurus.config.ts +0 -138
  141. synapse_sdk/devtools/docs/package-lock.json +0 -17455
  142. synapse_sdk/devtools/docs/package.json +0 -47
  143. synapse_sdk/devtools/docs/sidebars.ts +0 -44
  144. synapse_sdk/devtools/docs/src/components/HomepageFeatures/index.tsx +0 -71
  145. synapse_sdk/devtools/docs/src/components/HomepageFeatures/styles.module.css +0 -11
  146. synapse_sdk/devtools/docs/src/css/custom.css +0 -30
  147. synapse_sdk/devtools/docs/src/pages/index.module.css +0 -23
  148. synapse_sdk/devtools/docs/src/pages/index.tsx +0 -21
  149. synapse_sdk/devtools/docs/src/pages/markdown-page.md +0 -7
  150. synapse_sdk/devtools/docs/static/.nojekyll +0 -0
  151. synapse_sdk/devtools/docs/static/img/docusaurus-social-card.jpg +0 -0
  152. synapse_sdk/devtools/docs/static/img/docusaurus.png +0 -0
  153. synapse_sdk/devtools/docs/static/img/favicon.ico +0 -0
  154. synapse_sdk/devtools/docs/static/img/logo.png +0 -0
  155. synapse_sdk/devtools/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
  156. synapse_sdk/devtools/docs/static/img/undraw_docusaurus_react.svg +0 -170
  157. synapse_sdk/devtools/docs/static/img/undraw_docusaurus_tree.svg +0 -40
  158. synapse_sdk/devtools/docs/tsconfig.json +0 -8
  159. synapse_sdk/plugins/categories/export/actions/export.py +0 -346
  160. synapse_sdk/plugins/categories/export/enums.py +0 -7
  161. synapse_sdk/plugins/categories/neural_net/actions/gradio.py +0 -151
  162. synapse_sdk/plugins/categories/pre_annotation/actions/to_task.py +0 -943
  163. synapse_sdk/plugins/categories/upload/actions/upload.py +0 -954
  164. {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/WHEEL +0 -0
  165. {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/entry_points.txt +0 -0
  166. {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/licenses/LICENSE +0 -0
  167. {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,117 @@
1
+ """
2
+ Keypoint Tool Processor
3
+
4
+ Created: 2025-12-12
5
+
6
+ Conversion Rules (see data-model.md 9.2):
7
+ V1 → V2:
8
+ - coordinate {x, y} → data [x, y]
9
+ - classification.class → classification
10
+ - classification.{other} → attrs[{name, value}]
11
+
12
+ V2 → V1:
13
+ - data [x, y] → coordinate {x, y}
14
+ - classification → classification.class
15
+ - attrs[{name, value}] → classification.{name: value} (excluding special attrs)
16
+ """
17
+
18
+ from typing import Any
19
+
20
+
21
+ class KeypointProcessor:
22
+ """Keypoint Tool Processor
23
+
24
+ V1 coordinate: {x, y}
25
+ V2 data: [x, y]
26
+
27
+ Simplest single point structure.
28
+ """
29
+
30
+ tool_name = 'keypoint'
31
+
32
+ # V1 meta fields (not stored in attrs)
33
+ _META_FIELDS = {'isLocked', 'isVisible', 'isValid', 'isDrawCompleted', 'label', 'id', 'tool'}
34
+
35
+ # Special attrs not restored to V1 classification (_ prefix)
36
+ _INTERNAL_ATTR_PREFIX = '_'
37
+
38
+ def to_v2(self, v1_annotation: dict[str, Any], v1_data: dict[str, Any]) -> dict[str, Any]:
39
+ """Convert V1 keypoint to V2
40
+
41
+ Args:
42
+ v1_annotation: V1 annotations[] item
43
+ v1_data: V1 annotationsData[] item (same ID)
44
+
45
+ Returns:
46
+ V2 format keypoint annotation
47
+ """
48
+ coordinate = v1_data.get('coordinate', {})
49
+ classification_obj = v1_annotation.get('classification') or {}
50
+
51
+ # V2 data: [x, y] - extract x, y from coordinate object
52
+ data = [coordinate.get('x', 0), coordinate.get('y', 0)]
53
+
54
+ # Build V2 attrs
55
+ attrs: list[dict[str, Any]] = []
56
+
57
+ # Add other classification properties to attrs (excluding class)
58
+ for key, value in classification_obj.items():
59
+ if key != 'class':
60
+ attrs.append({'name': key, 'value': value})
61
+
62
+ # Build V2 annotation
63
+ return {
64
+ 'id': v1_annotation.get('id', ''),
65
+ 'classification': classification_obj.get('class', ''),
66
+ 'attrs': attrs,
67
+ 'data': data,
68
+ }
69
+
70
+ def to_v1(self, v2_annotation: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
71
+ """Convert V2 keypoint to V1
72
+
73
+ Args:
74
+ v2_annotation: V2 annotation object
75
+
76
+ Returns:
77
+ (V1 annotation, V1 annotationData) tuple
78
+ """
79
+ annotation_id = v2_annotation.get('id', '')
80
+ classification_str = v2_annotation.get('classification', '')
81
+ attrs = v2_annotation.get('attrs', [])
82
+ data = v2_annotation.get('data', [])
83
+
84
+ # Build V1 coordinate: [x, y] → {x, y}
85
+ coordinate: dict[str, Any] = {}
86
+ if isinstance(data, list) and len(data) >= 2:
87
+ coordinate = {
88
+ 'x': data[0],
89
+ 'y': data[1],
90
+ }
91
+
92
+ # Build V1 classification
93
+ classification: dict[str, Any] = {'class': classification_str}
94
+
95
+ # Restore properties from attrs
96
+ for attr in attrs:
97
+ name = attr.get('name', '')
98
+ value = attr.get('value')
99
+
100
+ if not name.startswith(self._INTERNAL_ATTR_PREFIX):
101
+ # Add non-internal attrs to classification
102
+ classification[name] = value
103
+
104
+ # V1 annotation (meta info)
105
+ v1_annotation: dict[str, Any] = {
106
+ 'id': annotation_id,
107
+ 'tool': self.tool_name,
108
+ 'classification': classification,
109
+ }
110
+
111
+ # V1 annotationData (coordinate info)
112
+ v1_data: dict[str, Any] = {
113
+ 'id': annotation_id,
114
+ 'coordinate': coordinate,
115
+ }
116
+
117
+ return v1_annotation, v1_data
@@ -0,0 +1,111 @@
1
+ """
2
+ Named Entity Tool Processor
3
+
4
+ Created: 2025-12-12
5
+
6
+ Conversion Rules (see data-model.md 9.5):
7
+ V1 → V2:
8
+ - ranges, content → data {ranges, content}
9
+ - classification.class → classification
10
+ - classification.{other} → attrs[{name, value}]
11
+
12
+ V2 → V1:
13
+ - data {ranges, content} → ranges, content
14
+ - classification → classification.class
15
+ - attrs[{name, value}] → classification.{name: value} (excluding special attrs)
16
+ """
17
+
18
+ from typing import Any
19
+
20
+
21
+ class NamedEntityProcessor:
22
+ """Named Entity Tool Processor
23
+
24
+ V1 annotationData: {ranges: [...], content: str}
25
+ V2 data: {ranges: [...], content: str}
26
+
27
+ Used with text media type.
28
+ """
29
+
30
+ tool_name = 'named_entity'
31
+
32
+ # V1 meta fields (not stored in attrs)
33
+ _META_FIELDS = {'isLocked', 'isVisible', 'isValid', 'isDrawCompleted', 'label', 'id', 'tool'}
34
+
35
+ # Special attrs not restored to V1 classification (_ prefix)
36
+ _INTERNAL_ATTR_PREFIX = '_'
37
+
38
+ def to_v2(self, v1_annotation: dict[str, Any], v1_data: dict[str, Any]) -> dict[str, Any]:
39
+ """Convert V1 named entity to V2
40
+
41
+ Args:
42
+ v1_annotation: V1 annotations[] item
43
+ v1_data: V1 annotationsData[] item (same ID)
44
+
45
+ Returns:
46
+ V2 format named entity annotation
47
+ """
48
+ classification_obj = v1_annotation.get('classification') or {}
49
+
50
+ # V2 data: {ranges, content}
51
+ data = {
52
+ 'ranges': v1_data.get('ranges', []),
53
+ 'content': v1_data.get('content', ''),
54
+ }
55
+
56
+ # Build V2 attrs
57
+ attrs: list[dict[str, Any]] = []
58
+
59
+ # Add other classification properties to attrs (excluding class)
60
+ for key, value in classification_obj.items():
61
+ if key != 'class':
62
+ attrs.append({'name': key, 'value': value})
63
+
64
+ # Build V2 annotation
65
+ return {
66
+ 'id': v1_annotation.get('id', ''),
67
+ 'classification': classification_obj.get('class', ''),
68
+ 'attrs': attrs,
69
+ 'data': data,
70
+ }
71
+
72
+ def to_v1(self, v2_annotation: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
73
+ """Convert V2 named entity to V1
74
+
75
+ Args:
76
+ v2_annotation: V2 annotation object
77
+
78
+ Returns:
79
+ (V1 annotation, V1 annotationData) tuple
80
+ """
81
+ annotation_id = v2_annotation.get('id', '')
82
+ classification_str = v2_annotation.get('classification', '')
83
+ attrs = v2_annotation.get('attrs', [])
84
+ data = v2_annotation.get('data', {})
85
+
86
+ # Build V1 classification
87
+ classification: dict[str, Any] = {'class': classification_str}
88
+
89
+ # Restore properties from attrs
90
+ for attr in attrs:
91
+ name = attr.get('name', '')
92
+ value = attr.get('value')
93
+
94
+ if not name.startswith(self._INTERNAL_ATTR_PREFIX):
95
+ classification[name] = value
96
+
97
+ # V1 annotation (meta info)
98
+ v1_annotation: dict[str, Any] = {
99
+ 'id': annotation_id,
100
+ 'tool': self.tool_name,
101
+ 'classification': classification,
102
+ }
103
+
104
+ # V1 annotationData (ranges, content)
105
+ v1_data: dict[str, Any] = {
106
+ 'id': annotation_id,
107
+ 'ranges': data.get('ranges', []),
108
+ 'content': data.get('content', ''),
109
+ }
110
+
111
+ return v1_annotation, v1_data
@@ -0,0 +1,122 @@
1
+ """
2
+ Polygon Tool Processor
3
+
4
+ Created: 2025-12-12
5
+
6
+ Conversion Rules (see data-model.md 4.2):
7
+ V1 → V2:
8
+ - coordinate [{x, y, id}, ...] → data [[x, y], ...]
9
+ - classification.class → classification
10
+ - classification.{other} → attrs[{name, value}]
11
+
12
+ V2 → V1:
13
+ - data [[x, y], ...] → coordinate [{x, y, id}, ...]
14
+ - classification → classification.class
15
+ - attrs[{name, value}] → classification.{name: value} (excluding special attrs)
16
+ """
17
+
18
+ from typing import Any
19
+
20
+ from ..utils import generate_random_id
21
+
22
+
23
+ class PolygonProcessor:
24
+ """Polygon Tool Processor
25
+
26
+ V1 coordinate: [{x, y, id}, ...]
27
+ V2 data: [[x, y], ...]
28
+ """
29
+
30
+ tool_name = 'polygon'
31
+
32
+ # V1 meta fields (not stored in attrs)
33
+ _META_FIELDS = {'isLocked', 'isVisible', 'isValid', 'isDrawCompleted', 'label', 'id', 'tool'}
34
+
35
+ # Special attrs not restored to V1 classification (_ prefix)
36
+ _INTERNAL_ATTR_PREFIX = '_'
37
+
38
+ def to_v2(self, v1_annotation: dict[str, Any], v1_data: dict[str, Any]) -> dict[str, Any]:
39
+ """Convert V1 polygon to V2
40
+
41
+ Args:
42
+ v1_annotation: V1 annotations[] item
43
+ v1_data: V1 annotationsData[] item (same ID)
44
+
45
+ Returns:
46
+ V2 format polygon annotation
47
+ """
48
+ coordinate = v1_data.get('coordinate', [])
49
+ classification_obj = v1_annotation.get('classification') or {}
50
+
51
+ # V2 data: [[x, y], ...] - extract only x, y from coordinate array
52
+ data = []
53
+ for point in coordinate:
54
+ if isinstance(point, dict):
55
+ data.append([point.get('x', 0), point.get('y', 0)])
56
+
57
+ # Build V2 attrs
58
+ attrs: list[dict[str, Any]] = []
59
+
60
+ # Add other classification properties to attrs (excluding class)
61
+ for key, value in classification_obj.items():
62
+ if key != 'class':
63
+ attrs.append({'name': key, 'value': value})
64
+
65
+ # Build V2 annotation
66
+ return {
67
+ 'id': v1_annotation.get('id', ''),
68
+ 'classification': classification_obj.get('class', ''),
69
+ 'attrs': attrs,
70
+ 'data': data,
71
+ }
72
+
73
+ def to_v1(self, v2_annotation: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
74
+ """Convert V2 polygon to V1
75
+
76
+ Args:
77
+ v2_annotation: V2 annotation object
78
+
79
+ Returns:
80
+ (V1 annotation, V1 annotationData) tuple
81
+ """
82
+ annotation_id = v2_annotation.get('id', '')
83
+ classification_str = v2_annotation.get('classification', '')
84
+ attrs = v2_annotation.get('attrs', [])
85
+ data = v2_annotation.get('data', [])
86
+
87
+ # Build V1 coordinate: [[x, y], ...] → [{x, y, id}, ...]
88
+ coordinate: list[dict[str, Any]] = []
89
+ for point in data:
90
+ if isinstance(point, list) and len(point) >= 2:
91
+ coordinate.append({
92
+ 'x': point[0],
93
+ 'y': point[1],
94
+ 'id': generate_random_id(),
95
+ })
96
+
97
+ # Build V1 classification
98
+ classification: dict[str, Any] = {'class': classification_str}
99
+
100
+ # Restore properties from attrs
101
+ for attr in attrs:
102
+ name = attr.get('name', '')
103
+ value = attr.get('value')
104
+
105
+ if not name.startswith(self._INTERNAL_ATTR_PREFIX):
106
+ # Add non-internal attrs to classification
107
+ classification[name] = value
108
+
109
+ # V1 annotation (meta info)
110
+ v1_annotation: dict[str, Any] = {
111
+ 'id': annotation_id,
112
+ 'tool': self.tool_name,
113
+ 'classification': classification,
114
+ }
115
+
116
+ # V1 annotationData (coordinate info)
117
+ v1_data: dict[str, Any] = {
118
+ 'id': annotation_id,
119
+ 'coordinate': coordinate,
120
+ }
121
+
122
+ return v1_annotation, v1_data
@@ -0,0 +1,124 @@
1
+ """
2
+ Polyline Tool Processor
3
+
4
+ Created: 2025-12-12
5
+
6
+ Conversion Rules (see data-model.md 4.2):
7
+ V1 → V2:
8
+ - coordinate [{x, y, id}, ...] → data [[x, y], ...]
9
+ - classification.class → classification
10
+ - classification.{other} → attrs[{name, value}]
11
+
12
+ V2 → V1:
13
+ - data [[x, y], ...] → coordinate [{x, y, id}, ...]
14
+ - classification → classification.class
15
+ - attrs[{name, value}] → classification.{name: value} (excluding special attrs)
16
+ """
17
+
18
+ from typing import Any
19
+
20
+ from ..utils import generate_random_id
21
+
22
+
23
+ class PolylineProcessor:
24
+ """Polyline Tool Processor
25
+
26
+ V1 coordinate: [{x, y, id}, ...]
27
+ V2 data: [[x, y], ...]
28
+
29
+ Same data structure as polygon, only tool_name differs.
30
+ """
31
+
32
+ tool_name = 'polyline'
33
+
34
+ # V1 meta fields (not stored in attrs)
35
+ _META_FIELDS = {'isLocked', 'isVisible', 'isValid', 'isDrawCompleted', 'label', 'id', 'tool'}
36
+
37
+ # Special attrs not restored to V1 classification (_ prefix)
38
+ _INTERNAL_ATTR_PREFIX = '_'
39
+
40
+ def to_v2(self, v1_annotation: dict[str, Any], v1_data: dict[str, Any]) -> dict[str, Any]:
41
+ """Convert V1 polyline to V2
42
+
43
+ Args:
44
+ v1_annotation: V1 annotations[] item
45
+ v1_data: V1 annotationsData[] item (same ID)
46
+
47
+ Returns:
48
+ V2 format polyline annotation
49
+ """
50
+ coordinate = v1_data.get('coordinate', [])
51
+ classification_obj = v1_annotation.get('classification') or {}
52
+
53
+ # V2 data: [[x, y], ...] - extract only x, y from coordinate array
54
+ data = []
55
+ for point in coordinate:
56
+ if isinstance(point, dict):
57
+ data.append([point.get('x', 0), point.get('y', 0)])
58
+
59
+ # Build V2 attrs
60
+ attrs: list[dict[str, Any]] = []
61
+
62
+ # Add other classification properties to attrs (excluding class)
63
+ for key, value in classification_obj.items():
64
+ if key != 'class':
65
+ attrs.append({'name': key, 'value': value})
66
+
67
+ # Build V2 annotation
68
+ return {
69
+ 'id': v1_annotation.get('id', ''),
70
+ 'classification': classification_obj.get('class', ''),
71
+ 'attrs': attrs,
72
+ 'data': data,
73
+ }
74
+
75
+ def to_v1(self, v2_annotation: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
76
+ """Convert V2 polyline to V1
77
+
78
+ Args:
79
+ v2_annotation: V2 annotation object
80
+
81
+ Returns:
82
+ (V1 annotation, V1 annotationData) tuple
83
+ """
84
+ annotation_id = v2_annotation.get('id', '')
85
+ classification_str = v2_annotation.get('classification', '')
86
+ attrs = v2_annotation.get('attrs', [])
87
+ data = v2_annotation.get('data', [])
88
+
89
+ # Build V1 coordinate: [[x, y], ...] → [{x, y, id}, ...]
90
+ coordinate: list[dict[str, Any]] = []
91
+ for point in data:
92
+ if isinstance(point, list) and len(point) >= 2:
93
+ coordinate.append({
94
+ 'x': point[0],
95
+ 'y': point[1],
96
+ 'id': generate_random_id(),
97
+ })
98
+
99
+ # Build V1 classification
100
+ classification: dict[str, Any] = {'class': classification_str}
101
+
102
+ # Restore properties from attrs
103
+ for attr in attrs:
104
+ name = attr.get('name', '')
105
+ value = attr.get('value')
106
+
107
+ if not name.startswith(self._INTERNAL_ATTR_PREFIX):
108
+ # Add non-internal attrs to classification
109
+ classification[name] = value
110
+
111
+ # V1 annotation (meta info)
112
+ v1_annotation: dict[str, Any] = {
113
+ 'id': annotation_id,
114
+ 'tool': self.tool_name,
115
+ 'classification': classification,
116
+ }
117
+
118
+ # V1 annotationData (coordinate info)
119
+ v1_data: dict[str, Any] = {
120
+ 'id': annotation_id,
121
+ 'coordinate': coordinate,
122
+ }
123
+
124
+ return v1_annotation, v1_data
@@ -0,0 +1,94 @@
1
+ """
2
+ Prompt Tool Processor
3
+
4
+ Created: 2025-12-12
5
+
6
+ Conversion Rules (see data-model.md 9.10):
7
+ V1 → V2:
8
+ - input → data.input
9
+ - Other fields from annotationsData → preserved in data
10
+ - classification.class → classification
11
+ - classification.{other} → attrs[{name, value}]
12
+
13
+ V2 → V1:
14
+ - data.input → input
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 PromptProcessor:
24
+ """Prompt Tool Processor
25
+
26
+ V1 annotationsData: {id, tool, input: [{type, value, changeHistory}]}
27
+ V2 data: {input: [...], model?, displayName?, generatedBy?, timestamp?}
28
+
29
+ Prompt annotation data conversion.
30
+ """
31
+
32
+ tool_name = 'prompt'
33
+
34
+ _META_FIELDS = {'isLocked', 'isVisible', 'isValid', 'isDrawCompleted', 'label', 'id', 'tool'}
35
+ _INTERNAL_ATTR_PREFIX = '_'
36
+ # Fields to copy from annotationsData to V2 data
37
+ _DATA_FIELDS = {'input', 'model', 'displayName', 'generatedBy', 'timestamp'}
38
+
39
+ def to_v2(self, v1_annotation: dict[str, Any], v1_data: dict[str, Any]) -> dict[str, Any]:
40
+ """Convert V1 prompt to V2"""
41
+ classification_obj = v1_annotation.get('classification') or {}
42
+
43
+ # Build V2 data (input and other fields)
44
+ data: dict[str, Any] = {}
45
+ for key in self._DATA_FIELDS:
46
+ if key in v1_data:
47
+ data[key] = v1_data[key]
48
+
49
+ # Build V2 attrs (all classification properties excluding class)
50
+ attrs: list[dict[str, Any]] = []
51
+ for key, value in classification_obj.items():
52
+ if key != 'class':
53
+ attrs.append({'name': key, 'value': value})
54
+
55
+ return {
56
+ 'id': v1_annotation.get('id', ''),
57
+ 'classification': classification_obj.get('class', ''),
58
+ 'attrs': attrs,
59
+ 'data': data,
60
+ }
61
+
62
+ def to_v1(self, v2_annotation: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
63
+ """Convert V2 prompt to V1"""
64
+ annotation_id = v2_annotation.get('id', '')
65
+ classification_str = v2_annotation.get('classification', '')
66
+ attrs = v2_annotation.get('attrs', [])
67
+ data = v2_annotation.get('data', {})
68
+
69
+ # Build V1 classification
70
+ classification: dict[str, Any] = {'class': classification_str}
71
+ for attr in attrs:
72
+ name = attr.get('name', '')
73
+ value = attr.get('value')
74
+ if not name.startswith(self._INTERNAL_ATTR_PREFIX):
75
+ classification[name] = value
76
+
77
+ v1_annotation: dict[str, Any] = {
78
+ 'id': annotation_id,
79
+ 'tool': self.tool_name,
80
+ 'classification': classification,
81
+ }
82
+
83
+ # Build V1 annotationsData
84
+ v1_data: dict[str, Any] = {
85
+ 'id': annotation_id,
86
+ 'tool': self.tool_name,
87
+ }
88
+
89
+ # Copy fields from data
90
+ for key in self._DATA_FIELDS:
91
+ if key in data:
92
+ v1_data[key] = data[key]
93
+
94
+ return v1_annotation, v1_data
@@ -0,0 +1,86 @@
1
+ """
2
+ Relation Tool Processor
3
+
4
+ Created: 2025-12-12
5
+
6
+ Conversion Rules (see data-model.md 9.9):
7
+ V1 → V2:
8
+ - annotationId, targetAnnotationId → data [from_id, to_id]
9
+ - classification.class → classification
10
+ - classification.{other} → attrs[{name, value}]
11
+
12
+ V2 → V1:
13
+ - data [from_id, to_id] → annotationId, targetAnnotationId
14
+ - classification → classification.class
15
+ - attrs[{name, value}] → classification.{name: value}
16
+ """
17
+
18
+ from typing import Any
19
+
20
+
21
+ class RelationProcessor:
22
+ """Relation Tool Processor
23
+
24
+ V1 annotationData: {annotationId, targetAnnotationId}
25
+ V2 data: [from_id, to_id]
26
+
27
+ Represents relationship between two annotations.
28
+ """
29
+
30
+ tool_name = 'relation'
31
+
32
+ _META_FIELDS = {'isLocked', 'isVisible', 'isValid', 'isDrawCompleted', 'label', 'id', 'tool'}
33
+ _INTERNAL_ATTR_PREFIX = '_'
34
+
35
+ def to_v2(self, v1_annotation: dict[str, Any], v1_data: dict[str, Any]) -> dict[str, Any]:
36
+ """Convert V1 relation to V2"""
37
+ classification_obj = v1_annotation.get('classification') or {}
38
+
39
+ # V2 data: [from_id, to_id]
40
+ data = [
41
+ v1_data.get('annotationId', ''),
42
+ v1_data.get('targetAnnotationId', ''),
43
+ ]
44
+
45
+ # Build V2 attrs
46
+ attrs: list[dict[str, Any]] = []
47
+ for key, value in classification_obj.items():
48
+ if key != 'class':
49
+ attrs.append({'name': key, 'value': value})
50
+
51
+ return {
52
+ 'id': v1_annotation.get('id', ''),
53
+ 'classification': classification_obj.get('class', ''),
54
+ 'attrs': attrs,
55
+ 'data': data,
56
+ }
57
+
58
+ def to_v1(self, v2_annotation: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
59
+ """Convert V2 relation to V1"""
60
+ annotation_id = v2_annotation.get('id', '')
61
+ classification_str = v2_annotation.get('classification', '')
62
+ attrs = v2_annotation.get('attrs', [])
63
+ data = v2_annotation.get('data', [])
64
+
65
+ # Build V1 classification
66
+ classification: dict[str, Any] = {'class': classification_str}
67
+ for attr in attrs:
68
+ name = attr.get('name', '')
69
+ value = attr.get('value')
70
+ if not name.startswith(self._INTERNAL_ATTR_PREFIX):
71
+ classification[name] = value
72
+
73
+ v1_annotation: dict[str, Any] = {
74
+ 'id': annotation_id,
75
+ 'tool': self.tool_name,
76
+ 'classification': classification,
77
+ }
78
+
79
+ # V1 annotationData
80
+ v1_data: dict[str, Any] = {
81
+ 'id': annotation_id,
82
+ 'annotationId': data[0] if len(data) > 0 else '',
83
+ 'targetAnnotationId': data[1] if len(data) > 1 else '',
84
+ }
85
+
86
+ return v1_annotation, v1_data