synapse-sdk 1.0.0a74__py3-none-any.whl → 1.0.0a76__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 (27) hide show
  1. synapse_sdk/clients/backend/annotation.py +0 -4
  2. synapse_sdk/devtools/docs/sidebars.ts +9 -1
  3. synapse_sdk/plugins/categories/pre_annotation/actions/to_task.py +208 -32
  4. synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +3 -0
  5. synapse_sdk/plugins/utils/__init__.py +43 -0
  6. synapse_sdk/plugins/utils/actions.py +119 -0
  7. synapse_sdk/plugins/utils/config.py +203 -0
  8. synapse_sdk/plugins/utils/legacy.py +95 -0
  9. synapse_sdk/plugins/utils/registry.py +58 -0
  10. synapse_sdk/plugins/utils.py +27 -0
  11. synapse_sdk/shared/enums.py +1 -0
  12. synapse_sdk/utils/converters/__init__.py +3 -1
  13. synapse_sdk/utils/converters/dm/__init__.py +109 -0
  14. synapse_sdk/utils/converters/dm/from_v1.py +415 -0
  15. synapse_sdk/utils/converters/dm/to_v1.py +254 -0
  16. synapse_sdk/utils/converters/pascal/__init__.py +0 -0
  17. synapse_sdk/utils/converters/pascal/from_dm.py +177 -0
  18. synapse_sdk/utils/converters/pascal/to_dm.py +135 -0
  19. synapse_sdk/utils/converters/yolo/from_dm.py +24 -18
  20. synapse_sdk/utils/converters/yolo/to_dm.py +185 -0
  21. synapse_sdk-1.0.0a76.dist-info/METADATA +107 -0
  22. {synapse_sdk-1.0.0a74.dist-info → synapse_sdk-1.0.0a76.dist-info}/RECORD +26 -14
  23. synapse_sdk-1.0.0a74.dist-info/METADATA +0 -37
  24. {synapse_sdk-1.0.0a74.dist-info → synapse_sdk-1.0.0a76.dist-info}/WHEEL +0 -0
  25. {synapse_sdk-1.0.0a74.dist-info → synapse_sdk-1.0.0a76.dist-info}/entry_points.txt +0 -0
  26. {synapse_sdk-1.0.0a74.dist-info → synapse_sdk-1.0.0a76.dist-info}/licenses/LICENSE +0 -0
  27. {synapse_sdk-1.0.0a74.dist-info → synapse_sdk-1.0.0a76.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,203 @@
1
+ """Plugin configuration utilities."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any, Dict, List, Optional, Union
5
+
6
+ from synapse_sdk.utils.file import get_dict_from_file
7
+
8
+
9
+ def read_plugin_config(plugin_path: Optional[Union[str, Path]] = None) -> Dict[str, Any]:
10
+ """Read plugin configuration from config.yaml file.
11
+
12
+ Args:
13
+ plugin_path: Path to plugin directory. If None, looks for config.yaml in current directory.
14
+
15
+ Returns:
16
+ Dict containing plugin configuration.
17
+
18
+ Raises:
19
+ FileNotFoundError: If config.yaml file is not found.
20
+ ValueError: If config.yaml is invalid.
21
+ """
22
+ config_file_name = 'config.yaml'
23
+ if plugin_path:
24
+ config_path = Path(plugin_path) / config_file_name
25
+ else:
26
+ config_path = config_file_name
27
+
28
+ try:
29
+ return get_dict_from_file(config_path)
30
+ except FileNotFoundError:
31
+ raise FileNotFoundError(f'Plugin config file not found: {config_path}')
32
+ except Exception as e:
33
+ raise ValueError(f'Invalid plugin config file: {e}')
34
+
35
+
36
+ def get_plugin_actions(
37
+ config: Optional[Dict[str, Any]] = None, plugin_path: Optional[Union[str, Path]] = None
38
+ ) -> List[str]:
39
+ """Get list of action names from plugin configuration.
40
+
41
+ Args:
42
+ config: Plugin configuration dictionary. If None, reads from plugin_path.
43
+ plugin_path: Path to plugin directory. Used if config is None.
44
+
45
+ Returns:
46
+ List of action names defined in the plugin.
47
+
48
+ Raises:
49
+ ValueError: If neither config nor plugin_path is provided.
50
+ KeyError: If 'actions' key is missing from config.
51
+
52
+ Examples:
53
+ >>> get_plugin_actions(plugin_path="./my-plugin")
54
+ ['train', 'inference', 'test']
55
+
56
+ >>> config = {'actions': {'train': {...}, 'test': {...}}}
57
+ >>> get_plugin_actions(config=config)
58
+ ['train', 'test']
59
+ """
60
+ if config is None:
61
+ if plugin_path is None:
62
+ raise ValueError('Either config or plugin_path must be provided')
63
+ config = read_plugin_config(plugin_path)
64
+
65
+ if 'actions' not in config:
66
+ raise KeyError("'actions' key not found in plugin configuration")
67
+
68
+ actions = config['actions']
69
+ if not isinstance(actions, dict):
70
+ raise ValueError("'actions' must be a dictionary")
71
+
72
+ return list(actions.keys())
73
+
74
+
75
+ def get_action_config(
76
+ action_name: str, config: Optional[Dict[str, Any]] = None, plugin_path: Optional[Union[str, Path]] = None
77
+ ) -> Dict[str, Any]:
78
+ """Get configuration for a specific action.
79
+
80
+ Args:
81
+ action_name: Name of the action to get config for.
82
+ config: Plugin configuration dictionary. If None, reads from plugin_path.
83
+ plugin_path: Path to plugin directory. Used if config is None.
84
+
85
+ Returns:
86
+ Dictionary containing action configuration.
87
+
88
+ Raises:
89
+ ValueError: If neither config nor plugin_path is provided.
90
+ KeyError: If action is not found in plugin configuration.
91
+
92
+ Examples:
93
+ >>> get_action_config('train', plugin_path="./my-plugin")
94
+ {'entrypoint': 'plugin.train.TrainAction', 'method': 'job'}
95
+ """
96
+ if config is None:
97
+ if plugin_path is None:
98
+ raise ValueError('Either config or plugin_path must be provided')
99
+ config = read_plugin_config(plugin_path)
100
+
101
+ if 'actions' not in config:
102
+ raise KeyError("'actions' key not found in plugin configuration")
103
+
104
+ actions = config['actions']
105
+ if action_name not in actions:
106
+ available_actions = list(actions.keys())
107
+ raise KeyError(f"Action '{action_name}' not found. Available actions: {available_actions}")
108
+
109
+ return actions[action_name]
110
+
111
+
112
+ def validate_plugin_config(config: Dict[str, Any]) -> bool:
113
+ """Validate plugin configuration structure.
114
+
115
+ Args:
116
+ config: Plugin configuration dictionary to validate.
117
+
118
+ Returns:
119
+ True if configuration is valid.
120
+
121
+ Raises:
122
+ ValueError: If configuration is invalid with detailed error message.
123
+
124
+ Examples:
125
+ >>> config = {
126
+ ... 'name': 'My Plugin',
127
+ ... 'code': 'my-plugin',
128
+ ... 'version': '1.0.0',
129
+ ... 'category': 'neural_net',
130
+ ... 'actions': {'train': {'entrypoint': 'plugin.train.TrainAction'}}
131
+ ... }
132
+ >>> validate_plugin_config(config)
133
+ True
134
+ """
135
+ required_fields = ['name', 'code', 'version', 'category', 'actions']
136
+
137
+ # Check required fields
138
+ for field in required_fields:
139
+ if field not in config:
140
+ raise ValueError(f"Required field '{field}' missing from plugin configuration")
141
+
142
+ # Validate actions structure
143
+ actions = config['actions']
144
+ if not isinstance(actions, dict):
145
+ raise ValueError("'actions' must be a dictionary")
146
+
147
+ if not actions:
148
+ raise ValueError('Plugin must define at least one action')
149
+
150
+ # Validate each action
151
+ for action_name, action_config in actions.items():
152
+ if not isinstance(action_config, dict):
153
+ raise ValueError(f"Action '{action_name}' configuration must be a dictionary")
154
+
155
+ # Check for entrypoint (required for most actions)
156
+ if 'entrypoint' not in action_config and action_config.get('method') != 'restapi':
157
+ raise ValueError(f"Action '{action_name}' missing required 'entrypoint' field")
158
+
159
+ # Validate category
160
+ from synapse_sdk.plugins.enums import PluginCategory
161
+
162
+ valid_categories = [cat.value for cat in PluginCategory]
163
+ if config['category'] not in valid_categories:
164
+ raise ValueError(f"Invalid category '{config['category']}'. Must be one of: {valid_categories}")
165
+
166
+ return True
167
+
168
+
169
+ def get_plugin_metadata(
170
+ config: Optional[Dict[str, Any]] = None, plugin_path: Optional[Union[str, Path]] = None
171
+ ) -> Dict[str, Any]:
172
+ """Get plugin metadata (name, version, description, etc.).
173
+
174
+ Args:
175
+ config: Plugin configuration dictionary. If None, reads from plugin_path.
176
+ plugin_path: Path to plugin directory. Used if config is None.
177
+
178
+ Returns:
179
+ Dictionary containing plugin metadata.
180
+
181
+ Examples:
182
+ >>> get_plugin_metadata(plugin_path="./my-plugin")
183
+ {
184
+ 'name': 'My Plugin',
185
+ 'code': 'my-plugin',
186
+ 'version': '1.0.0',
187
+ 'category': 'neural_net',
188
+ 'description': 'A custom ML plugin'
189
+ }
190
+ """
191
+ if config is None:
192
+ if plugin_path is None:
193
+ raise ValueError('Either config or plugin_path must be provided')
194
+ config = read_plugin_config(plugin_path)
195
+
196
+ metadata_fields = ['name', 'code', 'version', 'category', 'description']
197
+ metadata = {}
198
+
199
+ for field in metadata_fields:
200
+ if field in config:
201
+ metadata[field] = config[field]
202
+
203
+ return metadata
@@ -0,0 +1,95 @@
1
+ """Legacy utility functions for backward compatibility."""
2
+
3
+ from pathlib import Path
4
+
5
+ from synapse_sdk.i18n import gettext as _
6
+ from synapse_sdk.plugins.exceptions import ActionError
7
+
8
+ from .actions import get_action
9
+
10
+
11
+ def read_requirements(file_path):
12
+ """Read and parse a requirements.txt file.
13
+
14
+ Args:
15
+ file_path: Path to the requirements.txt file
16
+
17
+ Returns:
18
+ List of requirement strings, or None if file doesn't exist
19
+ """
20
+ file_path = Path(file_path)
21
+ if not file_path.exists():
22
+ return None
23
+
24
+ requirements = []
25
+ for line in file_path.read_text().splitlines():
26
+ stripped_line = line.strip()
27
+ if stripped_line and not stripped_line.startswith('#'):
28
+ requirements.append(stripped_line)
29
+ return requirements
30
+
31
+
32
+ def run_plugin(
33
+ action,
34
+ params,
35
+ plugin_config=None,
36
+ plugin_path=None,
37
+ modules=None,
38
+ requirements=None,
39
+ envs=None,
40
+ debug=False,
41
+ **kwargs,
42
+ ):
43
+ """Execute a plugin action with the specified parameters.
44
+
45
+ Args:
46
+ action: The action name to execute
47
+ params: Parameters for the action
48
+ plugin_config: Plugin configuration dictionary
49
+ plugin_path: Path to the plugin directory
50
+ modules: List of modules for debugging
51
+ requirements: List of requirements
52
+ envs: Environment variables dictionary
53
+ debug: Whether to run in debug mode
54
+ **kwargs: Additional keyword arguments
55
+
56
+ Returns:
57
+ Result of the action execution
58
+ """
59
+ from synapse_sdk.plugins.models import PluginRelease
60
+
61
+ if not envs:
62
+ envs = {}
63
+
64
+ if debug:
65
+ if plugin_path and plugin_path.startswith('http'):
66
+ if not plugin_config:
67
+ raise ActionError({'config': _('"plugin_path"가 url인 경우에는 "config"가 필수입니다.')})
68
+ plugin_release = PluginRelease(config=plugin_config)
69
+ else:
70
+ plugin_release = PluginRelease(plugin_path=plugin_path)
71
+ plugin_config = plugin_release.config
72
+
73
+ if action not in plugin_release.actions:
74
+ raise ActionError({'action': _('해당 액션은 존재하지 않습니다.')})
75
+
76
+ if plugin_path:
77
+ envs['SYNAPSE_DEBUG_PLUGIN_PATH'] = plugin_path
78
+
79
+ if modules:
80
+ envs['SYNAPSE_DEBUG_MODULES'] = ','.join(modules)
81
+
82
+ else:
83
+ if plugin_config is None:
84
+ raise ActionError({'config': _('플러그인 설정은 필수입니다.')})
85
+
86
+ action = get_action(
87
+ action,
88
+ params,
89
+ config=plugin_config,
90
+ requirements=requirements,
91
+ envs=envs,
92
+ debug=debug,
93
+ **kwargs,
94
+ )
95
+ return action.run_action()
@@ -0,0 +1,58 @@
1
+ """Plugin registry utilities."""
2
+
3
+ from typing import List
4
+
5
+ from synapse_sdk.plugins.enums import PluginCategory
6
+
7
+
8
+ def get_plugin_categories() -> List[str]:
9
+ """Get list of all available plugin categories.
10
+
11
+ Returns:
12
+ List of plugin category names.
13
+
14
+ Examples:
15
+ >>> get_plugin_categories()
16
+ ['neural_net', 'export', 'upload', 'smart_tool', 'post_annotation', 'pre_annotation', 'data_validation']
17
+ """
18
+ return [plugin_category.value for plugin_category in PluginCategory]
19
+
20
+
21
+ def is_valid_category(category: str) -> bool:
22
+ """Check if a category is valid.
23
+
24
+ Args:
25
+ category: Category name to validate.
26
+
27
+ Returns:
28
+ True if category is valid, False otherwise.
29
+
30
+ Examples:
31
+ >>> is_valid_category('neural_net')
32
+ True
33
+ >>> is_valid_category('invalid_category')
34
+ False
35
+ """
36
+ return category in get_plugin_categories()
37
+
38
+
39
+ def get_category_display_name(category: str) -> str:
40
+ """Get human-readable display name for a category.
41
+
42
+ Args:
43
+ category: Category name.
44
+
45
+ Returns:
46
+ Human-readable category name.
47
+
48
+ Examples:
49
+ >>> get_category_display_name('neural_net')
50
+ 'Neural Net'
51
+ >>> get_category_display_name('data_validation')
52
+ 'Data Validation'
53
+ """
54
+ try:
55
+ plugin_category = PluginCategory(category)
56
+ return plugin_category.name.replace('_', ' ').title()
57
+ except ValueError:
58
+ return category.replace('_', ' ').title()
@@ -43,7 +43,34 @@ def get_plugin_categories():
43
43
  return [plugin_category.value for plugin_category in PluginCategory]
44
44
 
45
45
 
46
+ def get_plugin_actions(config=None, plugin_path=None):
47
+ """Get list of action names from plugin configuration.
48
+
49
+ Args:
50
+ config: Plugin configuration dictionary. If None, reads from plugin_path.
51
+ plugin_path: Path to plugin directory. Used if config is None.
52
+
53
+ Returns:
54
+ List of action names defined in the plugin.
55
+ """
56
+ if config is None:
57
+ if plugin_path is None:
58
+ config = read_plugin_config()
59
+ else:
60
+ config = read_plugin_config(plugin_path)
61
+
62
+ if 'actions' not in config:
63
+ raise KeyError("'actions' key not found in plugin configuration")
64
+
65
+ actions = config['actions']
66
+ if not isinstance(actions, dict):
67
+ raise ValueError("'actions' must be a dictionary")
68
+
69
+ return list(actions.keys())
70
+
71
+
46
72
  def read_plugin_config(plugin_path=None):
73
+ """Legacy function for backward compatibility."""
47
74
  config_file_name = 'config.yaml'
48
75
  if plugin_path:
49
76
  config_path = Path(plugin_path) / config_file_name
@@ -6,3 +6,4 @@ class Context(str, Enum):
6
6
  SUCCESS = 'success'
7
7
  WARNING = 'warning'
8
8
  DANGER = 'danger'
9
+ ERROR = 'error'
@@ -192,7 +192,9 @@ class ToDMConverter(BaseConverter):
192
192
  json_filename = os.path.splitext(img_filename)[0] + '.json'
193
193
  with open(os.path.join(json_dir, json_filename), 'w', encoding='utf-8') as jf:
194
194
  json.dump(dm_json, jf, indent=2, ensure_ascii=False)
195
- if img_src_path and os.path.exists(img_src_path):
195
+ if img_src_path:
196
+ if not os.path.exists(img_src_path):
197
+ raise FileNotFoundError(f'Source file does not exist: {img_src_path}')
196
198
  shutil.copy(img_src_path, os.path.join(original_file_dir, img_filename))
197
199
  else:
198
200
  json_dir = os.path.join(output_dir, 'json')
@@ -0,0 +1,109 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class BaseDMConverter(ABC):
5
+ """Base class for DM format converters."""
6
+
7
+ SUPPORTED_TOOLS = [
8
+ 'bounding_box',
9
+ 'named_entity',
10
+ 'classification',
11
+ 'polyline',
12
+ 'keypoint',
13
+ '3d_bounding_box',
14
+ 'segmentation',
15
+ 'polygon',
16
+ 'relation',
17
+ 'group',
18
+ ]
19
+
20
+ def __init__(self):
21
+ """Initialize the base converter."""
22
+ self.tool_processors = self._setup_tool_processors()
23
+
24
+ def _setup_tool_processors(self):
25
+ """Setup tool processor mapping."""
26
+ return {
27
+ 'bounding_box': self._process_bounding_box,
28
+ 'named_entity': self._process_named_entity,
29
+ 'classification': self._process_classification,
30
+ 'polyline': self._process_polyline,
31
+ 'keypoint': self._process_keypoint,
32
+ '3d_bounding_box': self._process_3d_bounding_box,
33
+ 'segmentation': self._process_segmentation,
34
+ 'polygon': self._process_polygon,
35
+ 'relation': self._process_relation,
36
+ 'group': self._process_group,
37
+ }
38
+
39
+ @abstractmethod
40
+ def convert(self):
41
+ """Convert data from one format to another."""
42
+ pass
43
+
44
+ @abstractmethod
45
+ def _process_bounding_box(self, *args, **kwargs):
46
+ """Process bounding box annotation."""
47
+ pass
48
+
49
+ @abstractmethod
50
+ def _process_named_entity(self, *args, **kwargs):
51
+ """Process named entity annotation."""
52
+ pass
53
+
54
+ @abstractmethod
55
+ def _process_classification(self, *args, **kwargs):
56
+ """Process classification annotation."""
57
+ pass
58
+
59
+ @abstractmethod
60
+ def _process_polyline(self, *args, **kwargs):
61
+ """Process polyline annotation."""
62
+ pass
63
+
64
+ @abstractmethod
65
+ def _process_keypoint(self, *args, **kwargs):
66
+ """Process keypoint annotation."""
67
+ pass
68
+
69
+ @abstractmethod
70
+ def _process_3d_bounding_box(self, *args, **kwargs):
71
+ """Process 3D bounding box annotation."""
72
+ pass
73
+
74
+ @abstractmethod
75
+ def _process_segmentation(self, *args, **kwargs):
76
+ """Process segmentation annotation."""
77
+ pass
78
+
79
+ @abstractmethod
80
+ def _process_polygon(self, *args, **kwargs):
81
+ """Process polygon annotation."""
82
+ pass
83
+
84
+ @abstractmethod
85
+ def _process_relation(self, *args, **kwargs):
86
+ """Process relation annotation."""
87
+ pass
88
+
89
+ @abstractmethod
90
+ def _process_group(self, *args, **kwargs):
91
+ """Process group annotation."""
92
+ pass
93
+
94
+ def _handle_unknown_tool(self, tool_type, item_id=None):
95
+ """Handle unknown tool types with consistent warning message."""
96
+ warning_msg = f"Warning: Unknown tool type '{tool_type}'"
97
+ if item_id:
98
+ warning_msg += f' for item {item_id}'
99
+ print(warning_msg)
100
+
101
+ def _extract_media_type_info(self, media_id):
102
+ """Extract media type information from media ID."""
103
+ media_type = media_id.split('_')[0] if '_' in media_id else media_id
104
+ media_type_plural = media_type + 's' if not media_type.endswith('s') else media_type
105
+ return media_type, media_type_plural
106
+
107
+ def _singularize_media_type(self, media_type_plural):
108
+ """Convert plural media type to singular."""
109
+ return media_type_plural.rstrip('s')