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.
- synapse_sdk/__init__.py +24 -0
- synapse_sdk/cli/__init__.py +9 -8
- synapse_sdk/cli/agent/__init__.py +25 -0
- synapse_sdk/cli/agent/config.py +104 -0
- synapse_sdk/cli/agent/select.py +197 -0
- synapse_sdk/cli/auth.py +104 -0
- synapse_sdk/cli/main.py +1025 -0
- synapse_sdk/cli/plugin/__init__.py +58 -0
- synapse_sdk/cli/plugin/create.py +566 -0
- synapse_sdk/cli/plugin/job.py +196 -0
- synapse_sdk/cli/plugin/publish.py +322 -0
- synapse_sdk/cli/plugin/run.py +131 -0
- synapse_sdk/cli/plugin/test.py +200 -0
- synapse_sdk/clients/README.md +239 -0
- synapse_sdk/clients/__init__.py +5 -0
- synapse_sdk/clients/_template.py +266 -0
- synapse_sdk/clients/agent/__init__.py +84 -29
- synapse_sdk/clients/agent/async_ray.py +289 -0
- synapse_sdk/clients/agent/container.py +83 -0
- synapse_sdk/clients/agent/plugin.py +101 -0
- synapse_sdk/clients/agent/ray.py +296 -39
- synapse_sdk/clients/backend/__init__.py +152 -12
- synapse_sdk/clients/backend/annotation.py +164 -22
- synapse_sdk/clients/backend/core.py +101 -0
- synapse_sdk/clients/backend/data_collection.py +292 -0
- synapse_sdk/clients/backend/hitl.py +87 -0
- synapse_sdk/clients/backend/integration.py +374 -46
- synapse_sdk/clients/backend/ml.py +134 -22
- synapse_sdk/clients/backend/models.py +247 -0
- synapse_sdk/clients/base.py +538 -59
- synapse_sdk/clients/exceptions.py +35 -7
- synapse_sdk/clients/pipeline/__init__.py +5 -0
- synapse_sdk/clients/pipeline/client.py +636 -0
- synapse_sdk/clients/protocols.py +178 -0
- synapse_sdk/clients/utils.py +86 -8
- synapse_sdk/clients/validation.py +58 -0
- synapse_sdk/enums.py +76 -0
- synapse_sdk/exceptions.py +168 -0
- synapse_sdk/integrations/__init__.py +74 -0
- synapse_sdk/integrations/_base.py +119 -0
- synapse_sdk/integrations/_context.py +53 -0
- synapse_sdk/integrations/ultralytics/__init__.py +78 -0
- synapse_sdk/integrations/ultralytics/_callbacks.py +126 -0
- synapse_sdk/integrations/ultralytics/_patches.py +124 -0
- synapse_sdk/loggers.py +476 -95
- synapse_sdk/mcp/MCP.md +69 -0
- synapse_sdk/mcp/__init__.py +48 -0
- synapse_sdk/mcp/__main__.py +6 -0
- synapse_sdk/mcp/config.py +349 -0
- synapse_sdk/mcp/prompts/__init__.py +4 -0
- synapse_sdk/mcp/resources/__init__.py +4 -0
- synapse_sdk/mcp/server.py +1352 -0
- synapse_sdk/mcp/tools/__init__.py +6 -0
- synapse_sdk/plugins/__init__.py +133 -9
- synapse_sdk/plugins/action.py +229 -0
- synapse_sdk/plugins/actions/__init__.py +82 -0
- synapse_sdk/plugins/actions/dataset/__init__.py +37 -0
- synapse_sdk/plugins/actions/dataset/action.py +471 -0
- synapse_sdk/plugins/actions/export/__init__.py +55 -0
- synapse_sdk/plugins/actions/export/action.py +183 -0
- synapse_sdk/plugins/actions/export/context.py +59 -0
- synapse_sdk/plugins/actions/inference/__init__.py +84 -0
- synapse_sdk/plugins/actions/inference/action.py +285 -0
- synapse_sdk/plugins/actions/inference/context.py +81 -0
- synapse_sdk/plugins/actions/inference/deployment.py +322 -0
- synapse_sdk/plugins/actions/inference/serve.py +252 -0
- synapse_sdk/plugins/actions/train/__init__.py +54 -0
- synapse_sdk/plugins/actions/train/action.py +326 -0
- synapse_sdk/plugins/actions/train/context.py +57 -0
- synapse_sdk/plugins/actions/upload/__init__.py +49 -0
- synapse_sdk/plugins/actions/upload/action.py +165 -0
- synapse_sdk/plugins/actions/upload/context.py +61 -0
- synapse_sdk/plugins/config.py +98 -0
- synapse_sdk/plugins/context/__init__.py +109 -0
- synapse_sdk/plugins/context/env.py +113 -0
- synapse_sdk/plugins/datasets/__init__.py +113 -0
- synapse_sdk/plugins/datasets/converters/__init__.py +76 -0
- synapse_sdk/plugins/datasets/converters/base.py +347 -0
- synapse_sdk/plugins/datasets/converters/yolo/__init__.py +9 -0
- synapse_sdk/plugins/datasets/converters/yolo/from_dm.py +468 -0
- synapse_sdk/plugins/datasets/converters/yolo/to_dm.py +381 -0
- synapse_sdk/plugins/datasets/formats/__init__.py +82 -0
- synapse_sdk/plugins/datasets/formats/dm.py +351 -0
- synapse_sdk/plugins/datasets/formats/yolo.py +240 -0
- synapse_sdk/plugins/decorators.py +83 -0
- synapse_sdk/plugins/discovery.py +790 -0
- synapse_sdk/plugins/docs/ACTION_DEV_GUIDE.md +933 -0
- synapse_sdk/plugins/docs/ARCHITECTURE.md +1225 -0
- synapse_sdk/plugins/docs/LOGGING_SYSTEM.md +683 -0
- synapse_sdk/plugins/docs/OVERVIEW.md +531 -0
- synapse_sdk/plugins/docs/PIPELINE_GUIDE.md +145 -0
- synapse_sdk/plugins/docs/README.md +513 -0
- synapse_sdk/plugins/docs/STEP.md +656 -0
- synapse_sdk/plugins/enums.py +70 -10
- synapse_sdk/plugins/errors.py +92 -0
- synapse_sdk/plugins/executors/__init__.py +43 -0
- synapse_sdk/plugins/executors/local.py +99 -0
- synapse_sdk/plugins/executors/ray/__init__.py +18 -0
- synapse_sdk/plugins/executors/ray/base.py +282 -0
- synapse_sdk/plugins/executors/ray/job.py +298 -0
- synapse_sdk/plugins/executors/ray/jobs_api.py +511 -0
- synapse_sdk/plugins/executors/ray/packaging.py +137 -0
- synapse_sdk/plugins/executors/ray/pipeline.py +792 -0
- synapse_sdk/plugins/executors/ray/task.py +257 -0
- synapse_sdk/plugins/models/__init__.py +26 -0
- synapse_sdk/plugins/models/logger.py +173 -0
- synapse_sdk/plugins/models/pipeline.py +25 -0
- synapse_sdk/plugins/pipelines/__init__.py +81 -0
- synapse_sdk/plugins/pipelines/action_pipeline.py +417 -0
- synapse_sdk/plugins/pipelines/context.py +107 -0
- synapse_sdk/plugins/pipelines/display.py +311 -0
- synapse_sdk/plugins/runner.py +114 -0
- synapse_sdk/plugins/schemas/__init__.py +19 -0
- synapse_sdk/plugins/schemas/results.py +152 -0
- synapse_sdk/plugins/steps/__init__.py +63 -0
- synapse_sdk/plugins/steps/base.py +128 -0
- synapse_sdk/plugins/steps/context.py +90 -0
- synapse_sdk/plugins/steps/orchestrator.py +128 -0
- synapse_sdk/plugins/steps/registry.py +103 -0
- synapse_sdk/plugins/steps/utils/__init__.py +20 -0
- synapse_sdk/plugins/steps/utils/logging.py +85 -0
- synapse_sdk/plugins/steps/utils/timing.py +71 -0
- synapse_sdk/plugins/steps/utils/validation.py +68 -0
- synapse_sdk/plugins/templates/__init__.py +50 -0
- synapse_sdk/plugins/templates/base/.gitignore.j2 +26 -0
- synapse_sdk/plugins/templates/base/.synapseignore.j2 +11 -0
- synapse_sdk/plugins/templates/base/README.md.j2 +26 -0
- synapse_sdk/plugins/templates/base/plugin/__init__.py.j2 +1 -0
- synapse_sdk/plugins/templates/base/pyproject.toml.j2 +14 -0
- synapse_sdk/plugins/templates/base/requirements.txt.j2 +1 -0
- synapse_sdk/plugins/templates/custom/plugin/main.py.j2 +18 -0
- synapse_sdk/plugins/templates/data_validation/plugin/validate.py.j2 +32 -0
- synapse_sdk/plugins/templates/export/plugin/export.py.j2 +36 -0
- synapse_sdk/plugins/templates/neural_net/plugin/inference.py.j2 +36 -0
- synapse_sdk/plugins/templates/neural_net/plugin/train.py.j2 +33 -0
- synapse_sdk/plugins/templates/post_annotation/plugin/post_annotate.py.j2 +32 -0
- synapse_sdk/plugins/templates/pre_annotation/plugin/pre_annotate.py.j2 +32 -0
- synapse_sdk/plugins/templates/smart_tool/plugin/auto_label.py.j2 +44 -0
- synapse_sdk/plugins/templates/upload/plugin/upload.py.j2 +35 -0
- synapse_sdk/plugins/testing/__init__.py +25 -0
- synapse_sdk/plugins/testing/sample_actions.py +98 -0
- synapse_sdk/plugins/types.py +206 -0
- synapse_sdk/plugins/upload.py +595 -64
- synapse_sdk/plugins/utils.py +325 -37
- synapse_sdk/shared/__init__.py +25 -0
- synapse_sdk/utils/__init__.py +1 -0
- synapse_sdk/utils/auth.py +74 -0
- synapse_sdk/utils/file/__init__.py +58 -0
- synapse_sdk/utils/file/archive.py +449 -0
- synapse_sdk/utils/file/checksum.py +167 -0
- synapse_sdk/utils/file/download.py +286 -0
- synapse_sdk/utils/file/io.py +129 -0
- synapse_sdk/utils/file/requirements.py +36 -0
- synapse_sdk/utils/network.py +168 -0
- synapse_sdk/utils/storage/__init__.py +238 -0
- synapse_sdk/utils/storage/config.py +188 -0
- synapse_sdk/utils/storage/errors.py +52 -0
- synapse_sdk/utils/storage/providers/__init__.py +13 -0
- synapse_sdk/utils/storage/providers/base.py +76 -0
- synapse_sdk/utils/storage/providers/gcs.py +168 -0
- synapse_sdk/utils/storage/providers/http.py +250 -0
- synapse_sdk/utils/storage/providers/local.py +126 -0
- synapse_sdk/utils/storage/providers/s3.py +177 -0
- synapse_sdk/utils/storage/providers/sftp.py +208 -0
- synapse_sdk/utils/storage/registry.py +125 -0
- synapse_sdk/utils/websocket.py +99 -0
- synapse_sdk-2026.1.1b2.dist-info/METADATA +715 -0
- synapse_sdk-2026.1.1b2.dist-info/RECORD +172 -0
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/WHEEL +1 -1
- synapse_sdk-2026.1.1b2.dist-info/licenses/LICENSE +201 -0
- locale/en/LC_MESSAGES/messages.mo +0 -0
- locale/en/LC_MESSAGES/messages.po +0 -39
- locale/ko/LC_MESSAGES/messages.mo +0 -0
- locale/ko/LC_MESSAGES/messages.po +0 -34
- synapse_sdk/cli/create_plugin.py +0 -10
- synapse_sdk/clients/agent/core.py +0 -7
- synapse_sdk/clients/agent/service.py +0 -15
- synapse_sdk/clients/backend/dataset.py +0 -51
- synapse_sdk/clients/ray/__init__.py +0 -6
- synapse_sdk/clients/ray/core.py +0 -22
- synapse_sdk/clients/ray/serve.py +0 -20
- synapse_sdk/i18n.py +0 -35
- synapse_sdk/plugins/categories/__init__.py +0 -0
- synapse_sdk/plugins/categories/base.py +0 -235
- synapse_sdk/plugins/categories/data_validation/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/actions/validation.py +0 -10
- synapse_sdk/plugins/categories/data_validation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/data_validation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +0 -5
- synapse_sdk/plugins/categories/decorators.py +0 -13
- synapse_sdk/plugins/categories/export/__init__.py +0 -0
- synapse_sdk/plugins/categories/export/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/export/actions/export.py +0 -10
- synapse_sdk/plugins/categories/import/__init__.py +0 -0
- synapse_sdk/plugins/categories/import/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/import/actions/import.py +0 -10
- synapse_sdk/plugins/categories/neural_net/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/actions/deployment.py +0 -45
- synapse_sdk/plugins/categories/neural_net/actions/inference.py +0 -18
- synapse_sdk/plugins/categories/neural_net/actions/test.py +0 -10
- synapse_sdk/plugins/categories/neural_net/actions/train.py +0 -143
- synapse_sdk/plugins/categories/neural_net/templates/config.yaml +0 -12
- synapse_sdk/plugins/categories/neural_net/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +0 -4
- synapse_sdk/plugins/categories/neural_net/templates/plugin/test.py +0 -2
- synapse_sdk/plugins/categories/neural_net/templates/plugin/train.py +0 -14
- synapse_sdk/plugins/categories/post_annotation/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/actions/post_annotation.py +0 -10
- synapse_sdk/plugins/categories/post_annotation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/post_annotation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/templates/plugin/post_annotation.py +0 -3
- synapse_sdk/plugins/categories/pre_annotation/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation.py +0 -10
- synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/pre_annotation.py +0 -3
- synapse_sdk/plugins/categories/registry.py +0 -16
- synapse_sdk/plugins/categories/smart_tool/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/actions/auto_label.py +0 -37
- synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +0 -7
- synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py +0 -11
- synapse_sdk/plugins/categories/templates.py +0 -32
- synapse_sdk/plugins/cli/__init__.py +0 -21
- synapse_sdk/plugins/cli/publish.py +0 -37
- synapse_sdk/plugins/cli/run.py +0 -67
- synapse_sdk/plugins/exceptions.py +0 -22
- synapse_sdk/plugins/models.py +0 -121
- synapse_sdk/plugins/templates/cookiecutter.json +0 -11
- synapse_sdk/plugins/templates/hooks/post_gen_project.py +0 -3
- synapse_sdk/plugins/templates/hooks/pre_prompt.py +0 -21
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.gitignore +0 -27
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.pre-commit-config.yaml +0 -7
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/README.md +0 -5
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +0 -6
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/plugin/__init__.py +0 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/pyproject.toml +0 -13
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +0 -1
- synapse_sdk/shared/enums.py +0 -8
- synapse_sdk/utils/debug.py +0 -5
- synapse_sdk/utils/file.py +0 -87
- synapse_sdk/utils/module_loading.py +0 -29
- synapse_sdk/utils/pydantic/__init__.py +0 -0
- synapse_sdk/utils/pydantic/config.py +0 -4
- synapse_sdk/utils/pydantic/errors.py +0 -33
- synapse_sdk/utils/pydantic/validators.py +0 -7
- synapse_sdk/utils/storage.py +0 -91
- synapse_sdk/utils/string.py +0 -11
- synapse_sdk-1.0.0a11.dist-info/LICENSE +0 -21
- synapse_sdk-1.0.0a11.dist-info/METADATA +0 -43
- synapse_sdk-1.0.0a11.dist-info/RECORD +0 -111
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/entry_points.txt +0 -0
- {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
|
+
]
|