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,468 @@
|
|
|
1
|
+
"""Convert Datamaker format to YOLO format.
|
|
2
|
+
|
|
3
|
+
Supports:
|
|
4
|
+
- DMv1 and DMv2 schemas
|
|
5
|
+
- Bounding box, polygon, 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
|
+
import json
|
|
13
|
+
import shutil
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import IO, Any
|
|
16
|
+
|
|
17
|
+
from synapse_sdk.plugins.datasets.converters.base import DatasetFormat, FromDMConverter
|
|
18
|
+
from synapse_sdk.plugins.datasets.formats.dm import DMVersion
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FromDMToYOLOConverter(FromDMConverter):
|
|
22
|
+
"""Convert Datamaker dataset format to YOLO format.
|
|
23
|
+
|
|
24
|
+
Supports bounding boxes, polygons (native YOLO segmentation format),
|
|
25
|
+
and keypoints. Works with both DMv1 and DMv2 schemas.
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
>>> # Directory conversion
|
|
29
|
+
>>> converter = FromDMToYOLOConverter(
|
|
30
|
+
... root_dir='/data/dm_dataset',
|
|
31
|
+
... is_categorized=True,
|
|
32
|
+
... dm_version=DMVersion.V2,
|
|
33
|
+
... )
|
|
34
|
+
>>> converter.convert()
|
|
35
|
+
>>> converter.save_to_folder('/data/yolo_output')
|
|
36
|
+
|
|
37
|
+
>>> # Single file conversion
|
|
38
|
+
>>> converter = FromDMToYOLOConverter(is_single_conversion=True)
|
|
39
|
+
>>> result = converter.convert_single_file(dm_json, image_file)
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
target_format = DatasetFormat.YOLO
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
root_dir: str | Path | None = None,
|
|
47
|
+
is_categorized: bool = False,
|
|
48
|
+
is_single_conversion: bool = False,
|
|
49
|
+
dm_version: DMVersion = DMVersion.V2,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Initialize converter.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
root_dir: Root directory containing DM data.
|
|
55
|
+
is_categorized: Whether dataset has train/valid/test splits.
|
|
56
|
+
is_single_conversion: Whether converting single files only.
|
|
57
|
+
dm_version: Datamaker schema version (V1 or V2).
|
|
58
|
+
"""
|
|
59
|
+
super().__init__(root_dir, is_categorized, is_single_conversion, dm_version)
|
|
60
|
+
self.dataset_yaml_content: str = ''
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def get_all_classes(list_of_dirs: list[Path], dm_version: DMVersion = DMVersion.V2) -> list[str]:
|
|
64
|
+
"""Collect all unique class names from directories.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
list_of_dirs: List of directories to scan.
|
|
68
|
+
dm_version: Datamaker schema version.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Sorted list of unique class names.
|
|
72
|
+
"""
|
|
73
|
+
classes: set[str] = set()
|
|
74
|
+
|
|
75
|
+
for d in list_of_dirs:
|
|
76
|
+
if not d or not d.is_dir():
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
json_dir = d / 'json' if (d / 'json').is_dir() else d
|
|
80
|
+
|
|
81
|
+
for jfile in json_dir.glob('*.json'):
|
|
82
|
+
with open(jfile, encoding='utf-8') as f:
|
|
83
|
+
data = json.load(f)
|
|
84
|
+
|
|
85
|
+
if dm_version == DMVersion.V2:
|
|
86
|
+
for img_ann in data.get('images', []):
|
|
87
|
+
for key in ['bounding_box', 'polygon', 'keypoint']:
|
|
88
|
+
for ann in img_ann.get(key, []):
|
|
89
|
+
if 'classification' in ann:
|
|
90
|
+
classes.add(ann['classification'])
|
|
91
|
+
else:
|
|
92
|
+
# V1: annotations keyed by asset
|
|
93
|
+
for anns in data.get('annotations', {}).values():
|
|
94
|
+
for ann in anns:
|
|
95
|
+
classification = ann.get('classification', {})
|
|
96
|
+
if isinstance(classification, dict):
|
|
97
|
+
for val in classification.values():
|
|
98
|
+
if isinstance(val, str):
|
|
99
|
+
classes.add(val)
|
|
100
|
+
|
|
101
|
+
return sorted(classes)
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def polygon_to_yolo_string(polygon: list, width: int, height: int) -> str:
|
|
105
|
+
"""Convert polygon points to normalized YOLO segmentation format.
|
|
106
|
+
|
|
107
|
+
YOLO segmentation format: x1 y1 x2 y2 x3 y3 ... (normalized 0-1)
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
polygon: List of [x, y] points.
|
|
111
|
+
width: Image width in pixels.
|
|
112
|
+
height: Image height in pixels.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Space-separated normalized coordinate string.
|
|
116
|
+
"""
|
|
117
|
+
if not polygon:
|
|
118
|
+
return ''
|
|
119
|
+
|
|
120
|
+
coords = []
|
|
121
|
+
for point in polygon:
|
|
122
|
+
x, y = point[0], point[1]
|
|
123
|
+
x_norm = x / width
|
|
124
|
+
y_norm = y / height
|
|
125
|
+
coords.extend([f'{x_norm:.6f}', f'{y_norm:.6f}'])
|
|
126
|
+
|
|
127
|
+
return ' '.join(coords)
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def keypoints_to_yolo_string(keypoints: list, width: int, height: int) -> str:
|
|
131
|
+
"""Convert keypoints to normalized YOLO keypoint format.
|
|
132
|
+
|
|
133
|
+
YOLO keypoint format: x1 y1 v1 x2 y2 v2 ... (normalized, v=visibility)
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
keypoints: List of [x, y, visibility] points.
|
|
137
|
+
width: Image width in pixels.
|
|
138
|
+
height: Image height in pixels.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Space-separated normalized coordinate string with visibility.
|
|
142
|
+
"""
|
|
143
|
+
kp_strs = []
|
|
144
|
+
for kp in keypoints:
|
|
145
|
+
x, y = kp[0], kp[1]
|
|
146
|
+
v = kp[2] if len(kp) > 2 else 2 # Default visible
|
|
147
|
+
x_norm = x / width
|
|
148
|
+
y_norm = y / height
|
|
149
|
+
kp_strs.extend([f'{x_norm:.6f}', f'{y_norm:.6f}', str(int(v))])
|
|
150
|
+
return ' '.join(kp_strs)
|
|
151
|
+
|
|
152
|
+
def _convert_split_dir(self, split_dir: Path, split_name: str) -> list[dict[str, Any]]:
|
|
153
|
+
"""Convert one split folder to YOLO format."""
|
|
154
|
+
if self.class_map is None:
|
|
155
|
+
raise ValueError('class_map not initialized. Call convert() first.')
|
|
156
|
+
|
|
157
|
+
json_dir = split_dir / 'json'
|
|
158
|
+
img_dir = split_dir / 'original_files'
|
|
159
|
+
entries = []
|
|
160
|
+
|
|
161
|
+
for jfile in json_dir.glob('*.json'):
|
|
162
|
+
base = jfile.stem
|
|
163
|
+
|
|
164
|
+
# Find corresponding image
|
|
165
|
+
img_path = self.find_image_for_label(base, img_dir)
|
|
166
|
+
if not img_path:
|
|
167
|
+
print(f'[{split_name}] Image for {base} not found, skipping.')
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
width, height = self.get_image_size(img_path)
|
|
171
|
+
|
|
172
|
+
with open(jfile, encoding='utf-8') as f:
|
|
173
|
+
data = json.load(f)
|
|
174
|
+
|
|
175
|
+
label_lines = self._convert_dm_json_to_yolo_lines(data, width, height)
|
|
176
|
+
|
|
177
|
+
entries.append({
|
|
178
|
+
'img_path': img_path,
|
|
179
|
+
'img_name': img_path.name,
|
|
180
|
+
'label_name': f'{base}.txt',
|
|
181
|
+
'label_lines': label_lines,
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
return entries
|
|
185
|
+
|
|
186
|
+
def _convert_root_dir(self) -> list[dict[str, Any]]:
|
|
187
|
+
"""Convert non-categorized dataset to YOLO format."""
|
|
188
|
+
return self._convert_split_dir(self.root_dir, 'root')
|
|
189
|
+
|
|
190
|
+
def _convert_dm_json_to_yolo_lines(
|
|
191
|
+
self,
|
|
192
|
+
data: dict[str, Any],
|
|
193
|
+
width: int,
|
|
194
|
+
height: int,
|
|
195
|
+
) -> list[str]:
|
|
196
|
+
"""Convert DM JSON data to YOLO label lines."""
|
|
197
|
+
label_lines = []
|
|
198
|
+
|
|
199
|
+
if self.dm_version == DMVersion.V2:
|
|
200
|
+
if 'images' in data and data['images']:
|
|
201
|
+
img_ann = data['images'][0]
|
|
202
|
+
label_lines.extend(self._convert_v2_annotations(img_ann, width, height))
|
|
203
|
+
else:
|
|
204
|
+
# V1: annotations keyed by asset
|
|
205
|
+
for anns in data.get('annotations', {}).values():
|
|
206
|
+
label_lines.extend(self._convert_v1_annotations(anns, width, height))
|
|
207
|
+
|
|
208
|
+
return label_lines
|
|
209
|
+
|
|
210
|
+
def _convert_v2_annotations(
|
|
211
|
+
self,
|
|
212
|
+
img_ann: dict[str, Any],
|
|
213
|
+
width: int,
|
|
214
|
+
height: int,
|
|
215
|
+
) -> list[str]:
|
|
216
|
+
"""Convert DMv2 image annotations to YOLO lines."""
|
|
217
|
+
lines = []
|
|
218
|
+
|
|
219
|
+
# Bounding boxes
|
|
220
|
+
for box in img_ann.get('bounding_box', []):
|
|
221
|
+
classification = box.get('classification')
|
|
222
|
+
if classification not in self.class_map:
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
cidx = self.class_map[classification]
|
|
226
|
+
x, y, w, h = box['data']
|
|
227
|
+
|
|
228
|
+
# Convert to YOLO format: center_x, center_y, width, height (normalized)
|
|
229
|
+
cx = (x + w / 2) / width
|
|
230
|
+
cy = (y + h / 2) / height
|
|
231
|
+
nw = w / width
|
|
232
|
+
nh = h / height
|
|
233
|
+
|
|
234
|
+
lines.append(f'{cidx} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}')
|
|
235
|
+
|
|
236
|
+
# Polygons (YOLO segmentation format)
|
|
237
|
+
for poly in img_ann.get('polygon', []):
|
|
238
|
+
classification = poly.get('classification')
|
|
239
|
+
if classification not in self.class_map:
|
|
240
|
+
continue
|
|
241
|
+
|
|
242
|
+
cidx = self.class_map[classification]
|
|
243
|
+
poly_str = self.polygon_to_yolo_string(poly['data'], width, height)
|
|
244
|
+
if poly_str:
|
|
245
|
+
lines.append(f'{cidx} {poly_str}')
|
|
246
|
+
|
|
247
|
+
# Keypoints
|
|
248
|
+
for kp in img_ann.get('keypoint', []):
|
|
249
|
+
classification = kp.get('classification')
|
|
250
|
+
if classification not in self.class_map:
|
|
251
|
+
continue
|
|
252
|
+
|
|
253
|
+
cidx = self.class_map[classification]
|
|
254
|
+
|
|
255
|
+
# Get bounding box for keypoint (required for YOLO pose)
|
|
256
|
+
if 'bounding_box' in kp:
|
|
257
|
+
bbox = kp['bounding_box']
|
|
258
|
+
if isinstance(bbox, dict):
|
|
259
|
+
x, y, w, h = bbox['x'], bbox['y'], bbox['width'], bbox['height']
|
|
260
|
+
else:
|
|
261
|
+
x, y, w, h = bbox
|
|
262
|
+
cx = (x + w / 2) / width
|
|
263
|
+
cy = (y + h / 2) / height
|
|
264
|
+
nw = w / width
|
|
265
|
+
nh = h / height
|
|
266
|
+
else:
|
|
267
|
+
# Fallback to full image
|
|
268
|
+
cx, cy, nw, nh = 0.5, 0.5, 1.0, 1.0
|
|
269
|
+
|
|
270
|
+
kp_str = self.keypoints_to_yolo_string(kp['data'], width, height)
|
|
271
|
+
lines.append(f'{cidx} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f} {kp_str}')
|
|
272
|
+
|
|
273
|
+
return lines
|
|
274
|
+
|
|
275
|
+
def _convert_v1_annotations(
|
|
276
|
+
self,
|
|
277
|
+
annotations: list[dict[str, Any]],
|
|
278
|
+
width: int,
|
|
279
|
+
height: int,
|
|
280
|
+
) -> list[str]:
|
|
281
|
+
"""Convert DMv1 annotations to YOLO lines."""
|
|
282
|
+
lines = []
|
|
283
|
+
|
|
284
|
+
for ann in annotations:
|
|
285
|
+
tool = ann.get('tool', '')
|
|
286
|
+
|
|
287
|
+
# Get class name from classification
|
|
288
|
+
class_name = None
|
|
289
|
+
classification = ann.get('classification', {})
|
|
290
|
+
if isinstance(classification, dict):
|
|
291
|
+
class_name = classification.get('class') or classification.get('label')
|
|
292
|
+
if not class_name:
|
|
293
|
+
for val in classification.values():
|
|
294
|
+
if isinstance(val, str):
|
|
295
|
+
class_name = val
|
|
296
|
+
break
|
|
297
|
+
|
|
298
|
+
if not class_name or class_name not in self.class_map:
|
|
299
|
+
continue
|
|
300
|
+
|
|
301
|
+
cidx = self.class_map[class_name]
|
|
302
|
+
data = ann.get('data') or ann.get('points')
|
|
303
|
+
if not data:
|
|
304
|
+
continue
|
|
305
|
+
|
|
306
|
+
if tool in ('boundingBox', 'bounding_box'):
|
|
307
|
+
if isinstance(data, dict):
|
|
308
|
+
x, y, w, h = data['x'], data['y'], data['width'], data['height']
|
|
309
|
+
else:
|
|
310
|
+
x, y, w, h = data
|
|
311
|
+
|
|
312
|
+
cx = (x + w / 2) / width
|
|
313
|
+
cy = (y + h / 2) / height
|
|
314
|
+
nw = w / width
|
|
315
|
+
nh = h / height
|
|
316
|
+
lines.append(f'{cidx} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}')
|
|
317
|
+
|
|
318
|
+
elif tool == 'polygon':
|
|
319
|
+
poly_str = self.polygon_to_yolo_string(data, width, height)
|
|
320
|
+
if poly_str:
|
|
321
|
+
lines.append(f'{cidx} {poly_str}')
|
|
322
|
+
|
|
323
|
+
elif tool == 'keypoint':
|
|
324
|
+
bbox = ann.get('bounding_box')
|
|
325
|
+
if bbox:
|
|
326
|
+
if isinstance(bbox, dict):
|
|
327
|
+
x, y, w, h = bbox['x'], bbox['y'], bbox['width'], bbox['height']
|
|
328
|
+
else:
|
|
329
|
+
x, y, w, h = bbox
|
|
330
|
+
cx = (x + w / 2) / width
|
|
331
|
+
cy = (y + h / 2) / height
|
|
332
|
+
nw = w / width
|
|
333
|
+
nh = h / height
|
|
334
|
+
else:
|
|
335
|
+
cx, cy, nw, nh = 0.5, 0.5, 1.0, 1.0
|
|
336
|
+
|
|
337
|
+
kp_str = self.keypoints_to_yolo_string(data, width, height)
|
|
338
|
+
lines.append(f'{cidx} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f} {kp_str}')
|
|
339
|
+
|
|
340
|
+
return lines
|
|
341
|
+
|
|
342
|
+
def convert(self) -> dict[str, list[dict[str, Any]]] | list[dict[str, Any]]:
|
|
343
|
+
"""Convert DM format to YOLO format.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
If categorized: dict mapping split names to list of entries.
|
|
347
|
+
If not categorized: list of entries.
|
|
348
|
+
Each entry contains img_path, img_name, label_name, label_lines.
|
|
349
|
+
"""
|
|
350
|
+
yaml_lines = [f'path: {self.root_dir}']
|
|
351
|
+
|
|
352
|
+
if self.is_categorized:
|
|
353
|
+
splits = self._validate_splits(['train', 'valid'], ['test'])
|
|
354
|
+
self.class_names = self.get_all_classes(list(splits.values()), self.dm_version)
|
|
355
|
+
self.class_map = {name: idx for idx, name in enumerate(self.class_names)}
|
|
356
|
+
|
|
357
|
+
result = {}
|
|
358
|
+
for split, split_dir in splits.items():
|
|
359
|
+
result[split] = self._convert_split_dir(split_dir, split)
|
|
360
|
+
self.converted_data = result
|
|
361
|
+
|
|
362
|
+
yaml_lines.append('train: train/images')
|
|
363
|
+
yaml_lines.append('val: valid/images')
|
|
364
|
+
if 'test' in splits:
|
|
365
|
+
yaml_lines.append('test: test/images')
|
|
366
|
+
else:
|
|
367
|
+
self._validate_splits([], [])
|
|
368
|
+
self.class_names = self.get_all_classes([self.root_dir], self.dm_version)
|
|
369
|
+
self.class_map = {name: idx for idx, name in enumerate(self.class_names)}
|
|
370
|
+
|
|
371
|
+
result = self._convert_root_dir()
|
|
372
|
+
self.converted_data = result
|
|
373
|
+
|
|
374
|
+
yaml_lines.append('train: images')
|
|
375
|
+
yaml_lines.append('val: images')
|
|
376
|
+
|
|
377
|
+
yaml_lines.extend(['', f'nc: {len(self.class_names)}', f'names: {self.class_names}', ''])
|
|
378
|
+
self.dataset_yaml_content = '\n'.join(yaml_lines)
|
|
379
|
+
|
|
380
|
+
return self.converted_data
|
|
381
|
+
|
|
382
|
+
def save_to_folder(self, output_dir: str | Path | None = None) -> None:
|
|
383
|
+
"""Save converted YOLO data to folder.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
output_dir: Output directory. Defaults to root_dir.
|
|
387
|
+
"""
|
|
388
|
+
output_dir = Path(output_dir) if output_dir else self.root_dir
|
|
389
|
+
self.ensure_dir(output_dir)
|
|
390
|
+
|
|
391
|
+
if self.converted_data is None:
|
|
392
|
+
self.converted_data = self.convert()
|
|
393
|
+
|
|
394
|
+
if self.is_categorized:
|
|
395
|
+
for split, entries in self.converted_data.items():
|
|
396
|
+
split_imgs = self.ensure_dir(output_dir / split / 'images')
|
|
397
|
+
split_labels = self.ensure_dir(output_dir / split / 'labels')
|
|
398
|
+
|
|
399
|
+
for entry in entries:
|
|
400
|
+
shutil.copy(entry['img_path'], split_imgs / entry['img_name'])
|
|
401
|
+
(split_labels / entry['label_name']).write_text('\n'.join(entry['label_lines']))
|
|
402
|
+
else:
|
|
403
|
+
imgs_dir = self.ensure_dir(output_dir / 'images')
|
|
404
|
+
labels_dir = self.ensure_dir(output_dir / 'labels')
|
|
405
|
+
|
|
406
|
+
for entry in self.converted_data:
|
|
407
|
+
shutil.copy(entry['img_path'], imgs_dir / entry['img_name'])
|
|
408
|
+
(labels_dir / entry['label_name']).write_text('\n'.join(entry['label_lines']))
|
|
409
|
+
|
|
410
|
+
# Write dataset.yaml and classes.txt
|
|
411
|
+
(output_dir / 'dataset.yaml').write_text(self.dataset_yaml_content)
|
|
412
|
+
(output_dir / 'classes.txt').write_text('\n'.join(self.class_names) + '\n')
|
|
413
|
+
|
|
414
|
+
def convert_single_file(
|
|
415
|
+
self,
|
|
416
|
+
data: dict[str, Any],
|
|
417
|
+
original_file: IO,
|
|
418
|
+
class_names: list[str] | None = None,
|
|
419
|
+
) -> dict[str, Any]:
|
|
420
|
+
"""Convert a single DM JSON and image to YOLO format.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
data: DM format JSON data.
|
|
424
|
+
original_file: Image file object.
|
|
425
|
+
class_names: Optional class names. If not provided, extracted from data.
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
Dictionary with label_lines, class_names, class_map.
|
|
429
|
+
"""
|
|
430
|
+
if not self.is_single_conversion:
|
|
431
|
+
raise RuntimeError('convert_single_file only available when is_single_conversion=True')
|
|
432
|
+
|
|
433
|
+
# Extract class names if not provided
|
|
434
|
+
if class_names is None:
|
|
435
|
+
class_names = []
|
|
436
|
+
classes: set[str] = set()
|
|
437
|
+
|
|
438
|
+
if self.dm_version == DMVersion.V2:
|
|
439
|
+
for img_ann in data.get('images', []):
|
|
440
|
+
for key in ['bounding_box', 'polygon', 'keypoint']:
|
|
441
|
+
for ann in img_ann.get(key, []):
|
|
442
|
+
if 'classification' in ann:
|
|
443
|
+
classes.add(ann['classification'])
|
|
444
|
+
else:
|
|
445
|
+
for anns in data.get('annotations', {}).values():
|
|
446
|
+
for ann in anns:
|
|
447
|
+
classification = ann.get('classification', {})
|
|
448
|
+
if isinstance(classification, dict):
|
|
449
|
+
for val in classification.values():
|
|
450
|
+
if isinstance(val, str):
|
|
451
|
+
classes.add(val)
|
|
452
|
+
|
|
453
|
+
class_names = sorted(classes)
|
|
454
|
+
|
|
455
|
+
self.class_names = class_names
|
|
456
|
+
self.class_map = {name: idx for idx, name in enumerate(class_names)}
|
|
457
|
+
|
|
458
|
+
width, height = self.get_image_size(original_file)
|
|
459
|
+
label_lines = self._convert_dm_json_to_yolo_lines(data, width, height)
|
|
460
|
+
|
|
461
|
+
return {
|
|
462
|
+
'label_lines': label_lines,
|
|
463
|
+
'class_names': class_names,
|
|
464
|
+
'class_map': self.class_map,
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
__all__ = ['FromDMToYOLOConverter']
|