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,66 @@
1
+ """Utilities for converting HTTP URLs to Ray GCS URLs."""
2
+
3
+ import tempfile
4
+ from pathlib import Path
5
+
6
+
7
+ def convert_http_to_ray_gcs(http_url: str) -> str:
8
+ """Convert HTTP URL to Ray GCS URL by downloading and uploading to Ray's GCS.
9
+
10
+ Ray's working_dir only accepts certain protocols (gcs://, s3://, gs://, https://).
11
+ When SYNAPSE_PLUGIN_STORAGE is an HTTP URL (Django media server), this function
12
+ converts it to a Ray-compatible gcs:// URL by:
13
+ 1. Downloading the file from HTTP
14
+ 2. Uploading it to Ray's Global Control Store (GCS)
15
+ 3. Returning the content-addressable gcs:// URI
16
+
17
+ Args:
18
+ http_url: HTTP/HTTPS URL to plugin zip file
19
+
20
+ Returns:
21
+ gcs:// URL that Ray can use for working_dir
22
+ Example: "gcs://_ray_pkg_abc123def456.zip"
23
+
24
+ Raises:
25
+ RuntimeError: If Ray is not initialized or not installed
26
+ requests.exceptions.RequestException: If HTTP download fails
27
+
28
+ Note:
29
+ - Ray must be initialized (ray.init()) before calling this function
30
+ - The gcs:// URI is content-addressable (same file = same URI)
31
+ - Ray automatically deduplicates uploads via package_exists()
32
+ """
33
+ try:
34
+ import ray
35
+ except ImportError:
36
+ raise RuntimeError(
37
+ 'Ray is not installed but is required for HTTP → GCS conversion. Install ray with: pip install ray'
38
+ )
39
+
40
+ if not ray.is_initialized():
41
+ raise RuntimeError(
42
+ 'Ray must be initialized before converting HTTP URLs to GCS. '
43
+ 'Call ray.init() before submitting jobs with HTTP storage.'
44
+ )
45
+
46
+ from ray._private.runtime_env.packaging import (
47
+ get_uri_for_package,
48
+ package_exists,
49
+ upload_package_to_gcs,
50
+ )
51
+
52
+ from synapse_sdk.plugins.upload import download_file
53
+
54
+ # Download HTTP file to temporary location
55
+ with tempfile.TemporaryDirectory() as temp_dir:
56
+ local_path = Path(download_file(http_url, temp_dir))
57
+
58
+ # Generate content-addressable gcs:// URI based on file content
59
+ gcs_uri = get_uri_for_package(local_path)
60
+
61
+ # Check if already exists in Ray GCS (deduplication)
62
+ if not package_exists(gcs_uri):
63
+ # Upload to Ray's Global Control Store
64
+ upload_package_to_gcs(gcs_uri, local_path.read_bytes())
65
+
66
+ return gcs_uri
@@ -0,0 +1,25 @@
1
+ import os
2
+
3
+
4
+ def needs_sentry_init():
5
+ return os.getenv('SENTRY_DSN') is not None
6
+
7
+
8
+ def init_sentry():
9
+ import sentry_sdk
10
+ from sentry_sdk.integrations.ray import RayIntegration
11
+
12
+ dsn = os.getenv('SENTRY_DSN')
13
+ if dsn is None:
14
+ return
15
+
16
+ sentry_sdk.init(
17
+ dsn=dsn,
18
+ environment=os.getenv('DEPLOYMENT_TARGET', 'development'),
19
+ integrations=[RayIntegration()],
20
+ send_default_pii=True,
21
+ )
22
+
23
+
24
+ def worker_process_setup_hook(*_, **__):
25
+ init_sentry()
@@ -1,56 +1,57 @@
1
- from abc import ABC, abstractmethod
1
+ """
2
+ DM Schema V1/V2 Bidirectional Converter
2
3
 
3
- from synapse_sdk.shared.enums import SupportedTools
4
+ """
4
5
 
6
+ from typing import Any
5
7
 
6
- class BaseDMConverter(ABC):
7
- """Base class for DM format converters."""
8
+ from .types import (
9
+ AnnotationMeta,
10
+ V2AnnotationData,
11
+ V2ConversionResult,
12
+ )
8
13
 
9
- SUPPORTED_TOOLS = SupportedTools.get_all_values()
10
14
 
11
- def __init__(self, file_type=None):
12
- """Initialize the base converter.
15
+ def convert_v1_to_v2(v1_data: dict[str, Any]) -> V2ConversionResult:
16
+ """Convert DM Schema V1 data to V2 (separated result)
13
17
 
14
- Args:
15
- file_type (str, optional): Type of file being converted (image, video, pcd, text, audio)
16
- """
17
- self.file_type = file_type
18
- self.tool_processors = self._setup_tool_processors()
18
+ Args:
19
+ v1_data: DM Schema V1 format data
19
20
 
20
- def _setup_tool_processors(self):
21
- """Setup tool processor mapping dynamically based on file_type."""
22
- if not self.file_type:
23
- return {}
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
24
27
 
25
- processors = {}
26
- tools = SupportedTools.get_tools_for_file_type(self.file_type)
28
+ converter = DMV1ToV2Converter()
29
+ return converter.convert(v1_data)
27
30
 
28
- for tool in tools:
29
- # For other tools, use generic method names
30
- method_name = f'_convert_{tool.method_name}'
31
31
 
32
- if hasattr(self, method_name):
33
- processors[tool.annotation_tool] = getattr(self, method_name)
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
34
37
 
35
- return processors
38
+ Args:
39
+ v2_data: DM Schema V2 format data
40
+ annotation_meta: Optional V1 top-level structure passed separately
36
41
 
37
- @abstractmethod
38
- def convert(self):
39
- """Convert data from one format to another."""
42
+ Returns:
43
+ DM Schema V1 format data
44
+ """
45
+ from .to_v1 import DMV2ToV1Converter
40
46
 
41
- def _handle_unknown_tool(self, tool_type, item_id=None):
42
- """Handle unknown tool types with consistent warning message."""
43
- warning_msg = f"Warning: Unknown tool type '{tool_type}'"
44
- if item_id:
45
- warning_msg += f' for item {item_id}'
46
- print(warning_msg)
47
+ converter = DMV2ToV1Converter()
48
+ return converter.convert(v2_data, annotation_meta)
47
49
 
48
- def _extract_media_type_info(self, media_id):
49
- """Extract media type information from media ID."""
50
- media_type = media_id.split('_')[0] if '_' in media_id else media_id
51
- media_type_plural = media_type + 's' if not media_type.endswith('s') else media_type
52
- return media_type, media_type_plural
53
50
 
54
- def _singularize_media_type(self, media_type_plural):
55
- """Convert plural media type to singular."""
56
- return media_type_plural.rstrip('s')
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)