synapse-sdk 1.0.0a35__py3-none-any.whl → 2025.11.7__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 (307) hide show
  1. synapse_sdk/__init__.py +24 -0
  2. synapse_sdk/cli/__init__.py +308 -5
  3. synapse_sdk/cli/alias/utils.py +1 -1
  4. synapse_sdk/cli/code_server.py +687 -0
  5. synapse_sdk/cli/config.py +440 -0
  6. synapse_sdk/cli/devtools.py +90 -0
  7. synapse_sdk/cli/plugin/publish.py +23 -15
  8. synapse_sdk/clients/agent/__init__.py +9 -3
  9. synapse_sdk/clients/agent/container.py +133 -0
  10. synapse_sdk/clients/agent/core.py +19 -0
  11. synapse_sdk/clients/agent/ray.py +298 -9
  12. synapse_sdk/clients/backend/__init__.py +28 -12
  13. synapse_sdk/clients/backend/annotation.py +9 -1
  14. synapse_sdk/clients/backend/core.py +31 -4
  15. synapse_sdk/clients/backend/data_collection.py +186 -0
  16. synapse_sdk/clients/backend/hitl.py +1 -1
  17. synapse_sdk/clients/backend/integration.py +4 -3
  18. synapse_sdk/clients/backend/ml.py +1 -1
  19. synapse_sdk/clients/backend/models.py +35 -1
  20. synapse_sdk/clients/base.py +309 -36
  21. synapse_sdk/clients/ray/serve.py +2 -0
  22. synapse_sdk/devtools/__init__.py +0 -0
  23. synapse_sdk/devtools/config.py +94 -0
  24. synapse_sdk/devtools/docs/.gitignore +20 -0
  25. synapse_sdk/devtools/docs/README.md +41 -0
  26. synapse_sdk/devtools/docs/blog/2019-05-28-first-blog-post.md +12 -0
  27. synapse_sdk/devtools/docs/blog/2019-05-29-long-blog-post.md +44 -0
  28. synapse_sdk/devtools/docs/blog/2021-08-01-mdx-blog-post.mdx +24 -0
  29. synapse_sdk/devtools/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  30. synapse_sdk/devtools/docs/blog/2021-08-26-welcome/index.md +29 -0
  31. synapse_sdk/devtools/docs/blog/authors.yml +25 -0
  32. synapse_sdk/devtools/docs/blog/tags.yml +19 -0
  33. synapse_sdk/devtools/docs/docs/api/clients/agent.md +43 -0
  34. synapse_sdk/devtools/docs/docs/api/clients/annotation-mixin.md +378 -0
  35. synapse_sdk/devtools/docs/docs/api/clients/backend.md +420 -0
  36. synapse_sdk/devtools/docs/docs/api/clients/base.md +257 -0
  37. synapse_sdk/devtools/docs/docs/api/clients/core-mixin.md +477 -0
  38. synapse_sdk/devtools/docs/docs/api/clients/data-collection-mixin.md +422 -0
  39. synapse_sdk/devtools/docs/docs/api/clients/hitl-mixin.md +554 -0
  40. synapse_sdk/devtools/docs/docs/api/clients/index.md +391 -0
  41. synapse_sdk/devtools/docs/docs/api/clients/integration-mixin.md +571 -0
  42. synapse_sdk/devtools/docs/docs/api/clients/ml-mixin.md +578 -0
  43. synapse_sdk/devtools/docs/docs/api/clients/ray.md +342 -0
  44. synapse_sdk/devtools/docs/docs/api/index.md +52 -0
  45. synapse_sdk/devtools/docs/docs/api/plugins/categories.md +43 -0
  46. synapse_sdk/devtools/docs/docs/api/plugins/models.md +114 -0
  47. synapse_sdk/devtools/docs/docs/api/plugins/utils.md +328 -0
  48. synapse_sdk/devtools/docs/docs/categories.md +0 -0
  49. synapse_sdk/devtools/docs/docs/cli-usage.md +280 -0
  50. synapse_sdk/devtools/docs/docs/concepts/index.md +38 -0
  51. synapse_sdk/devtools/docs/docs/configuration.md +83 -0
  52. synapse_sdk/devtools/docs/docs/contributing.md +306 -0
  53. synapse_sdk/devtools/docs/docs/examples/index.md +29 -0
  54. synapse_sdk/devtools/docs/docs/faq.md +179 -0
  55. synapse_sdk/devtools/docs/docs/features/converters/index.md +455 -0
  56. synapse_sdk/devtools/docs/docs/features/index.md +24 -0
  57. synapse_sdk/devtools/docs/docs/features/utils/file.md +415 -0
  58. synapse_sdk/devtools/docs/docs/features/utils/network.md +378 -0
  59. synapse_sdk/devtools/docs/docs/features/utils/storage.md +57 -0
  60. synapse_sdk/devtools/docs/docs/features/utils/types.md +51 -0
  61. synapse_sdk/devtools/docs/docs/installation.md +94 -0
  62. synapse_sdk/devtools/docs/docs/introduction.md +47 -0
  63. synapse_sdk/devtools/docs/docs/plugins/categories/neural-net-plugins/train-action-overview.md +814 -0
  64. synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/pre-annotation-plugin-overview.md +198 -0
  65. synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-action-development.md +1645 -0
  66. synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-overview.md +717 -0
  67. synapse_sdk/devtools/docs/docs/plugins/categories/pre-annotation-plugins/to-task-template-development.md +1380 -0
  68. synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-action.md +948 -0
  69. synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-overview.md +544 -0
  70. synapse_sdk/devtools/docs/docs/plugins/categories/upload-plugins/upload-plugin-template.md +766 -0
  71. synapse_sdk/devtools/docs/docs/plugins/export-plugins.md +1092 -0
  72. synapse_sdk/devtools/docs/docs/plugins/plugins.md +852 -0
  73. synapse_sdk/devtools/docs/docs/quickstart.md +78 -0
  74. synapse_sdk/devtools/docs/docs/troubleshooting.md +519 -0
  75. synapse_sdk/devtools/docs/docs/tutorial-basics/_category_.json +8 -0
  76. synapse_sdk/devtools/docs/docs/tutorial-basics/congratulations.md +23 -0
  77. synapse_sdk/devtools/docs/docs/tutorial-basics/create-a-blog-post.md +34 -0
  78. synapse_sdk/devtools/docs/docs/tutorial-basics/create-a-document.md +57 -0
  79. synapse_sdk/devtools/docs/docs/tutorial-basics/create-a-page.md +43 -0
  80. synapse_sdk/devtools/docs/docs/tutorial-basics/deploy-your-site.md +31 -0
  81. synapse_sdk/devtools/docs/docs/tutorial-basics/markdown-features.mdx +152 -0
  82. synapse_sdk/devtools/docs/docs/tutorial-extras/_category_.json +7 -0
  83. synapse_sdk/devtools/docs/docs/tutorial-extras/img/docsVersionDropdown.png +0 -0
  84. synapse_sdk/devtools/docs/docs/tutorial-extras/img/localeDropdown.png +0 -0
  85. synapse_sdk/devtools/docs/docs/tutorial-extras/manage-docs-versions.md +55 -0
  86. synapse_sdk/devtools/docs/docs/tutorial-extras/translate-your-site.md +88 -0
  87. synapse_sdk/devtools/docs/docusaurus.config.ts +148 -0
  88. synapse_sdk/devtools/docs/i18n/ko/code.json +325 -0
  89. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/agent.md +43 -0
  90. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/annotation-mixin.md +289 -0
  91. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/backend.md +420 -0
  92. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/base.md +257 -0
  93. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/core-mixin.md +417 -0
  94. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/data-collection-mixin.md +356 -0
  95. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/hitl-mixin.md +192 -0
  96. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/index.md +391 -0
  97. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/integration-mixin.md +479 -0
  98. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/ml-mixin.md +284 -0
  99. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/ray.md +342 -0
  100. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/index.md +52 -0
  101. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/plugins/models.md +114 -0
  102. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/categories.md +0 -0
  103. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/cli-usage.md +280 -0
  104. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/concepts/index.md +38 -0
  105. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/configuration.md +83 -0
  106. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/contributing.md +306 -0
  107. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/examples/index.md +29 -0
  108. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/faq.md +179 -0
  109. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/converters/index.md +30 -0
  110. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/index.md +24 -0
  111. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/utils/file.md +415 -0
  112. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/utils/network.md +378 -0
  113. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/utils/storage.md +60 -0
  114. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/utils/types.md +51 -0
  115. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/installation.md +94 -0
  116. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/introduction.md +47 -0
  117. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/neural-net-plugins/train-action-overview.md +815 -0
  118. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/pre-annotation-plugin-overview.md +198 -0
  119. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-action-development.md +1645 -0
  120. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-overview.md +717 -0
  121. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/pre-annotation-plugins/to-task-template-development.md +1380 -0
  122. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-action.md +948 -0
  123. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-overview.md +544 -0
  124. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/categories/upload-plugins/upload-plugin-template.md +766 -0
  125. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/export-plugins.md +1092 -0
  126. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/plugins.md +117 -0
  127. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/quickstart.md +78 -0
  128. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/troubleshooting.md +519 -0
  129. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current.json +34 -0
  130. synapse_sdk/devtools/docs/i18n/ko/docusaurus-theme-classic/footer.json +42 -0
  131. synapse_sdk/devtools/docs/i18n/ko/docusaurus-theme-classic/navbar.json +18 -0
  132. synapse_sdk/devtools/docs/package-lock.json +18784 -0
  133. synapse_sdk/devtools/docs/package.json +48 -0
  134. synapse_sdk/devtools/docs/sidebars.ts +122 -0
  135. synapse_sdk/devtools/docs/src/components/HomepageFeatures/index.tsx +71 -0
  136. synapse_sdk/devtools/docs/src/components/HomepageFeatures/styles.module.css +11 -0
  137. synapse_sdk/devtools/docs/src/css/custom.css +30 -0
  138. synapse_sdk/devtools/docs/src/pages/index.module.css +23 -0
  139. synapse_sdk/devtools/docs/src/pages/index.tsx +21 -0
  140. synapse_sdk/devtools/docs/src/pages/markdown-page.md +7 -0
  141. synapse_sdk/devtools/docs/static/.nojekyll +0 -0
  142. synapse_sdk/devtools/docs/static/img/docusaurus-social-card.jpg +0 -0
  143. synapse_sdk/devtools/docs/static/img/docusaurus.png +0 -0
  144. synapse_sdk/devtools/docs/static/img/favicon.ico +0 -0
  145. synapse_sdk/devtools/docs/static/img/logo.png +0 -0
  146. synapse_sdk/devtools/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
  147. synapse_sdk/devtools/docs/static/img/undraw_docusaurus_react.svg +170 -0
  148. synapse_sdk/devtools/docs/static/img/undraw_docusaurus_tree.svg +40 -0
  149. synapse_sdk/devtools/docs/tsconfig.json +8 -0
  150. synapse_sdk/devtools/server.py +41 -0
  151. synapse_sdk/devtools/streamlit_app/__init__.py +5 -0
  152. synapse_sdk/devtools/streamlit_app/app.py +128 -0
  153. synapse_sdk/devtools/streamlit_app/services/__init__.py +11 -0
  154. synapse_sdk/devtools/streamlit_app/services/job_service.py +233 -0
  155. synapse_sdk/devtools/streamlit_app/services/plugin_service.py +236 -0
  156. synapse_sdk/devtools/streamlit_app/services/serve_service.py +95 -0
  157. synapse_sdk/devtools/streamlit_app/ui/__init__.py +15 -0
  158. synapse_sdk/devtools/streamlit_app/ui/config_tab.py +76 -0
  159. synapse_sdk/devtools/streamlit_app/ui/deployment_tab.py +66 -0
  160. synapse_sdk/devtools/streamlit_app/ui/http_tab.py +125 -0
  161. synapse_sdk/devtools/streamlit_app/ui/jobs_tab.py +573 -0
  162. synapse_sdk/devtools/streamlit_app/ui/serve_tab.py +346 -0
  163. synapse_sdk/devtools/streamlit_app/ui/status_bar.py +118 -0
  164. synapse_sdk/devtools/streamlit_app/utils/__init__.py +40 -0
  165. synapse_sdk/devtools/streamlit_app/utils/json_viewer.py +197 -0
  166. synapse_sdk/devtools/streamlit_app/utils/log_formatter.py +38 -0
  167. synapse_sdk/devtools/streamlit_app/utils/styles.py +241 -0
  168. synapse_sdk/devtools/streamlit_app/utils/ui_components.py +289 -0
  169. synapse_sdk/devtools/streamlit_app.py +10 -0
  170. synapse_sdk/loggers.py +65 -7
  171. synapse_sdk/plugins/README.md +1340 -0
  172. synapse_sdk/plugins/categories/base.py +73 -11
  173. synapse_sdk/plugins/categories/data_validation/actions/validation.py +72 -0
  174. synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +33 -5
  175. synapse_sdk/plugins/categories/export/actions/__init__.py +3 -0
  176. synapse_sdk/plugins/categories/export/actions/export/__init__.py +28 -0
  177. synapse_sdk/plugins/categories/export/actions/export/action.py +165 -0
  178. synapse_sdk/plugins/categories/export/actions/export/enums.py +113 -0
  179. synapse_sdk/plugins/categories/export/actions/export/exceptions.py +53 -0
  180. synapse_sdk/plugins/categories/export/actions/export/models.py +74 -0
  181. synapse_sdk/plugins/categories/export/actions/export/run.py +195 -0
  182. synapse_sdk/plugins/categories/export/actions/{export.py → export/utils.py} +47 -82
  183. synapse_sdk/plugins/categories/export/templates/config.yaml +19 -1
  184. synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +390 -0
  185. synapse_sdk/plugins/categories/export/templates/plugin/export.py +153 -129
  186. synapse_sdk/plugins/categories/neural_net/actions/deployment.py +9 -62
  187. synapse_sdk/plugins/categories/neural_net/actions/train.py +1062 -32
  188. synapse_sdk/plugins/categories/neural_net/actions/tune.py +534 -0
  189. synapse_sdk/plugins/categories/neural_net/templates/config.yaml +27 -5
  190. synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +26 -10
  191. synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
  192. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
  193. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/action.py +10 -0
  194. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
  195. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +145 -0
  196. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
  197. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
  198. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
  199. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +97 -0
  200. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +250 -0
  201. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
  202. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
  203. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +287 -0
  204. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
  205. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
  206. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +87 -0
  207. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +127 -0
  208. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
  209. synapse_sdk/plugins/categories/pre_annotation/actions/to_task.py +966 -0
  210. synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +19 -0
  211. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/to_task.py +40 -0
  212. synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +19 -0
  213. synapse_sdk/plugins/categories/upload/actions/upload/action.py +232 -0
  214. synapse_sdk/plugins/categories/upload/actions/upload/context.py +185 -0
  215. synapse_sdk/plugins/categories/upload/actions/upload/enums.py +471 -0
  216. synapse_sdk/plugins/categories/upload/actions/upload/exceptions.py +36 -0
  217. synapse_sdk/plugins/categories/upload/actions/upload/factory.py +138 -0
  218. synapse_sdk/plugins/categories/upload/actions/upload/models.py +203 -0
  219. synapse_sdk/plugins/categories/upload/actions/upload/orchestrator.py +183 -0
  220. synapse_sdk/plugins/categories/upload/actions/upload/registry.py +113 -0
  221. synapse_sdk/plugins/categories/upload/actions/upload/run.py +179 -0
  222. synapse_sdk/plugins/categories/upload/actions/upload/steps/__init__.py +1 -0
  223. synapse_sdk/plugins/categories/upload/actions/upload/steps/base.py +107 -0
  224. synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +62 -0
  225. synapse_sdk/plugins/categories/upload/actions/upload/steps/collection.py +63 -0
  226. synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +84 -0
  227. synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +82 -0
  228. synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +235 -0
  229. synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +203 -0
  230. synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +97 -0
  231. synapse_sdk/plugins/categories/upload/actions/upload/steps/validate.py +71 -0
  232. synapse_sdk/plugins/categories/upload/actions/upload/strategies/__init__.py +1 -0
  233. synapse_sdk/plugins/categories/upload/actions/upload/strategies/base.py +82 -0
  234. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/__init__.py +1 -0
  235. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/batch.py +39 -0
  236. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/single.py +29 -0
  237. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/__init__.py +1 -0
  238. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +258 -0
  239. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +281 -0
  240. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/__init__.py +1 -0
  241. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/excel.py +174 -0
  242. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/none.py +16 -0
  243. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/__init__.py +1 -0
  244. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/sync.py +84 -0
  245. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/__init__.py +1 -0
  246. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +60 -0
  247. synapse_sdk/plugins/categories/upload/actions/upload/utils.py +250 -0
  248. synapse_sdk/plugins/categories/upload/templates/README.md +470 -0
  249. synapse_sdk/plugins/categories/upload/templates/config.yaml +29 -2
  250. synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +294 -0
  251. synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +88 -30
  252. synapse_sdk/plugins/models.py +122 -16
  253. synapse_sdk/plugins/templates/plugin-config-schema.json +406 -0
  254. synapse_sdk/plugins/templates/schema.json +491 -0
  255. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +1 -1
  256. synapse_sdk/plugins/utils/__init__.py +46 -0
  257. synapse_sdk/plugins/utils/actions.py +119 -0
  258. synapse_sdk/plugins/utils/config.py +203 -0
  259. synapse_sdk/plugins/{utils.py → utils/legacy.py} +26 -46
  260. synapse_sdk/plugins/utils/ray_gcs.py +66 -0
  261. synapse_sdk/plugins/utils/registry.py +58 -0
  262. synapse_sdk/shared/__init__.py +25 -0
  263. synapse_sdk/shared/enums.py +93 -0
  264. synapse_sdk/utils/converters/__init__.py +240 -0
  265. synapse_sdk/utils/converters/coco/__init__.py +0 -0
  266. synapse_sdk/utils/converters/coco/from_dm.py +322 -0
  267. synapse_sdk/utils/converters/coco/to_dm.py +215 -0
  268. synapse_sdk/utils/converters/dm/__init__.py +56 -0
  269. synapse_sdk/utils/converters/dm/from_v1.py +627 -0
  270. synapse_sdk/utils/converters/dm/to_v1.py +367 -0
  271. synapse_sdk/utils/converters/pascal/__init__.py +0 -0
  272. synapse_sdk/utils/converters/pascal/from_dm.py +244 -0
  273. synapse_sdk/utils/converters/pascal/to_dm.py +214 -0
  274. synapse_sdk/utils/converters/yolo/__init__.py +0 -0
  275. synapse_sdk/utils/converters/yolo/from_dm.py +384 -0
  276. synapse_sdk/utils/converters/yolo/to_dm.py +267 -0
  277. synapse_sdk/utils/dataset.py +46 -0
  278. synapse_sdk/utils/encryption.py +158 -0
  279. synapse_sdk/utils/file/__init__.py +39 -0
  280. synapse_sdk/utils/file/archive.py +32 -0
  281. synapse_sdk/utils/file/checksum.py +56 -0
  282. synapse_sdk/utils/file/chunking.py +31 -0
  283. synapse_sdk/utils/file/download.py +385 -0
  284. synapse_sdk/utils/file/encoding.py +40 -0
  285. synapse_sdk/utils/file/io.py +22 -0
  286. synapse_sdk/utils/file/video/__init__.py +29 -0
  287. synapse_sdk/utils/file/video/transcode.py +307 -0
  288. synapse_sdk/utils/{file.py → file.py.backup} +84 -2
  289. synapse_sdk/utils/http.py +138 -0
  290. synapse_sdk/utils/network.py +293 -0
  291. synapse_sdk/utils/storage/__init__.py +36 -2
  292. synapse_sdk/utils/storage/providers/__init__.py +141 -0
  293. synapse_sdk/utils/storage/providers/file_system.py +134 -0
  294. synapse_sdk/utils/storage/providers/http.py +190 -0
  295. synapse_sdk/utils/storage/providers/s3.py +54 -6
  296. synapse_sdk/utils/storage/providers/sftp.py +31 -0
  297. synapse_sdk/utils/storage/registry.py +6 -0
  298. synapse_sdk-2025.11.7.dist-info/METADATA +122 -0
  299. synapse_sdk-2025.11.7.dist-info/RECORD +386 -0
  300. {synapse_sdk-1.0.0a35.dist-info → synapse_sdk-2025.11.7.dist-info}/WHEEL +1 -1
  301. synapse_sdk/clients/backend/dataset.py +0 -102
  302. synapse_sdk/plugins/categories/upload/actions/upload.py +0 -293
  303. synapse_sdk-1.0.0a35.dist-info/METADATA +0 -47
  304. synapse_sdk-1.0.0a35.dist-info/RECORD +0 -137
  305. {synapse_sdk-1.0.0a35.dist-info → synapse_sdk-2025.11.7.dist-info}/entry_points.txt +0 -0
  306. {synapse_sdk-1.0.0a35.dist-info → synapse_sdk-2025.11.7.dist-info}/licenses/LICENSE +0 -0
  307. {synapse_sdk-1.0.0a35.dist-info → synapse_sdk-2025.11.7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1380 @@
1
+ ---
2
+ id: to-task-template-development
3
+ title: ToTask Template Development with AnnotationToTask
4
+ sidebar_position: 4
5
+ ---
6
+
7
+ # ToTask Template Development with AnnotationToTask
8
+
9
+ This guide is for plugin developers who want to create custom pre-annotation plugins using the `AnnotationToTask` template. The AnnotationToTask template provides a simple interface for converting data to task annotations in Synapse projects.
10
+
11
+ ## Overview
12
+
13
+ The `AnnotationToTask` template (`synapse_sdk.plugins.categories.pre_annotation.templates.plugin.to_task`) provides a structured approach to building pre-annotation plugins. It handles the workflow integration while you focus on implementing custom data conversion logic.
14
+
15
+ ### What is AnnotationToTask?
16
+
17
+ `AnnotationToTask` is a template class that defines two key conversion methods:
18
+ - **`convert_data_from_file()`**: Convert JSON data from files into task annotations
19
+ - **`convert_data_from_inference()`**: Convert model inference results into task annotations
20
+
21
+ The ToTaskAction framework automatically calls these methods during the annotation workflow, allowing you to customize how data is transformed into task objects.
22
+
23
+ ### When to Use This Template
24
+
25
+ Use the AnnotationToTask template when you need to:
26
+ - Transform external annotation data into Synapse task format
27
+ - Convert model predictions to task annotations
28
+ - Implement custom data validation and transformation logic
29
+ - Create reusable annotation conversion plugins
30
+
31
+ ## Getting Started
32
+
33
+ ### Template Structure
34
+
35
+ When you create a pre-annotation plugin using the ToTask template, you get this structure:
36
+
37
+ ```
38
+ synapse-{plugin-code}-plugin/
39
+ ├── config.yaml # Plugin metadata and configuration
40
+ ├── plugin/ # Source code directory
41
+ │ ├── __init__.py
42
+ │ └── to_task.py # AnnotationToTask implementation
43
+ ├── requirements.txt # Python dependencies
44
+ ├── pyproject.toml # Package configuration
45
+ └── README.md # Plugin documentation
46
+ ```
47
+
48
+ ### Basic Plugin Implementation
49
+
50
+ ```python
51
+ # plugin/to_task.py
52
+ class AnnotationToTask:
53
+ """Template for custom annotation conversion logic."""
54
+
55
+ def __init__(self, run, *args, **kwargs):
56
+ """Initialize the plugin task pre annotation action class.
57
+
58
+ Args:
59
+ run: Plugin run object providing logging and context.
60
+ """
61
+ self.run = run
62
+
63
+ def convert_data_from_file(
64
+ self,
65
+ primary_file_url: str,
66
+ primary_file_original_name: str,
67
+ data_file_url: str,
68
+ data_file_original_name: str,
69
+ ) -> dict:
70
+ """Convert data from a file to a task object.
71
+
72
+ Args:
73
+ primary_file_url: URL of the primary file (e.g., image being annotated)
74
+ primary_file_original_name: Original name of primary file
75
+ data_file_url: URL of the annotation data file (JSON)
76
+ data_file_original_name: Original name of annotation file
77
+
78
+ Returns:
79
+ dict: Task object with annotations in Synapse format
80
+ """
81
+ # Your custom implementation here
82
+ converted_data = {}
83
+ return converted_data
84
+
85
+ def convert_data_from_inference(self, data: dict) -> dict:
86
+ """Convert data from inference result to a task object.
87
+
88
+ Args:
89
+ data: Raw inference results from pre-processor
90
+
91
+ Returns:
92
+ dict: Task object with annotations in Synapse format
93
+ """
94
+ # Your custom implementation here
95
+ return data
96
+ ```
97
+
98
+ ## AnnotationToTask Class Reference
99
+
100
+ ### Constructor
101
+
102
+ ```python
103
+ def __init__(self, run, *args, **kwargs):
104
+ ```
105
+
106
+ **Parameters:**
107
+ - `run`: Plugin run object providing logging and context access
108
+ - Use `self.run.log_message(msg)` for logging
109
+ - Access configuration via `self.run.params`
110
+
111
+ **Usage:**
112
+ ```python
113
+ def __init__(self, run, *args, **kwargs):
114
+ self.run = run
115
+ # Initialize any custom attributes
116
+ self.confidence_threshold = 0.8
117
+ self.custom_mapping = {}
118
+ ```
119
+
120
+ ### Method: convert_data_from_file()
121
+
122
+ Converts annotation data from a JSON file into Synapse task object format.
123
+
124
+ ```python
125
+ def convert_data_from_file(
126
+ self,
127
+ primary_file_url: str,
128
+ primary_file_original_name: str,
129
+ data_file_url: str,
130
+ data_file_original_name: str,
131
+ ) -> dict:
132
+ ```
133
+
134
+ **Parameters:**
135
+ - `primary_file_url` (str): HTTP/HTTPS URL of the primary file (e.g., the image being annotated)
136
+ - `primary_file_original_name` (str): Original filename of the primary file
137
+ - `data_file_url` (str): HTTP/HTTPS URL of the annotation JSON file
138
+ - `data_file_original_name` (str): Original filename of the annotation file
139
+
140
+ **Returns:**
141
+ - `dict`: Task object containing annotations in Synapse format
142
+
143
+ **Called By:**
144
+ - `FileAnnotationStrategy` during file-based annotation workflow
145
+
146
+ **Workflow:**
147
+ 1. Download JSON data from `data_file_url`
148
+ 2. Parse and validate the JSON structure
149
+ 3. Transform data to match Synapse task object schema
150
+ 4. Return formatted task object
151
+
152
+ **Example Implementation:**
153
+
154
+ ```python
155
+ import requests
156
+ import json
157
+
158
+ def convert_data_from_file(
159
+ self,
160
+ primary_file_url: str,
161
+ primary_file_original_name: str,
162
+ data_file_url: str,
163
+ data_file_original_name: str,
164
+ ) -> dict:
165
+ """Convert COCO format annotations to Synapse task format."""
166
+
167
+ # Download annotation file
168
+ response = requests.get(data_file_url, timeout=30)
169
+ response.raise_for_status()
170
+ coco_data = response.json()
171
+
172
+ # Extract annotations
173
+ annotations = coco_data.get('annotations', [])
174
+
175
+ # Convert to Synapse format
176
+ task_objects = []
177
+ for idx, ann in enumerate(annotations):
178
+ task_object = {
179
+ 'id': f'obj_{idx}',
180
+ 'class_id': ann['category_id'],
181
+ 'type': 'bbox',
182
+ 'coordinates': {
183
+ 'x': ann['bbox'][0],
184
+ 'y': ann['bbox'][1],
185
+ 'width': ann['bbox'][2],
186
+ 'height': ann['bbox'][3]
187
+ },
188
+ 'properties': {
189
+ 'area': ann.get('area', 0),
190
+ 'iscrowd': ann.get('iscrowd', 0)
191
+ }
192
+ }
193
+ task_objects.append(task_object)
194
+
195
+ # Log conversion info
196
+ self.run.log_message(
197
+ f'Converted {len(task_objects)} COCO annotations from {data_file_original_name}'
198
+ )
199
+
200
+ return {'objects': task_objects}
201
+ ```
202
+
203
+ ### Method: convert_data_from_inference()
204
+
205
+ Converts model inference results into Synapse task object format.
206
+
207
+ ```python
208
+ def convert_data_from_inference(self, data: dict) -> dict:
209
+ ```
210
+
211
+ **Parameters:**
212
+ - `data` (dict): Raw inference results from the pre-processor plugin
213
+
214
+ **Returns:**
215
+ - `dict`: Task object containing annotations in Synapse format
216
+
217
+ **Called By:**
218
+ - `InferenceAnnotationStrategy` during inference-based annotation workflow
219
+
220
+ **Workflow:**
221
+ 1. Receive inference results from pre-processor
222
+ 2. Extract predictions, bounding boxes, classes, etc.
223
+ 3. Transform to Synapse task object schema
224
+ 4. Apply any filtering or post-processing
225
+ 5. Return formatted task object
226
+
227
+ **Example Implementation:**
228
+
229
+ ```python
230
+ def convert_data_from_inference(self, data: dict) -> dict:
231
+ """Convert YOLOv8 detection results to Synapse task format."""
232
+
233
+ # Extract detections from inference results
234
+ detections = data.get('detections', [])
235
+
236
+ # Filter by confidence threshold
237
+ confidence_threshold = 0.5
238
+ task_objects = []
239
+
240
+ for idx, det in enumerate(detections):
241
+ confidence = det.get('confidence', 0)
242
+
243
+ # Skip low-confidence detections
244
+ if confidence < confidence_threshold:
245
+ continue
246
+
247
+ # Convert to Synapse format
248
+ task_object = {
249
+ 'id': f'det_{idx}',
250
+ 'class_id': det['class_id'],
251
+ 'type': 'bbox',
252
+ 'coordinates': {
253
+ 'x': det['bbox']['x'],
254
+ 'y': det['bbox']['y'],
255
+ 'width': det['bbox']['width'],
256
+ 'height': det['bbox']['height']
257
+ },
258
+ 'properties': {
259
+ 'confidence': confidence,
260
+ 'class_name': det.get('class_name', 'unknown')
261
+ }
262
+ }
263
+ task_objects.append(task_object)
264
+
265
+ # Log conversion info
266
+ self.run.log_message(
267
+ f'Converted {len(task_objects)} detections '
268
+ f'(filtered from {len(detections)} total)'
269
+ )
270
+
271
+ return {'objects': task_objects}
272
+ ```
273
+
274
+ ## Using SDK Data Converters
275
+
276
+ The Synapse SDK provides built-in data converters that handle common annotation formats (COCO, YOLO, Pascal VOC). Instead of writing custom parsing logic, you can leverage these converters in your templates for faster development and better reliability.
277
+
278
+ ### Why Use SDK Converters?
279
+
280
+ - **Proven & Tested**: Converters are maintained and tested by the SDK team
281
+ - **Standard Formats**: Support for COCO, YOLO, Pascal VOC out of the box
282
+ - **Less Code**: Avoid reimplementing format parsers
283
+ - **Consistent**: Same conversion logic across all plugins
284
+ - **Error Handling**: Built-in validation and error messages
285
+
286
+ ### Available Converters
287
+
288
+ | Converter | Format | Direction | Module Path | Use Case |
289
+ |-----------|--------|-----------|-------------|----------|
290
+ | `COCOToDMConverter` | COCO JSON | External → DM | `synapse_sdk.utils.converters.coco` | COCO format annotations |
291
+ | `YOLOToDMConverter` | YOLO .txt | External → DM | `synapse_sdk.utils.converters.yolo` | YOLO format labels |
292
+ | `PascalToDMConverter` | Pascal VOC XML | External → DM | `synapse_sdk.utils.converters.pascal` | Pascal VOC annotations |
293
+ | `DMV2ToV1Converter` | DM v2 | DM v2 → DM v1 | `synapse_sdk.utils.converters.dm` | Version conversion |
294
+ | `DMV1ToV2Converter` | DM v1 | DM v1 → DM v2 | `synapse_sdk.utils.converters.dm` | Version conversion |
295
+
296
+ **DM Format**: Synapse's internal Data Manager format (what task objects use)
297
+
298
+ ### Using Converters in Templates
299
+
300
+ All To-DM converters provide a `convert_single_file()` method specifically designed for template usage.
301
+
302
+ #### In convert_data_from_file()
303
+
304
+ ```python
305
+ import requests
306
+ from synapse_sdk.utils.converters.coco import COCOToDMConverter
307
+
308
+ class AnnotationToTask:
309
+ def convert_data_from_file(
310
+ self,
311
+ primary_file_url: str,
312
+ primary_file_original_name: str,
313
+ data_file_url: str,
314
+ data_file_original_name: str,
315
+ ) -> dict:
316
+ """Convert COCO annotations using SDK converter."""
317
+
318
+ # Download annotation file
319
+ response = requests.get(data_file_url, timeout=30)
320
+ response.raise_for_status()
321
+ coco_data = response.json()
322
+
323
+ # Create converter in single-file mode
324
+ converter = COCOToDMConverter(is_single_conversion=True)
325
+
326
+ # Create a mock file object with the image path
327
+ class FileObj:
328
+ def __init__(self, name):
329
+ self.name = name
330
+
331
+ # Convert using SDK converter
332
+ result = converter.convert_single_file(
333
+ data=coco_data,
334
+ original_file=FileObj(primary_file_url),
335
+ original_image_name=primary_file_original_name
336
+ )
337
+
338
+ # Return the DM format data
339
+ return result['dm_json']
340
+ ```
341
+
342
+ #### In convert_data_from_inference()
343
+
344
+ ```python
345
+ from synapse_sdk.utils.converters.dm import DMV2ToV1Converter
346
+
347
+ class AnnotationToTask:
348
+ def convert_data_from_inference(self, data: dict) -> dict:
349
+ """Convert inference results with optional DM version conversion."""
350
+
351
+ # Your inference result processing
352
+ dm_v2_data = self._process_inference_results(data)
353
+
354
+ # Optionally convert DM v2 to v1 if needed
355
+ if self._needs_v1_format():
356
+ converter = DMV2ToV1Converter(new_dm_data=dm_v2_data)
357
+ dm_v1_data = converter.convert()
358
+ return dm_v1_data
359
+
360
+ return dm_v2_data
361
+ ```
362
+
363
+ ### Converter Examples
364
+
365
+ #### Example 1: COCO Converter
366
+
367
+ Complete implementation using `COCOToDMConverter`:
368
+
369
+ ```python
370
+ # plugin/to_task.py
371
+ import requests
372
+ from synapse_sdk.utils.converters.coco import COCOToDMConverter
373
+
374
+ class AnnotationToTask:
375
+ """Use SDK COCO converter for annotation conversion."""
376
+
377
+ def __init__(self, run, *args, **kwargs):
378
+ self.run = run
379
+
380
+ def convert_data_from_file(
381
+ self,
382
+ primary_file_url: str,
383
+ primary_file_original_name: str,
384
+ data_file_url: str,
385
+ data_file_original_name: str,
386
+ ) -> dict:
387
+ """Convert COCO JSON to Synapse task format using SDK converter."""
388
+
389
+ try:
390
+ # Download COCO annotation file
391
+ self.run.log_message(f'Downloading COCO annotations: {data_file_url}')
392
+ response = requests.get(data_file_url, timeout=30)
393
+ response.raise_for_status()
394
+ coco_data = response.json()
395
+
396
+ # Validate COCO structure
397
+ if 'annotations' not in coco_data or 'images' not in coco_data:
398
+ raise ValueError('Invalid COCO format: missing required fields')
399
+
400
+ # Create converter for single file conversion
401
+ converter = COCOToDMConverter(is_single_conversion=True)
402
+
403
+ # Create file object
404
+ class MockFile:
405
+ def __init__(self, path):
406
+ self.name = path
407
+
408
+ # Convert using SDK converter
409
+ result = converter.convert_single_file(
410
+ data=coco_data,
411
+ original_file=MockFile(primary_file_url),
412
+ original_image_name=primary_file_original_name
413
+ )
414
+
415
+ self.run.log_message(
416
+ f'Successfully converted COCO data using SDK converter'
417
+ )
418
+
419
+ # Return DM format
420
+ return result['dm_json']
421
+
422
+ except requests.RequestException as e:
423
+ self.run.log_message(f'Failed to download annotations: {str(e)}')
424
+ raise
425
+ except ValueError as e:
426
+ self.run.log_message(f'Invalid COCO data: {str(e)}')
427
+ raise
428
+ except Exception as e:
429
+ self.run.log_message(f'Conversion failed: {str(e)}')
430
+ raise
431
+
432
+ def convert_data_from_inference(self, data: dict) -> dict:
433
+ """Not used for this plugin."""
434
+ return data
435
+ ```
436
+
437
+ **Supported COCO Features:**
438
+ - Bounding boxes
439
+ - Keypoints
440
+ - Groups (bbox + keypoints)
441
+ - Category mapping
442
+ - Attributes
443
+
444
+ #### Example 2: YOLO Converter
445
+
446
+ Complete implementation using `YOLOToDMConverter`:
447
+
448
+ ```python
449
+ # plugin/to_task.py
450
+ import requests
451
+ from synapse_sdk.utils.converters.yolo import YOLOToDMConverter
452
+
453
+ class AnnotationToTask:
454
+ """Use SDK YOLO converter for label conversion."""
455
+
456
+ def __init__(self, run, *args, **kwargs):
457
+ self.run = run
458
+ # YOLO class names (must match your model)
459
+ self.class_names = ['person', 'car', 'truck', 'bicycle']
460
+
461
+ def convert_data_from_file(
462
+ self,
463
+ primary_file_url: str,
464
+ primary_file_original_name: str,
465
+ data_file_url: str,
466
+ data_file_original_name: str,
467
+ ) -> dict:
468
+ """Convert YOLO labels to Synapse task format using SDK converter."""
469
+
470
+ try:
471
+ # Download YOLO label file
472
+ self.run.log_message(f'Downloading YOLO labels: {data_file_url}')
473
+ response = requests.get(data_file_url, timeout=30)
474
+ response.raise_for_status()
475
+ label_text = response.text
476
+
477
+ # Parse label lines
478
+ label_lines = [line.strip() for line in label_text.splitlines() if line.strip()]
479
+
480
+ # Create converter with class names
481
+ converter = YOLOToDMConverter(
482
+ is_single_conversion=True,
483
+ class_names=self.class_names
484
+ )
485
+
486
+ # Create file object
487
+ class MockFile:
488
+ def __init__(self, path):
489
+ self.name = path
490
+
491
+ # Convert using SDK converter
492
+ result = converter.convert_single_file(
493
+ data=label_lines, # List of label strings
494
+ original_file=MockFile(primary_file_url)
495
+ )
496
+
497
+ self.run.log_message(
498
+ f'Successfully converted {len(label_lines)} YOLO labels'
499
+ )
500
+
501
+ return result['dm_json']
502
+
503
+ except Exception as e:
504
+ self.run.log_message(f'YOLO conversion failed: {str(e)}')
505
+ raise
506
+
507
+ def convert_data_from_inference(self, data: dict) -> dict:
508
+ """Not used for this plugin."""
509
+ return data
510
+ ```
511
+
512
+ **Supported YOLO Features:**
513
+ - Bounding boxes (standard YOLO format)
514
+ - Polygons (segmentation format)
515
+ - Keypoints (pose estimation format)
516
+ - Automatic coordinate denormalization
517
+ - Class name mapping
518
+
519
+ #### Example 3: Pascal VOC Converter
520
+
521
+ Complete implementation using `PascalToDMConverter`:
522
+
523
+ ```python
524
+ # plugin/to_task.py
525
+ import requests
526
+ from synapse_sdk.utils.converters.pascal import PascalToDMConverter
527
+
528
+ class AnnotationToTask:
529
+ """Use SDK Pascal VOC converter for XML annotation conversion."""
530
+
531
+ def __init__(self, run, *args, **kwargs):
532
+ self.run = run
533
+
534
+ def convert_data_from_file(
535
+ self,
536
+ primary_file_url: str,
537
+ primary_file_original_name: str,
538
+ data_file_url: str,
539
+ data_file_original_name: str,
540
+ ) -> dict:
541
+ """Convert Pascal VOC XML to Synapse task format using SDK converter."""
542
+
543
+ try:
544
+ # Download Pascal VOC XML file
545
+ self.run.log_message(f'Downloading Pascal VOC XML: {data_file_url}')
546
+ response = requests.get(data_file_url, timeout=30)
547
+ response.raise_for_status()
548
+ xml_content = response.text
549
+
550
+ # Create converter
551
+ converter = PascalToDMConverter(is_single_conversion=True)
552
+
553
+ # Create file object
554
+ class MockFile:
555
+ def __init__(self, path):
556
+ self.name = path
557
+
558
+ # Convert using SDK converter
559
+ result = converter.convert_single_file(
560
+ data=xml_content, # XML string
561
+ original_file=MockFile(primary_file_url)
562
+ )
563
+
564
+ self.run.log_message('Successfully converted Pascal VOC annotations')
565
+
566
+ return result['dm_json']
567
+
568
+ except Exception as e:
569
+ self.run.log_message(f'Pascal VOC conversion failed: {str(e)}')
570
+ raise
571
+
572
+ def convert_data_from_inference(self, data: dict) -> dict:
573
+ """Not used for this plugin."""
574
+ return data
575
+ ```
576
+
577
+ **Supported Pascal VOC Features:**
578
+ - Bounding boxes (xmin, ymin, xmax, ymax)
579
+ - Object names/classes
580
+ - Automatic width/height calculation
581
+ - XML parsing and validation
582
+
583
+ ### Best Practices with Converters
584
+
585
+ #### 1. When to Use Converters
586
+
587
+ **Use SDK Converters When:**
588
+ - Working with standard formats (COCO, YOLO, Pascal VOC)
589
+ - Need reliable, tested conversion logic
590
+ - Want to minimize maintenance burden
591
+ - Working with complex formats (COCO with keypoints, YOLO segmentation)
592
+
593
+ **Write Custom Code When:**
594
+ - Format is non-standard or proprietary
595
+ - Need special preprocessing before conversion
596
+ - Converter doesn't support your specific variant
597
+ - Performance optimization is critical
598
+
599
+ #### 2. Error Handling with Converters
600
+
601
+ Always wrap converter calls in try-except blocks:
602
+
603
+ ```python
604
+ def convert_data_from_file(self, *args) -> dict:
605
+ try:
606
+ converter = COCOToDMConverter(is_single_conversion=True)
607
+ result = converter.convert_single_file(...)
608
+ return result['dm_json']
609
+
610
+ except ValueError as e:
611
+ # Validation errors from converter
612
+ self.run.log_message(f'Invalid data format: {str(e)}')
613
+ raise
614
+
615
+ except KeyError as e:
616
+ # Missing required fields
617
+ self.run.log_message(f'Missing field in result: {str(e)}')
618
+ raise
619
+
620
+ except Exception as e:
621
+ # Unexpected errors
622
+ self.run.log_message(f'Converter error: {str(e)}')
623
+ raise
624
+ ```
625
+
626
+ #### 3. Combining Converters with Custom Logic
627
+
628
+ You can post-process converter output:
629
+
630
+ ```python
631
+ def convert_data_from_file(self, *args) -> dict:
632
+ # Use converter for basic conversion
633
+ converter = YOLOToDMConverter(
634
+ is_single_conversion=True,
635
+ class_names=self.class_names
636
+ )
637
+ result = converter.convert_single_file(...)
638
+ dm_data = result['dm_json']
639
+
640
+ # Add custom post-processing
641
+ for img in dm_data.get('images', []):
642
+ for bbox in img.get('bounding_box', []):
643
+ # Add custom attributes
644
+ bbox['attrs'].append({
645
+ 'name': 'source',
646
+ 'value': 'yolo_model_v2'
647
+ })
648
+
649
+ # Filter by size
650
+ if bbox['data'][2] < 10 or bbox['data'][3] < 10:
651
+ # Mark small boxes
652
+ bbox['attrs'].append({
653
+ 'name': 'too_small',
654
+ 'value': True
655
+ })
656
+
657
+ return dm_data
658
+ ```
659
+
660
+ #### 4. Performance Considerations
661
+
662
+ **Converters are optimized but:**
663
+ - Download files efficiently (use timeouts, streaming if large)
664
+ - Cache converter instances if processing multiple files
665
+ - Log conversion progress for monitoring
666
+
667
+ ```python
668
+ def __init__(self, run, *args, **kwargs):
669
+ self.run = run
670
+ # Cache converter instance
671
+ self.coco_converter = COCOToDMConverter(is_single_conversion=True)
672
+
673
+ def convert_data_from_file(self, *args) -> dict:
674
+ # Reuse cached converter
675
+ result = self.coco_converter.convert_single_file(...)
676
+ return result['dm_json']
677
+ ```
678
+
679
+ #### 5. Testing with Converters
680
+
681
+ Test both converter integration and edge cases:
682
+
683
+ ```python
684
+ # test_to_task.py
685
+ import pytest
686
+ from plugin.to_task import AnnotationToTask
687
+
688
+ class MockRun:
689
+ def log_message(self, msg):
690
+ print(msg)
691
+
692
+ def test_coco_converter_integration():
693
+ """Test COCO converter integration."""
694
+ converter = AnnotationToTask(MockRun())
695
+
696
+ # Test with valid COCO data
697
+ coco_data = {
698
+ 'images': [{'id': 1, 'file_name': 'test.jpg'}],
699
+ 'annotations': [{
700
+ 'id': 1,
701
+ 'image_id': 1,
702
+ 'category_id': 1,
703
+ 'bbox': [10, 20, 100, 200]
704
+ }],
705
+ 'categories': [{'id': 1, 'name': 'person'}]
706
+ }
707
+
708
+ result = converter._convert_with_coco_converter(coco_data, 'test.jpg')
709
+
710
+ # Verify DM structure
711
+ assert 'images' in result
712
+ assert len(result['images']) == 1
713
+ assert 'bounding_box' in result['images'][0]
714
+
715
+ def test_invalid_format_handling():
716
+ """Test error handling for invalid data."""
717
+ converter = AnnotationToTask(MockRun())
718
+
719
+ # Test with invalid COCO data
720
+ invalid_data = {'invalid': 'data'}
721
+
722
+ with pytest.raises(ValueError):
723
+ converter._convert_with_coco_converter(invalid_data, 'test.jpg')
724
+ ```
725
+
726
+ ### Converter API Reference
727
+
728
+ #### COCOToDMConverter.convert_single_file()
729
+
730
+ ```python
731
+ def convert_single_file(
732
+ data: Dict[str, Any],
733
+ original_file: IO,
734
+ original_image_name: str
735
+ ) -> Dict[str, Any]:
736
+ ```
737
+
738
+ **Parameters:**
739
+ - `data`: COCO format dictionary (JSON content)
740
+ - `original_file`: File object with `.name` attribute
741
+ - `original_image_name`: Name of the image file
742
+
743
+ **Returns:**
744
+ ```python
745
+ {
746
+ 'dm_json': {...}, # DM format data
747
+ 'image_path': str, # Path from file object
748
+ 'image_name': str # Basename of image
749
+ }
750
+ ```
751
+
752
+ #### YOLOToDMConverter.convert_single_file()
753
+
754
+ ```python
755
+ def convert_single_file(
756
+ data: List[str],
757
+ original_file: IO
758
+ ) -> Dict[str, Any]:
759
+ ```
760
+
761
+ **Parameters:**
762
+ - `data`: List of YOLO label lines (strings from .txt file)
763
+ - `original_file`: File object with `.name` attribute
764
+
765
+ **Returns:**
766
+ ```python
767
+ {
768
+ 'dm_json': {...}, # DM format data
769
+ 'image_path': str, # Path from file object
770
+ 'image_name': str # Basename of image
771
+ }
772
+ ```
773
+
774
+ #### PascalToDMConverter.convert_single_file()
775
+
776
+ ```python
777
+ def convert_single_file(
778
+ data: str,
779
+ original_file: IO
780
+ ) -> Dict[str, Any]:
781
+ ```
782
+
783
+ **Parameters:**
784
+ - `data`: Pascal VOC XML content as string
785
+ - `original_file`: File object with `.name` attribute
786
+
787
+ **Returns:**
788
+ ```python
789
+ {
790
+ 'dm_json': {...}, # DM format data
791
+ 'image_path': str, # Path from file object
792
+ 'image_name': str # Basename of image
793
+ }
794
+ ```
795
+
796
+ ## Complete Examples
797
+
798
+ ### Example 1: COCO Format Annotation Plugin
799
+
800
+ A complete plugin for converting COCO format annotations to Synapse tasks.
801
+
802
+ ```python
803
+ # plugin/to_task.py
804
+ import requests
805
+ import json
806
+ from typing import Dict, List
807
+
808
+ class AnnotationToTask:
809
+ """Convert COCO format annotations to Synapse task objects."""
810
+
811
+ def __init__(self, run, *args, **kwargs):
812
+ self.run = run
813
+ # COCO category ID to Synapse class ID mapping
814
+ self.category_mapping = {
815
+ 1: 1, # person
816
+ 2: 2, # bicycle
817
+ 3: 3, # car
818
+ # Add more mappings as needed
819
+ }
820
+
821
+ def convert_data_from_file(
822
+ self,
823
+ primary_file_url: str,
824
+ primary_file_original_name: str,
825
+ data_file_url: str,
826
+ data_file_original_name: str,
827
+ ) -> Dict:
828
+ """Convert COCO JSON file to Synapse task format."""
829
+
830
+ try:
831
+ # Download COCO annotation file
832
+ self.run.log_message(f'Downloading: {data_file_url}')
833
+ response = requests.get(data_file_url, timeout=30)
834
+ response.raise_for_status()
835
+ coco_data = response.json()
836
+
837
+ # Validate COCO structure
838
+ if 'annotations' not in coco_data:
839
+ raise ValueError('Invalid COCO format: missing annotations')
840
+
841
+ # Convert annotations
842
+ task_objects = self._convert_coco_annotations(
843
+ coco_data['annotations']
844
+ )
845
+
846
+ self.run.log_message(
847
+ f'Successfully converted {len(task_objects)} annotations'
848
+ )
849
+
850
+ return {
851
+ 'objects': task_objects,
852
+ 'metadata': {
853
+ 'source': 'coco',
854
+ 'file': data_file_original_name
855
+ }
856
+ }
857
+
858
+ except requests.RequestException as e:
859
+ self.run.log_message(f'Failed to download file: {str(e)}')
860
+ raise
861
+ except json.JSONDecodeError as e:
862
+ self.run.log_message(f'Invalid JSON format: {str(e)}')
863
+ raise
864
+ except Exception as e:
865
+ self.run.log_message(f'Conversion failed: {str(e)}')
866
+ raise
867
+
868
+ def _convert_coco_annotations(self, annotations: List[Dict]) -> List[Dict]:
869
+ """Convert COCO annotations to Synapse task objects."""
870
+ task_objects = []
871
+
872
+ for idx, ann in enumerate(annotations):
873
+ # Map COCO category to Synapse class
874
+ coco_category = ann.get('category_id')
875
+ synapse_class = self.category_mapping.get(coco_category)
876
+
877
+ if not synapse_class:
878
+ self.run.log_message(
879
+ f'Warning: Unmapped category {coco_category}, skipping'
880
+ )
881
+ continue
882
+
883
+ # Convert bbox format: [x, y, width, height]
884
+ bbox = ann.get('bbox', [])
885
+ if len(bbox) != 4:
886
+ continue
887
+
888
+ task_object = {
889
+ 'id': f'coco_{ann.get("id", idx)}',
890
+ 'class_id': synapse_class,
891
+ 'type': 'bbox',
892
+ 'coordinates': {
893
+ 'x': float(bbox[0]),
894
+ 'y': float(bbox[1]),
895
+ 'width': float(bbox[2]),
896
+ 'height': float(bbox[3])
897
+ },
898
+ 'properties': {
899
+ 'area': ann.get('area', 0),
900
+ 'iscrowd': ann.get('iscrowd', 0),
901
+ 'original_category': coco_category
902
+ }
903
+ }
904
+ task_objects.append(task_object)
905
+
906
+ return task_objects
907
+
908
+ def convert_data_from_inference(self, data: Dict) -> Dict:
909
+ """Not used for this plugin - file-based only."""
910
+ return data
911
+ ```
912
+
913
+ ### Example 2: Object Detection Inference Plugin
914
+
915
+ A complete plugin for converting object detection model outputs.
916
+
917
+ ```python
918
+ # plugin/to_task.py
919
+ from typing import Dict, List
920
+
921
+ class AnnotationToTask:
922
+ """Convert object detection inference results to Synapse tasks."""
923
+
924
+ def __init__(self, run, *args, **kwargs):
925
+ self.run = run
926
+ # Configuration
927
+ self.confidence_threshold = 0.7
928
+ self.nms_threshold = 0.5
929
+ self.max_detections = 100
930
+
931
+ def convert_data_from_file(
932
+ self,
933
+ primary_file_url: str,
934
+ primary_file_original_name: str,
935
+ data_file_url: str,
936
+ data_file_original_name: str,
937
+ ) -> Dict:
938
+ """Not used for this plugin - inference-based only."""
939
+ return {}
940
+
941
+ def convert_data_from_inference(self, data: Dict) -> Dict:
942
+ """Convert YOLOv8 detection results to Synapse format."""
943
+
944
+ try:
945
+ # Extract predictions
946
+ predictions = data.get('predictions', [])
947
+
948
+ if not predictions:
949
+ self.run.log_message('No predictions found in inference results')
950
+ return {'objects': []}
951
+
952
+ # Filter and convert detections
953
+ task_objects = self._process_detections(predictions)
954
+
955
+ # Apply NMS if needed
956
+ if len(task_objects) > self.max_detections:
957
+ task_objects = self._apply_nms(task_objects)
958
+
959
+ self.run.log_message(
960
+ f'Converted {len(task_objects)} detections '
961
+ f'(threshold: {self.confidence_threshold})'
962
+ )
963
+
964
+ return {
965
+ 'objects': task_objects,
966
+ 'metadata': {
967
+ 'model': data.get('model_name', 'unknown'),
968
+ 'inference_time': data.get('inference_time_ms', 0),
969
+ 'confidence_threshold': self.confidence_threshold
970
+ }
971
+ }
972
+
973
+ except Exception as e:
974
+ self.run.log_message(f'Inference conversion failed: {str(e)}')
975
+ raise
976
+
977
+ def _process_detections(self, predictions: List[Dict]) -> List[Dict]:
978
+ """Process and filter detections."""
979
+ task_objects = []
980
+
981
+ for idx, pred in enumerate(predictions):
982
+ confidence = pred.get('confidence', 0.0)
983
+
984
+ # Filter by confidence
985
+ if confidence < self.confidence_threshold:
986
+ continue
987
+
988
+ # Extract bbox coordinates
989
+ bbox = pred.get('bbox', {})
990
+
991
+ task_object = {
992
+ 'id': f'det_{idx}',
993
+ 'class_id': pred.get('class_id', 0),
994
+ 'type': 'bbox',
995
+ 'coordinates': {
996
+ 'x': float(bbox.get('x', 0)),
997
+ 'y': float(bbox.get('y', 0)),
998
+ 'width': float(bbox.get('width', 0)),
999
+ 'height': float(bbox.get('height', 0))
1000
+ },
1001
+ 'properties': {
1002
+ 'confidence': float(confidence),
1003
+ 'class_name': pred.get('class_name', 'unknown'),
1004
+ 'model_version': pred.get('model_version', '1.0')
1005
+ }
1006
+ }
1007
+ task_objects.append(task_object)
1008
+
1009
+ return task_objects
1010
+
1011
+ def _apply_nms(self, detections: List[Dict]) -> List[Dict]:
1012
+ """Apply Non-Maximum Suppression to reduce overlapping boxes."""
1013
+ # Sort by confidence
1014
+ sorted_dets = sorted(
1015
+ detections,
1016
+ key=lambda x: x['properties']['confidence'],
1017
+ reverse=True
1018
+ )
1019
+
1020
+ # Return top N detections
1021
+ return sorted_dets[:self.max_detections]
1022
+ ```
1023
+
1024
+ ### Example 3: Hybrid Plugin (File + Inference)
1025
+
1026
+ A plugin supporting both annotation methods.
1027
+
1028
+ ```python
1029
+ # plugin/to_task.py
1030
+ import requests
1031
+ import json
1032
+ from typing import Dict
1033
+
1034
+ class AnnotationToTask:
1035
+ """Hybrid plugin supporting both file and inference annotation."""
1036
+
1037
+ def __init__(self, run, *args, **kwargs):
1038
+ self.run = run
1039
+ self.default_confidence = 0.8
1040
+
1041
+ def convert_data_from_file(
1042
+ self,
1043
+ primary_file_url: str,
1044
+ primary_file_original_name: str,
1045
+ data_file_url: str,
1046
+ data_file_original_name: str,
1047
+ ) -> Dict:
1048
+ """Handle custom JSON annotation format."""
1049
+
1050
+ # Download annotation file
1051
+ response = requests.get(data_file_url, timeout=30)
1052
+ response.raise_for_status()
1053
+ annotation_data = response.json()
1054
+
1055
+ # Convert from custom format
1056
+ task_objects = []
1057
+ for obj in annotation_data.get('objects', []):
1058
+ task_object = {
1059
+ 'id': obj['id'],
1060
+ 'class_id': obj['class'],
1061
+ 'type': obj.get('type', 'bbox'),
1062
+ 'coordinates': obj['coords'],
1063
+ 'properties': obj.get('props', {})
1064
+ }
1065
+ task_objects.append(task_object)
1066
+
1067
+ return {'objects': task_objects}
1068
+
1069
+ def convert_data_from_inference(self, data: Dict) -> Dict:
1070
+ """Handle inference results with validation."""
1071
+
1072
+ # Extract and validate predictions
1073
+ predictions = data.get('predictions', [])
1074
+
1075
+ task_objects = []
1076
+ for idx, pred in enumerate(predictions):
1077
+ # Validate required fields
1078
+ if not self._validate_prediction(pred):
1079
+ continue
1080
+
1081
+ task_object = {
1082
+ 'id': f'pred_{idx}',
1083
+ 'class_id': pred['class_id'],
1084
+ 'type': 'bbox',
1085
+ 'coordinates': pred['bbox'],
1086
+ 'properties': {
1087
+ 'confidence': pred.get('confidence', self.default_confidence),
1088
+ 'source': 'inference'
1089
+ }
1090
+ }
1091
+ task_objects.append(task_object)
1092
+
1093
+ return {'objects': task_objects}
1094
+
1095
+ def _validate_prediction(self, pred: Dict) -> bool:
1096
+ """Validate prediction has required fields."""
1097
+ required_fields = ['class_id', 'bbox']
1098
+ return all(field in pred for field in required_fields)
1099
+ ```
1100
+
1101
+ ## Best Practices
1102
+
1103
+ ### 1. Data Validation
1104
+
1105
+ Always validate input data before conversion:
1106
+
1107
+ ```python
1108
+ def convert_data_from_file(self, *args) -> dict:
1109
+ # Validate JSON structure
1110
+ if 'required_field' not in data:
1111
+ raise ValueError('Missing required field in annotation data')
1112
+
1113
+ # Validate data types
1114
+ if not isinstance(data['objects'], list):
1115
+ raise TypeError('Objects must be a list')
1116
+
1117
+ # Validate values
1118
+ for obj in data['objects']:
1119
+ if obj.get('confidence', 0) < 0 or obj.get('confidence', 1) > 1:
1120
+ raise ValueError('Confidence must be between 0 and 1')
1121
+ ```
1122
+
1123
+ ### 2. Error Handling
1124
+
1125
+ Implement comprehensive error handling:
1126
+
1127
+ ```python
1128
+ def convert_data_from_file(self, *args) -> dict:
1129
+ try:
1130
+ # Conversion logic
1131
+ return converted_data
1132
+
1133
+ except requests.RequestException as e:
1134
+ self.run.log_message(f'Network error: {str(e)}')
1135
+ raise
1136
+
1137
+ except json.JSONDecodeError as e:
1138
+ self.run.log_message(f'Invalid JSON: {str(e)}')
1139
+ raise
1140
+
1141
+ except KeyError as e:
1142
+ self.run.log_message(f'Missing field: {str(e)}')
1143
+ raise
1144
+
1145
+ except Exception as e:
1146
+ self.run.log_message(f'Unexpected error: {str(e)}')
1147
+ raise
1148
+ ```
1149
+
1150
+ ### 3. Logging
1151
+
1152
+ Use logging to track conversion progress:
1153
+
1154
+ ```python
1155
+ def convert_data_from_inference(self, data: dict) -> dict:
1156
+ # Log start
1157
+ self.run.log_message('Starting inference data conversion')
1158
+
1159
+ predictions = data.get('predictions', [])
1160
+ self.run.log_message(f'Processing {len(predictions)} predictions')
1161
+
1162
+ # Process data
1163
+ filtered = [p for p in predictions if p['confidence'] > 0.5]
1164
+ self.run.log_message(f'Filtered to {len(filtered)} high-confidence predictions')
1165
+
1166
+ # Log completion
1167
+ self.run.log_message('Conversion completed successfully')
1168
+
1169
+ return converted_data
1170
+ ```
1171
+
1172
+ ### 4. Configuration
1173
+
1174
+ Make your plugin configurable:
1175
+
1176
+ ```python
1177
+ class AnnotationToTask:
1178
+ def __init__(self, run, *args, **kwargs):
1179
+ self.run = run
1180
+
1181
+ # Get configuration from plugin params
1182
+ params = getattr(run, 'params', {})
1183
+ pre_processor_params = params.get('pre_processor_params', {})
1184
+
1185
+ # Set configuration
1186
+ self.confidence_threshold = pre_processor_params.get('confidence_threshold', 0.7)
1187
+ self.nms_threshold = pre_processor_params.get('nms_threshold', 0.5)
1188
+ self.max_detections = pre_processor_params.get('max_detections', 100)
1189
+ ```
1190
+
1191
+ ### 5. Testing
1192
+
1193
+ Test your conversions thoroughly:
1194
+
1195
+ ```python
1196
+ # test_to_task.py
1197
+ import pytest
1198
+ from plugin.to_task import AnnotationToTask
1199
+
1200
+ class MockRun:
1201
+ def log_message(self, msg):
1202
+ print(msg)
1203
+
1204
+ def test_convert_coco_format():
1205
+ """Test COCO format conversion."""
1206
+ converter = AnnotationToTask(MockRun())
1207
+
1208
+ # Mock COCO data
1209
+ coco_data = {
1210
+ 'annotations': [
1211
+ {
1212
+ 'id': 1,
1213
+ 'category_id': 1,
1214
+ 'bbox': [10, 20, 100, 200],
1215
+ 'area': 20000
1216
+ }
1217
+ ]
1218
+ }
1219
+
1220
+ result = converter._convert_coco_annotations(coco_data['annotations'])
1221
+
1222
+ assert len(result) == 1
1223
+ assert result[0]['class_id'] == 1
1224
+ assert result[0]['coordinates']['x'] == 10
1225
+
1226
+ def test_confidence_filtering():
1227
+ """Test confidence threshold filtering."""
1228
+ converter = AnnotationToTask(MockRun())
1229
+ converter.confidence_threshold = 0.7
1230
+
1231
+ predictions = [
1232
+ {'confidence': 0.9, 'class_id': 1, 'bbox': {}},
1233
+ {'confidence': 0.5, 'class_id': 2, 'bbox': {}}, # Below threshold
1234
+ {'confidence': 0.8, 'class_id': 3, 'bbox': {}},
1235
+ ]
1236
+
1237
+ result = converter._process_detections(predictions)
1238
+
1239
+ # Only 2 should pass threshold
1240
+ assert len(result) == 2
1241
+ ```
1242
+
1243
+ ## Integration with ToTaskAction
1244
+
1245
+ ### How Template Methods Are Called
1246
+
1247
+ Your template methods are called by the ToTaskAction framework during workflow execution:
1248
+
1249
+ **File-based Annotation Flow:**
1250
+ ```
1251
+ 1. ToTaskAction.start()
1252
+
1253
+ 2. ToTaskOrchestrator.execute_workflow()
1254
+
1255
+ 3. FileAnnotationStrategy.process_task()
1256
+
1257
+ 4. annotation_to_task = context.entrypoint(logger)
1258
+
1259
+ 5. converted_data = annotation_to_task.convert_data_from_file(...)
1260
+
1261
+ 6. client.annotate_task_data(task_id, data=converted_data)
1262
+ ```
1263
+
1264
+ **Inference-based Annotation Flow:**
1265
+ ```
1266
+ 1. ToTaskAction.start()
1267
+
1268
+ 2. ToTaskOrchestrator.execute_workflow()
1269
+
1270
+ 3. InferenceAnnotationStrategy.process_task()
1271
+
1272
+ 4. inference_result = preprocessor_api.predict(...)
1273
+
1274
+ 5. annotation_to_task = context.entrypoint(logger)
1275
+
1276
+ 6. converted_data = annotation_to_task.convert_data_from_inference(inference_result)
1277
+
1278
+ 7. client.annotate_task_data(task_id, data=converted_data)
1279
+ ```
1280
+
1281
+ ### Debugging Templates
1282
+
1283
+ When debugging your template:
1284
+
1285
+ 1. **Check Logs**: Review plugin run logs for your log messages
1286
+ 2. **Validate Returns**: Ensure return format matches Synapse task object schema
1287
+ 3. **Test Locally**: Test conversion methods independently before deploying
1288
+ 4. **Inspect Inputs**: Log input parameters to verify data being received
1289
+ 5. **Handle Errors**: Catch and log exceptions with descriptive messages
1290
+
1291
+ ```python
1292
+ def convert_data_from_file(self, *args) -> dict:
1293
+ # Debug logging
1294
+ self.run.log_message(f'Received URLs: primary={args[0]}, data={args[2]}')
1295
+
1296
+ try:
1297
+ # Your logic
1298
+ result = process_data()
1299
+
1300
+ # Validate result
1301
+ self.run.log_message(f'Converted {len(result["objects"])} objects')
1302
+
1303
+ return result
1304
+
1305
+ except Exception as e:
1306
+ # Detailed error logging
1307
+ self.run.log_message(f'Conversion failed: {type(e).__name__}: {str(e)}')
1308
+ import traceback
1309
+ self.run.log_message(traceback.format_exc())
1310
+ raise
1311
+ ```
1312
+
1313
+ ## Common Pitfalls
1314
+
1315
+ ### 1. Incorrect Return Format
1316
+
1317
+ **Wrong:**
1318
+ ```python
1319
+ def convert_data_from_file(self, *args) -> dict:
1320
+ return [obj1, obj2, obj3] # Returns list, not dict
1321
+ ```
1322
+
1323
+ **Correct:**
1324
+ ```python
1325
+ def convert_data_from_file(self, *args) -> dict:
1326
+ return {'objects': [obj1, obj2, obj3]} # Returns dict with 'objects' key
1327
+ ```
1328
+
1329
+ ### 2. Missing Error Handling
1330
+
1331
+ **Wrong:**
1332
+ ```python
1333
+ def convert_data_from_file(self, *args) -> dict:
1334
+ response = requests.get(url) # No timeout, no error handling
1335
+ data = response.json()
1336
+ return data
1337
+ ```
1338
+
1339
+ **Correct:**
1340
+ ```python
1341
+ def convert_data_from_file(self, *args) -> dict:
1342
+ try:
1343
+ response = requests.get(url, timeout=30)
1344
+ response.raise_for_status()
1345
+ data = response.json()
1346
+ return self._transform_data(data)
1347
+ except Exception as e:
1348
+ self.run.log_message(f'Error: {str(e)}')
1349
+ raise
1350
+ ```
1351
+
1352
+ ### 3. Not Using Logging
1353
+
1354
+ **Wrong:**
1355
+ ```python
1356
+ def convert_data_from_inference(self, data: dict) -> dict:
1357
+ # Silent conversion - no visibility
1358
+ return process(data)
1359
+ ```
1360
+
1361
+ **Correct:**
1362
+ ```python
1363
+ def convert_data_from_inference(self, data: dict) -> dict:
1364
+ self.run.log_message(f'Converting {len(data["predictions"])} predictions')
1365
+ result = process(data)
1366
+ self.run.log_message(f'Conversion complete: {len(result["objects"])} objects')
1367
+ return result
1368
+ ```
1369
+
1370
+ ## Related Documentation
1371
+
1372
+ - [ToTask Overview](./to-task-overview.md) - User guide for ToTask action
1373
+ - [ToTask Action Development](./to-task-action-development.md) - SDK developer guide
1374
+ - [Pre-annotation Plugin Overview](./pre-annotation-plugin-overview.md) - Category overview
1375
+ - Plugin Development Guide - General plugin development
1376
+
1377
+ ## Template Source Code
1378
+
1379
+ - Template: `synapse_sdk/plugins/categories/pre_annotation/templates/plugin/to_task.py`
1380
+ - Called by: `synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py`