dtlpy 1.113.10__py3-none-any.whl → 1.114.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. dtlpy/__init__.py +488 -488
  2. dtlpy/__version__.py +1 -1
  3. dtlpy/assets/__init__.py +26 -26
  4. dtlpy/assets/__pycache__/__init__.cpython-38.pyc +0 -0
  5. dtlpy/assets/code_server/config.yaml +2 -2
  6. dtlpy/assets/code_server/installation.sh +24 -24
  7. dtlpy/assets/code_server/launch.json +13 -13
  8. dtlpy/assets/code_server/settings.json +2 -2
  9. dtlpy/assets/main.py +53 -53
  10. dtlpy/assets/main_partial.py +18 -18
  11. dtlpy/assets/mock.json +11 -11
  12. dtlpy/assets/model_adapter.py +83 -83
  13. dtlpy/assets/package.json +61 -61
  14. dtlpy/assets/package_catalog.json +29 -29
  15. dtlpy/assets/package_gitignore +307 -307
  16. dtlpy/assets/service_runners/__init__.py +33 -33
  17. dtlpy/assets/service_runners/converter.py +96 -96
  18. dtlpy/assets/service_runners/multi_method.py +49 -49
  19. dtlpy/assets/service_runners/multi_method_annotation.py +54 -54
  20. dtlpy/assets/service_runners/multi_method_dataset.py +55 -55
  21. dtlpy/assets/service_runners/multi_method_item.py +52 -52
  22. dtlpy/assets/service_runners/multi_method_json.py +52 -52
  23. dtlpy/assets/service_runners/single_method.py +37 -37
  24. dtlpy/assets/service_runners/single_method_annotation.py +43 -43
  25. dtlpy/assets/service_runners/single_method_dataset.py +43 -43
  26. dtlpy/assets/service_runners/single_method_item.py +41 -41
  27. dtlpy/assets/service_runners/single_method_json.py +42 -42
  28. dtlpy/assets/service_runners/single_method_multi_input.py +45 -45
  29. dtlpy/assets/voc_annotation_template.xml +23 -23
  30. dtlpy/caches/base_cache.py +32 -32
  31. dtlpy/caches/cache.py +473 -473
  32. dtlpy/caches/dl_cache.py +201 -201
  33. dtlpy/caches/filesystem_cache.py +89 -89
  34. dtlpy/caches/redis_cache.py +84 -84
  35. dtlpy/dlp/__init__.py +20 -20
  36. dtlpy/dlp/cli_utilities.py +367 -367
  37. dtlpy/dlp/command_executor.py +764 -764
  38. dtlpy/dlp/dlp +1 -1
  39. dtlpy/dlp/dlp.bat +1 -1
  40. dtlpy/dlp/dlp.py +128 -128
  41. dtlpy/dlp/parser.py +651 -651
  42. dtlpy/entities/__init__.py +83 -83
  43. dtlpy/entities/analytic.py +311 -311
  44. dtlpy/entities/annotation.py +1879 -1879
  45. dtlpy/entities/annotation_collection.py +699 -699
  46. dtlpy/entities/annotation_definitions/__init__.py +20 -20
  47. dtlpy/entities/annotation_definitions/base_annotation_definition.py +100 -100
  48. dtlpy/entities/annotation_definitions/box.py +195 -195
  49. dtlpy/entities/annotation_definitions/classification.py +67 -67
  50. dtlpy/entities/annotation_definitions/comparison.py +72 -72
  51. dtlpy/entities/annotation_definitions/cube.py +204 -204
  52. dtlpy/entities/annotation_definitions/cube_3d.py +149 -149
  53. dtlpy/entities/annotation_definitions/description.py +32 -32
  54. dtlpy/entities/annotation_definitions/ellipse.py +124 -124
  55. dtlpy/entities/annotation_definitions/free_text.py +62 -62
  56. dtlpy/entities/annotation_definitions/gis.py +69 -69
  57. dtlpy/entities/annotation_definitions/note.py +139 -139
  58. dtlpy/entities/annotation_definitions/point.py +117 -117
  59. dtlpy/entities/annotation_definitions/polygon.py +182 -182
  60. dtlpy/entities/annotation_definitions/polyline.py +111 -111
  61. dtlpy/entities/annotation_definitions/pose.py +92 -92
  62. dtlpy/entities/annotation_definitions/ref_image.py +86 -86
  63. dtlpy/entities/annotation_definitions/segmentation.py +240 -240
  64. dtlpy/entities/annotation_definitions/subtitle.py +34 -34
  65. dtlpy/entities/annotation_definitions/text.py +85 -85
  66. dtlpy/entities/annotation_definitions/undefined_annotation.py +74 -74
  67. dtlpy/entities/app.py +220 -220
  68. dtlpy/entities/app_module.py +107 -107
  69. dtlpy/entities/artifact.py +174 -174
  70. dtlpy/entities/assignment.py +399 -399
  71. dtlpy/entities/base_entity.py +214 -214
  72. dtlpy/entities/bot.py +113 -113
  73. dtlpy/entities/codebase.py +296 -296
  74. dtlpy/entities/collection.py +38 -38
  75. dtlpy/entities/command.py +169 -169
  76. dtlpy/entities/compute.py +442 -442
  77. dtlpy/entities/dataset.py +1285 -1285
  78. dtlpy/entities/directory_tree.py +44 -44
  79. dtlpy/entities/dpk.py +470 -470
  80. dtlpy/entities/driver.py +222 -222
  81. dtlpy/entities/execution.py +397 -397
  82. dtlpy/entities/feature.py +124 -124
  83. dtlpy/entities/feature_set.py +145 -145
  84. dtlpy/entities/filters.py +641 -641
  85. dtlpy/entities/gis_item.py +107 -107
  86. dtlpy/entities/integration.py +184 -184
  87. dtlpy/entities/item.py +953 -953
  88. dtlpy/entities/label.py +123 -123
  89. dtlpy/entities/links.py +85 -85
  90. dtlpy/entities/message.py +175 -175
  91. dtlpy/entities/model.py +694 -691
  92. dtlpy/entities/node.py +1005 -1005
  93. dtlpy/entities/ontology.py +803 -803
  94. dtlpy/entities/organization.py +287 -287
  95. dtlpy/entities/package.py +657 -657
  96. dtlpy/entities/package_defaults.py +5 -5
  97. dtlpy/entities/package_function.py +185 -185
  98. dtlpy/entities/package_module.py +113 -113
  99. dtlpy/entities/package_slot.py +118 -118
  100. dtlpy/entities/paged_entities.py +290 -267
  101. dtlpy/entities/pipeline.py +593 -593
  102. dtlpy/entities/pipeline_execution.py +279 -279
  103. dtlpy/entities/project.py +394 -394
  104. dtlpy/entities/prompt_item.py +499 -499
  105. dtlpy/entities/recipe.py +301 -301
  106. dtlpy/entities/reflect_dict.py +102 -102
  107. dtlpy/entities/resource_execution.py +138 -138
  108. dtlpy/entities/service.py +958 -958
  109. dtlpy/entities/service_driver.py +117 -117
  110. dtlpy/entities/setting.py +294 -294
  111. dtlpy/entities/task.py +491 -491
  112. dtlpy/entities/time_series.py +143 -143
  113. dtlpy/entities/trigger.py +426 -426
  114. dtlpy/entities/user.py +118 -118
  115. dtlpy/entities/webhook.py +124 -124
  116. dtlpy/examples/__init__.py +19 -19
  117. dtlpy/examples/add_labels.py +135 -135
  118. dtlpy/examples/add_metadata_to_item.py +21 -21
  119. dtlpy/examples/annotate_items_using_model.py +65 -65
  120. dtlpy/examples/annotate_video_using_model_and_tracker.py +75 -75
  121. dtlpy/examples/annotations_convert_to_voc.py +9 -9
  122. dtlpy/examples/annotations_convert_to_yolo.py +9 -9
  123. dtlpy/examples/convert_annotation_types.py +51 -51
  124. dtlpy/examples/converter.py +143 -143
  125. dtlpy/examples/copy_annotations.py +22 -22
  126. dtlpy/examples/copy_folder.py +31 -31
  127. dtlpy/examples/create_annotations.py +51 -51
  128. dtlpy/examples/create_video_annotations.py +83 -83
  129. dtlpy/examples/delete_annotations.py +26 -26
  130. dtlpy/examples/filters.py +113 -113
  131. dtlpy/examples/move_item.py +23 -23
  132. dtlpy/examples/play_video_annotation.py +13 -13
  133. dtlpy/examples/show_item_and_mask.py +53 -53
  134. dtlpy/examples/triggers.py +49 -49
  135. dtlpy/examples/upload_batch_of_items.py +20 -20
  136. dtlpy/examples/upload_items_and_custom_format_annotations.py +55 -55
  137. dtlpy/examples/upload_items_with_modalities.py +43 -43
  138. dtlpy/examples/upload_segmentation_annotations_from_mask_image.py +44 -44
  139. dtlpy/examples/upload_yolo_format_annotations.py +70 -70
  140. dtlpy/exceptions.py +125 -125
  141. dtlpy/miscellaneous/__init__.py +20 -20
  142. dtlpy/miscellaneous/dict_differ.py +95 -95
  143. dtlpy/miscellaneous/git_utils.py +217 -217
  144. dtlpy/miscellaneous/json_utils.py +14 -14
  145. dtlpy/miscellaneous/list_print.py +105 -105
  146. dtlpy/miscellaneous/zipping.py +130 -130
  147. dtlpy/ml/__init__.py +20 -20
  148. dtlpy/ml/base_feature_extractor_adapter.py +27 -27
  149. dtlpy/ml/base_model_adapter.py +945 -940
  150. dtlpy/ml/metrics.py +461 -461
  151. dtlpy/ml/predictions_utils.py +274 -274
  152. dtlpy/ml/summary_writer.py +57 -57
  153. dtlpy/ml/train_utils.py +60 -60
  154. dtlpy/new_instance.py +252 -252
  155. dtlpy/repositories/__init__.py +56 -56
  156. dtlpy/repositories/analytics.py +85 -85
  157. dtlpy/repositories/annotations.py +916 -916
  158. dtlpy/repositories/apps.py +383 -383
  159. dtlpy/repositories/artifacts.py +452 -452
  160. dtlpy/repositories/assignments.py +599 -599
  161. dtlpy/repositories/bots.py +213 -213
  162. dtlpy/repositories/codebases.py +559 -559
  163. dtlpy/repositories/collections.py +332 -348
  164. dtlpy/repositories/commands.py +158 -158
  165. dtlpy/repositories/compositions.py +61 -61
  166. dtlpy/repositories/computes.py +434 -406
  167. dtlpy/repositories/datasets.py +1291 -1291
  168. dtlpy/repositories/downloader.py +895 -895
  169. dtlpy/repositories/dpks.py +433 -433
  170. dtlpy/repositories/drivers.py +266 -266
  171. dtlpy/repositories/executions.py +817 -817
  172. dtlpy/repositories/feature_sets.py +226 -226
  173. dtlpy/repositories/features.py +238 -238
  174. dtlpy/repositories/integrations.py +484 -484
  175. dtlpy/repositories/items.py +909 -915
  176. dtlpy/repositories/messages.py +94 -94
  177. dtlpy/repositories/models.py +877 -867
  178. dtlpy/repositories/nodes.py +80 -80
  179. dtlpy/repositories/ontologies.py +511 -511
  180. dtlpy/repositories/organizations.py +525 -525
  181. dtlpy/repositories/packages.py +1941 -1941
  182. dtlpy/repositories/pipeline_executions.py +448 -448
  183. dtlpy/repositories/pipelines.py +642 -642
  184. dtlpy/repositories/projects.py +539 -539
  185. dtlpy/repositories/recipes.py +399 -399
  186. dtlpy/repositories/resource_executions.py +137 -137
  187. dtlpy/repositories/schema.py +120 -120
  188. dtlpy/repositories/service_drivers.py +213 -213
  189. dtlpy/repositories/services.py +1704 -1704
  190. dtlpy/repositories/settings.py +339 -339
  191. dtlpy/repositories/tasks.py +1124 -1124
  192. dtlpy/repositories/times_series.py +278 -278
  193. dtlpy/repositories/triggers.py +536 -536
  194. dtlpy/repositories/upload_element.py +257 -257
  195. dtlpy/repositories/uploader.py +651 -651
  196. dtlpy/repositories/webhooks.py +249 -249
  197. dtlpy/services/__init__.py +22 -22
  198. dtlpy/services/aihttp_retry.py +131 -131
  199. dtlpy/services/api_client.py +1782 -1782
  200. dtlpy/services/api_reference.py +40 -40
  201. dtlpy/services/async_utils.py +133 -133
  202. dtlpy/services/calls_counter.py +44 -44
  203. dtlpy/services/check_sdk.py +68 -68
  204. dtlpy/services/cookie.py +115 -115
  205. dtlpy/services/create_logger.py +156 -156
  206. dtlpy/services/events.py +84 -84
  207. dtlpy/services/logins.py +235 -235
  208. dtlpy/services/reporter.py +256 -256
  209. dtlpy/services/service_defaults.py +91 -91
  210. dtlpy/utilities/__init__.py +20 -20
  211. dtlpy/utilities/annotations/__init__.py +16 -16
  212. dtlpy/utilities/annotations/annotation_converters.py +269 -269
  213. dtlpy/utilities/base_package_runner.py +264 -264
  214. dtlpy/utilities/converter.py +1650 -1650
  215. dtlpy/utilities/dataset_generators/__init__.py +1 -1
  216. dtlpy/utilities/dataset_generators/dataset_generator.py +670 -670
  217. dtlpy/utilities/dataset_generators/dataset_generator_tensorflow.py +23 -23
  218. dtlpy/utilities/dataset_generators/dataset_generator_torch.py +21 -21
  219. dtlpy/utilities/local_development/__init__.py +1 -1
  220. dtlpy/utilities/local_development/local_session.py +179 -179
  221. dtlpy/utilities/reports/__init__.py +2 -2
  222. dtlpy/utilities/reports/figures.py +343 -343
  223. dtlpy/utilities/reports/report.py +71 -71
  224. dtlpy/utilities/videos/__init__.py +17 -17
  225. dtlpy/utilities/videos/video_player.py +598 -598
  226. dtlpy/utilities/videos/videos.py +470 -470
  227. {dtlpy-1.113.10.data → dtlpy-1.114.13.data}/scripts/dlp +1 -1
  228. dtlpy-1.114.13.data/scripts/dlp.bat +2 -0
  229. {dtlpy-1.113.10.data → dtlpy-1.114.13.data}/scripts/dlp.py +128 -128
  230. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/LICENSE +200 -200
  231. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/METADATA +172 -172
  232. dtlpy-1.114.13.dist-info/RECORD +240 -0
  233. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/WHEEL +1 -1
  234. tests/features/environment.py +551 -550
  235. dtlpy-1.113.10.data/scripts/dlp.bat +0 -2
  236. dtlpy-1.113.10.dist-info/RECORD +0 -244
  237. tests/assets/__init__.py +0 -0
  238. tests/assets/models_flow/__init__.py +0 -0
  239. tests/assets/models_flow/failedmain.py +0 -52
  240. tests/assets/models_flow/main.py +0 -62
  241. tests/assets/models_flow/main_model.py +0 -54
  242. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/entry_points.txt +0 -0
  243. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/top_level.txt +0 -0
dtlpy/entities/item.py CHANGED
@@ -1,953 +1,953 @@
1
- import warnings
2
- from collections import namedtuple
3
- from enum import Enum
4
- import traceback
5
- import logging
6
- import attr
7
- import copy
8
- import os
9
- import io
10
- from .. import repositories, entities, exceptions
11
- from .annotation import ViewAnnotationOptions, ExportVersion
12
- from ..services.api_client import ApiClient
13
- from ..services.api_client import client as client_api
14
- import json
15
- from typing import List
16
- import requests
17
-
18
- logger = logging.getLogger(name='dtlpy')
19
-
20
-
21
- class ExportMetadata(Enum):
22
- FROM_JSON = 'from_json'
23
-
24
-
25
- class ItemStatus(str, Enum):
26
- COMPLETED = "completed"
27
- APPROVED = "approved"
28
- DISCARDED = "discard"
29
-
30
-
31
- @attr.s
32
- class Item(entities.BaseEntity):
33
- """
34
- Item object
35
- """
36
- # item information
37
- annotations_link = attr.ib(repr=False)
38
- dataset_url = attr.ib()
39
- thumbnail = attr.ib(repr=False)
40
- created_at = attr.ib()
41
- updated_at = attr.ib()
42
- updated_by = attr.ib()
43
- dataset_id = attr.ib()
44
- annotated = attr.ib(repr=False)
45
- metadata = attr.ib(repr=False)
46
- filename = attr.ib()
47
- stream = attr.ib(repr=False)
48
- name = attr.ib()
49
- type = attr.ib()
50
- url = attr.ib(repr=False)
51
- id = attr.ib()
52
- hidden = attr.ib(repr=False)
53
- dir = attr.ib(repr=False)
54
- spec = attr.ib()
55
- creator = attr.ib()
56
- _description = attr.ib()
57
- _src_item = attr.ib(repr=False)
58
-
59
- # name change
60
- annotations_count = attr.ib()
61
-
62
- # api
63
- _client_api = attr.ib(type=ApiClient, repr=False)
64
- _platform_dict = attr.ib(repr=False)
65
-
66
- # entities
67
- _dataset = attr.ib(repr=False)
68
- _model = attr.ib(repr=False)
69
- _project = attr.ib(repr=False)
70
- _project_id = attr.ib(repr=False)
71
-
72
- # repositories
73
- _repositories = attr.ib(repr=False)
74
-
75
- @property
76
- def createdAt(self):
77
- return self.created_at
78
-
79
- @property
80
- def datasetId(self):
81
- return self.dataset_id
82
-
83
- @staticmethod
84
- def _protected_from_json(_json, client_api, dataset=None):
85
- """
86
- Same as from_json but with try-except to catch if error
87
- :param _json: platform json
88
- :param client_api: ApiClient entity
89
- :param dataset: dataset entity
90
- :return:
91
- """
92
- try:
93
- item = Item.from_json(_json=_json,
94
- client_api=client_api,
95
- dataset=dataset)
96
- status = True
97
- except Exception:
98
- item = traceback.format_exc()
99
- status = False
100
- return status, item
101
-
102
- @classmethod
103
- def from_json(cls, _json, client_api, dataset=None, project=None, model=None, is_fetched=True):
104
- """
105
- Build an item entity object from a json
106
-
107
- :param dtlpy.entities.project.Project project: project entity
108
- :param dict _json: _json response from host
109
- :param dtlpy.entities.dataset.Dataset dataset: dataset in which the annotation's item is located
110
- :param dtlpy.entities.dataset.Model model: the model entity if item is an artifact of a model
111
- :param dlApiClient client_api: ApiClient entity
112
- :param bool is_fetched: is Entity fetched from Platform
113
- :return: Item object
114
- :rtype: dtlpy.entities.item.Item
115
- """
116
- dataset_id = None
117
- if dataset is not None:
118
- dataset_id = _json.get('datasetId', None)
119
- if dataset.id != dataset_id and dataset_id is not None:
120
- logger.warning('Item has been fetched from a dataset that is not belong to it')
121
- dataset = None
122
- else:
123
- dataset_id = dataset.id
124
-
125
- metadata = _json.get('metadata', dict())
126
- project_id = _json.get('projectId', None)
127
- if project_id is None:
128
- project_id = project.id if project else None
129
- inst = cls(
130
- # sdk
131
- platform_dict=copy.deepcopy(_json),
132
- client_api=client_api,
133
- dataset=dataset,
134
- project=project,
135
- model=model,
136
- # params
137
- annotations_link=_json.get('annotations', None),
138
- thumbnail=_json.get('thumbnail', None),
139
- dataset_id=_json.get('datasetId', dataset_id),
140
- annotated=_json.get('annotated', None),
141
- dataset_url=_json.get('dataset', None),
142
- created_at=_json.get('createdAt', None),
143
- annotations_count=_json.get('annotationsCount', None),
144
- hidden=_json.get('hidden', False),
145
- stream=_json.get('stream', None),
146
- dir=_json.get('dir', None),
147
- filename=_json.get('filename', None),
148
- metadata=metadata,
149
- name=_json.get('name', None),
150
- type=_json.get('type', None),
151
- url=_json.get('url', None),
152
- id=_json.get('id', None),
153
- spec=_json.get('spec', None),
154
- creator=_json.get('creator', None),
155
- project_id=project_id,
156
- description=_json.get('description', None),
157
- src_item=_json.get('srcItem', None),
158
- updated_at=_json.get('updatedAt', None),
159
- updated_by=_json.get('updatedBy', None)
160
- )
161
- inst.is_fetched = is_fetched
162
- return inst
163
-
164
- def __getstate__(self):
165
- # dump state to json
166
- return self.to_json()
167
-
168
- def __setstate__(self, state):
169
- # create a new item, and update the current one with the same state
170
- # this way we can have _client_api, and all the repositories and entities which are not picklable
171
- self.__dict__.update(entities.Item.from_json(_json=state,
172
- client_api=client_api).__dict__)
173
-
174
- ############
175
- # entities #
176
- ############
177
- @property
178
- def dataset(self):
179
- if self._dataset is None:
180
- self._dataset = self.datasets.get(dataset_id=self.dataset_id, fetch=None)
181
- assert isinstance(self._dataset, entities.Dataset)
182
- return self._dataset
183
-
184
- @property
185
- def model(self):
186
- return self._model
187
-
188
- def __update_item_binary(self, _json):
189
- binary = io.BytesIO()
190
- binary.write(json.dumps(_json).encode())
191
- binary.seek(0)
192
- binary.name = self.name
193
- success, resp = client_api.gen_request(req_type='post',
194
- path=f'/items/{self.id}/revisions',
195
- files={'file': (binary.name, binary)})
196
- if not success:
197
- raise exceptions.PlatformException(resp)
198
-
199
- @property
200
- def project(self):
201
- if self._project is None:
202
- if self._dataset is None:
203
- self._dataset = self.datasets.get(dataset_id=self.dataset_id, fetch=None)
204
- self._project = self._dataset.project
205
- if self._project is None:
206
- raise exceptions.PlatformException(error='2001',
207
- message='Missing entity "project".')
208
- assert isinstance(self._project, entities.Project)
209
- return self._project
210
-
211
- @property
212
- def project_id(self):
213
- if self._project_id is None:
214
- if self._dataset is None:
215
- self._dataset = self.datasets.get(dataset_id=self.dataset_id, fetch=None)
216
- self._project_id = self._dataset.project.id
217
- return self._project_id
218
-
219
- ################
220
- # repositories #
221
- ################
222
-
223
- @_repositories.default
224
- def set_repositories(self):
225
- reps = namedtuple('repositories',
226
- field_names=['annotations', 'datasets', 'items', 'codebases', 'artifacts', 'modalities',
227
- 'features', 'assignments', 'tasks', 'resource_executions', 'collections'])
228
- reps.__new__.__defaults__ = (None, None, None, None, None, None, None, None, None)
229
-
230
- if self._dataset is None:
231
- items = repositories.Items(
232
- client_api=self._client_api,
233
- dataset=self._dataset,
234
- dataset_id=self.dataset_id,
235
- datasets=repositories.Datasets(client_api=self._client_api, project=None)
236
- )
237
- datasets = items.datasets
238
-
239
- else:
240
- items = self.dataset.items
241
- datasets = self.dataset.datasets
242
-
243
- r = reps(
244
- annotations=repositories.Annotations(
245
- client_api=self._client_api,
246
- dataset_id=self.dataset_id,
247
- item=self,
248
- dataset=self._dataset
249
- ),
250
- items=items,
251
- datasets=datasets,
252
- codebases=None,
253
- artifacts=None,
254
- modalities=Modalities(item=self),
255
- features=repositories.Features(
256
- client_api=self._client_api,
257
- project=self._project,
258
- item=self
259
- ),
260
- tasks=repositories.Tasks(
261
- client_api=self._client_api,
262
- project=self._project,
263
- dataset=self._dataset
264
- ),
265
- assignments=repositories.Assignments(
266
- client_api=self._client_api,
267
- project=self._project,
268
- dataset=self._dataset
269
- ),
270
- resource_executions=repositories.ResourceExecutions(
271
- client_api=self._client_api,
272
- project=self._project,
273
- resource=self
274
- ),
275
- collections=repositories.Collections(client_api=self._client_api, item=self, dataset=self._dataset)
276
- )
277
- return r
278
-
279
- @property
280
- def modalities(self):
281
- assert isinstance(self._repositories.modalities, Modalities)
282
- return self._repositories.modalities
283
-
284
- @property
285
- def annotations(self):
286
- assert isinstance(self._repositories.annotations, repositories.Annotations)
287
- return self._repositories.annotations
288
-
289
- @property
290
- def datasets(self):
291
- assert isinstance(self._repositories.datasets, repositories.Datasets)
292
- return self._repositories.datasets
293
-
294
- @property
295
- def assignments(self):
296
- assert isinstance(self._repositories.assignments, repositories.Assignments)
297
- return self._repositories.assignments
298
-
299
- @property
300
- def tasks(self):
301
- assert isinstance(self._repositories.tasks, repositories.Tasks)
302
- return self._repositories.tasks
303
-
304
- @property
305
- def resource_executions(self):
306
- assert isinstance(self._repositories.resource_executions, repositories.ResourceExecutions)
307
- return self._repositories.resource_executions
308
-
309
- @property
310
- def items(self):
311
- assert isinstance(self._repositories.items, repositories.Items)
312
- return self._repositories.items
313
-
314
- @property
315
- def features(self):
316
- assert isinstance(self._repositories.features, repositories.Features)
317
- return self._repositories.features
318
-
319
- @property
320
- def collections(self):
321
- assert isinstance(self._repositories.collections, repositories.Collections)
322
- return self._repositories.collections
323
-
324
- ##############
325
- # Properties #
326
- ##############
327
- @property
328
- def height(self):
329
- return self.metadata.get('system', dict()).get('height', None)
330
-
331
- @height.setter
332
- def height(self, val):
333
- if 'system' not in self.metadata:
334
- self.metadata['system'] = dict()
335
- self.metadata['system']['height'] = val
336
-
337
- @property
338
- def width(self):
339
- return self.metadata.get('system', dict()).get('width', None)
340
-
341
- @width.setter
342
- def width(self, val):
343
- if 'system' not in self.metadata:
344
- self.metadata['system'] = dict()
345
- self.metadata['system']['width'] = val
346
-
347
- @property
348
- def fps(self):
349
- return self.metadata.get('fps', None)
350
-
351
- @fps.setter
352
- def fps(self, val):
353
- self.metadata['fps'] = val
354
-
355
- @property
356
- def mimetype(self):
357
- return self.metadata.get('system', dict()).get('mimetype', None)
358
-
359
- @property
360
- def size(self):
361
- return self.metadata.get('system', dict()).get('size', None)
362
-
363
- @property
364
- def system(self):
365
- return self.metadata.get('system', dict())
366
-
367
- @property
368
- def description(self):
369
- description = None
370
- if self._description is not None:
371
- description = self._description
372
- elif 'description' in self.metadata:
373
- description = self.metadata['description'].get('text', None)
374
- return description
375
-
376
- @property
377
- def platform_url(self):
378
- return self._client_api._get_resource_url(
379
- "projects/{}/datasets/{}/items/{}".format(self.dataset.projects[-1], self.dataset.id, self.id))
380
-
381
- @description.setter
382
- def description(self, text: str):
383
- """
384
- Update Item description
385
-
386
- :param text: if None or "" description will be deleted
387
- :return
388
- """
389
- self.set_description(text=text)
390
-
391
- ###########
392
- # Functions #
393
- ###########
394
- def to_json(self):
395
- """
396
- Returns platform _json format of object
397
-
398
- :return: platform json format of object
399
- :rtype: dict
400
- """
401
- _json = attr.asdict(self,
402
- filter=attr.filters.exclude(attr.fields(Item)._repositories,
403
- attr.fields(Item)._dataset,
404
- attr.fields(Item)._model,
405
- attr.fields(Item)._project,
406
- attr.fields(Item)._client_api,
407
- attr.fields(Item)._platform_dict,
408
- attr.fields(Item).annotations_count,
409
- attr.fields(Item).dataset_url,
410
- attr.fields(Item).annotations_link,
411
- attr.fields(Item).spec,
412
- attr.fields(Item).creator,
413
- attr.fields(Item).created_at,
414
- attr.fields(Item).dataset_id,
415
- attr.fields(Item)._project_id,
416
- attr.fields(Item)._description,
417
- attr.fields(Item)._src_item,
418
- attr.fields(Item).updated_at,
419
- attr.fields(Item).updated_by
420
- ))
421
-
422
- _json.update({'annotations': self.annotations_link,
423
- 'annotationsCount': self.annotations_count,
424
- 'dataset': self.dataset_url,
425
- 'createdAt': self.created_at,
426
- 'datasetId': self.dataset_id,
427
- })
428
- if self.spec is not None:
429
- _json['spec'] = self.spec
430
- if self.creator is not None:
431
- _json['creator'] = self.creator
432
- if self._description is not None:
433
- _json['description'] = self.description
434
- if self._src_item is not None:
435
- _json['srcItem'] = self._src_item
436
-
437
- _json['updatedAt'] = self.updated_at
438
- _json['updatedBy'] = self.updated_by
439
-
440
- return _json
441
-
442
- def download(
443
- self,
444
- # download options
445
- local_path=None,
446
- file_types=None,
447
- save_locally=True,
448
- to_array=False,
449
- annotation_options: ViewAnnotationOptions = None,
450
- overwrite=False,
451
- to_items_folder=True,
452
- thickness=1,
453
- with_text=False,
454
- annotation_filters=None,
455
- alpha=1,
456
- export_version=ExportVersion.V1,
457
- dataset_lock=False,
458
- lock_timeout_sec=None,
459
- export_summary=False,
460
- ):
461
- """
462
- Download dataset by filters.
463
- Filtering the dataset for items and save them local
464
- Optional - also download annotation, mask, instance and image mask of the item
465
-
466
- :param str local_path: local folder or filename to save to.
467
- :param list file_types: a list of file type to download. e.g ['video/webm', 'video/mp4', 'image/jpeg', 'image/png']
468
- :param bool save_locally: bool. save to disk or return a buffer
469
- :param bool to_array: returns Ndarray when True and local_path = False
470
- :param list annotation_options: download annotations options: list(dl.ViewAnnotationOptions)
471
- :param dtlpy.entities.filters.Filters annotation_filters: Filters entity to filter annotations for download
472
- :param bool overwrite: optional - default = False
473
- :param bool dataset_lock: optional - default = False
474
- :param bool export_summary: optional - default = False
475
- :param int lock_timeout_sec: optional
476
- :param bool to_items_folder: Create 'items' folder and download items to it
477
- :param int thickness: optional - line thickness, if -1 annotation will be filled, default =1
478
- :param bool with_text: optional - add text to annotations, default = False
479
- :param float alpha: opacity value [0 1], default 1
480
- :param str export_version: exported items will have original extension in filename, `V1` - no original extension in filenames
481
- :return: generator of local_path per each downloaded item
482
- :rtype: generator or single item
483
-
484
- **Example**:
485
-
486
- .. code-block:: python
487
-
488
- item.download(local_path='local_path',
489
- annotation_options=dl.ViewAnnotationOptions.MASK,
490
- overwrite=False,
491
- thickness=1,
492
- with_text=False,
493
- alpha=1,
494
- save_locally=True,
495
- dataset_lock=False
496
- lock_timeout_sec=300,
497
- export_summary=False
498
- )
499
- """
500
- # if dir - concatenate local path and item name
501
- if local_path is not None:
502
- if os.path.isdir(local_path):
503
- local_path = os.path.join(local_path, self.name)
504
- else:
505
- _, ext = os.path.splitext(local_path)
506
- _, item_ext = os.path.splitext(self.name)
507
- if not ext or ext != item_ext:
508
- os.makedirs(local_path, exist_ok=True)
509
- local_path = os.path.join(local_path, self.name)
510
-
511
- # download
512
- filters = None
513
- items = self
514
- if self.type == 'dir':
515
- filters = self.datasets._bulid_folder_filter(folder_path=self.filename)
516
- items = None
517
-
518
- return self.items.download(items=items,
519
- local_path=local_path,
520
- file_types=file_types,
521
- save_locally=save_locally,
522
- to_array=to_array,
523
- annotation_options=annotation_options,
524
- overwrite=overwrite,
525
- to_items_folder=to_items_folder,
526
- annotation_filters=annotation_filters,
527
- thickness=thickness,
528
- alpha=alpha,
529
- with_text=with_text,
530
- export_version=export_version,
531
- filters=filters,
532
- dataset_lock=dataset_lock,
533
- lock_timeout_sec=lock_timeout_sec,
534
- export_summary=export_summary)
535
-
536
- def delete(self):
537
- """
538
- Delete item from platform
539
-
540
- :return: True
541
- :rtype: bool
542
- """
543
- return self.items.delete(item_id=self.id)
544
-
545
- def update(self, system_metadata=False):
546
- """
547
- Update items metadata
548
-
549
- :param bool system_metadata: bool - True, if you want to change metadata system
550
- :return: Item object
551
- :rtype: dtlpy.entities.item.Item
552
- """
553
- return self.items.update(item=self, system_metadata=system_metadata)
554
-
555
- def move(self, new_path):
556
- """
557
- Move item from one folder to another in Platform
558
- If the directory doesn't exist it will be created
559
-
560
- :param str new_path: new full path to move item to.
561
- :return: True if update successfully
562
- :rtype: bool
563
- """
564
- assert isinstance(new_path, str)
565
- if not new_path.startswith('/'):
566
- new_path = '/' + new_path
567
- if new_path.endswith('/'):
568
- self.filename = new_path + self.name
569
- else:
570
- try:
571
- self.items.get(filepath=new_path, is_dir=True)
572
- self.filename = new_path + '/' + self.name
573
- except exceptions.NotFound:
574
- self.filename = new_path
575
-
576
- return self.update(system_metadata=True)
577
-
578
- def clone(self, dst_dataset_id=None, remote_filepath=None, metadata=None, with_annotations=True,
579
- with_metadata=True, with_task_annotations_status=False, allow_many=False, wait=True):
580
- """
581
- Clone item
582
-
583
- :param str dst_dataset_id: destination dataset id
584
- :param str remote_filepath: complete filepath
585
- :param dict metadata: new metadata to add
586
- :param bool with_annotations: clone annotations
587
- :param bool with_metadata: clone metadata
588
- :param bool with_task_annotations_status: clone task annotations status
589
- :param bool allow_many: `bool` if True, using multiple clones in single dataset is allowed, (default=False)
590
- :param bool wait: wait for the command to finish
591
- :return: Item object
592
- :rtype: dtlpy.entities.item.Item
593
-
594
- **Example**:
595
-
596
- .. code-block:: python
597
-
598
- item.clone(item_id='item_id',
599
- dst_dataset_id='dist_dataset_id',
600
- with_metadata=True,
601
- with_task_annotations_status=False,
602
- with_annotations=False)
603
- """
604
- if remote_filepath is None:
605
- remote_filepath = self.filename
606
- if dst_dataset_id is None:
607
- dst_dataset_id = self.dataset_id
608
- return self.items.clone(item_id=self.id,
609
- dst_dataset_id=dst_dataset_id,
610
- remote_filepath=remote_filepath,
611
- metadata=metadata,
612
- with_annotations=with_annotations,
613
- with_metadata=with_metadata,
614
- with_task_annotations_status=with_task_annotations_status,
615
- allow_many=allow_many,
616
- wait=wait)
617
-
618
- def open_in_web(self):
619
- """
620
- Open the items in web platform
621
-
622
- :return:
623
- """
624
- self._client_api._open_in_web(url=self.platform_url)
625
-
626
- def _set_action(self, status: str, operation: str, assignment_id: str = None, task_id: str = None):
627
- """
628
- update item status
629
-
630
- :param status: str - string the describes the status
631
- :param operation: str - 'create' or 'delete'
632
- :param assignment_id: str - assignment id
633
- :param task_id: str - task id
634
-
635
- :return :True/False
636
- """
637
- if assignment_id:
638
- success = self.assignments.set_status(
639
- status=status,
640
- operation=operation,
641
- item_id=self.id,
642
- assignment_id=assignment_id
643
- )
644
- elif task_id:
645
- success = self.tasks.set_status(
646
- status=status,
647
- operation=operation,
648
- item_ids=[self.id],
649
- task_id=task_id
650
- )
651
-
652
- else:
653
- raise exceptions.PlatformException('400', 'Must provide task_id or assignment_id')
654
-
655
- return success
656
-
657
- def update_status(self, status: str, clear: bool = False, assignment_id: str = None, task_id: str = None):
658
- """
659
- update item status
660
-
661
- :param str status: "completed" ,"approved" ,"discard"
662
- :param bool clear: if true delete status
663
- :param str assignment_id: assignment id
664
- :param str task_id: task id
665
-
666
- :return :True/False
667
- :rtype: bool
668
-
669
- **Example**:
670
-
671
- .. code-block:: python
672
-
673
- item.update_status(status='complete',
674
- task_id='task_id')
675
-
676
- """
677
- if not assignment_id and not task_id:
678
- system_metadata = self.metadata.get('system', dict())
679
- if 'refs' in system_metadata:
680
- refs = system_metadata['refs']
681
- if len(refs) <= 2:
682
- for ref in refs:
683
- if ref.get('type', '') == 'assignment':
684
- assignment_id = ref['id']
685
- if ref.get('type', '') == 'task':
686
- task_id = ref['id']
687
-
688
- if assignment_id or task_id:
689
- if clear:
690
- self._set_action(status=status, operation='delete', assignment_id=assignment_id, task_id=task_id)
691
- else:
692
- self._set_action(status=status, operation='create', assignment_id=assignment_id, task_id=task_id)
693
- else:
694
- raise exceptions.PlatformException('400', 'must provide assignment_id or task_id')
695
-
696
- def status(self, assignment_id: str = None, task_id: str = None):
697
- """
698
- Get item status
699
-
700
- :param str assignment_id: assignment id
701
- :param str task_id: task id
702
-
703
- :return: status
704
- :rtype: str
705
-
706
- **Example**:
707
-
708
- .. code-block:: python
709
-
710
- status = item.status(task_id='task_id')
711
- """
712
- if not assignment_id and not task_id:
713
- raise exceptions.PlatformException('400', 'must provide assignment_id or task_id')
714
- status = None
715
- resource_id = assignment_id if assignment_id else task_id
716
- for ref in self.metadata.get('system', dict()).get('refs', []):
717
- if ref.get('id') == resource_id:
718
- status = ref.get('metadata', {}).get('status', None)
719
- break
720
- return status
721
-
722
- def set_description(self, text: str):
723
- """
724
- Update Item description
725
-
726
- :param str text: if None or "" description will be deleted
727
-
728
- :return
729
- """
730
- if text is None:
731
- text = ""
732
- if not isinstance(text, str):
733
- raise ValueError("Description must get string")
734
- self._description = text
735
- self._platform_dict = self.update()._platform_dict
736
- return self
737
-
738
- def assign_subset(self, subset: str):
739
- """
740
- Assign a single ML subset (train/validation/test) to this item.
741
- Sets the chosen subset to True and the others to None.
742
- Then calls item.update(system_metadata=True).
743
-
744
- :param str subset: 'train', 'validation', or 'test'
745
- """
746
- if subset not in ['train', 'validation', 'test']:
747
- raise ValueError("subset must be one of: 'train', 'validation', 'test'")
748
-
749
- if 'system' not in self.metadata:
750
- self.metadata['system'] = {}
751
- if 'tags' not in self.metadata['system']:
752
- self.metadata['system']['tags'] = {}
753
-
754
- self.metadata['system']['tags']['train'] = True if subset == 'train' else None
755
- self.metadata['system']['tags']['validation'] = True if subset == 'validation' else None
756
- self.metadata['system']['tags']['test'] = True if subset == 'test' else None
757
-
758
- return self.update(system_metadata=True)
759
-
760
-
761
- def remove_subset(self):
762
- """
763
- Remove any ML subset assignment from this item.
764
- Sets train, validation, and test to None.
765
- Then calls item.update(system_metadata=True).
766
- """
767
- if 'system' not in self.metadata:
768
- self.metadata['system'] = {}
769
- if 'tags' not in self.metadata['system']:
770
- self.metadata['system']['tags'] = {}
771
-
772
- self.metadata['system']['tags']['train'] = None
773
- self.metadata['system']['tags']['validation'] = None
774
- self.metadata['system']['tags']['test'] = None
775
-
776
- return self.update(system_metadata=True)
777
-
778
-
779
- def get_current_subset(self) -> str:
780
- """
781
- Get the current ML subset assignment of this item.
782
- Returns 'train', 'validation', 'test', or None if not assigned.
783
-
784
- :return: subset name or None
785
- :rtype: str or None
786
- """
787
- tags = self.metadata.get('system', {}).get('tags', {})
788
- for subset in ['train', 'validation', 'test']:
789
- if tags.get(subset) is True:
790
- return subset
791
- return None
792
-
793
- def assign_collection(self, collections: List[str]) -> bool:
794
- """
795
- Assign this item to one or more collections.
796
-
797
- :param collections: List of collection names to assign the item to.
798
- :return: True if the assignment was successful, otherwise False.
799
- """
800
- return self.collections.assign(dataset_id=self.dataset_id, collections=collections, item_id=self.id,)
801
-
802
- def unassign_collection(self, collections: List[str]) -> bool:
803
- """
804
- Unassign this item from one or more collections.
805
-
806
- :param collections: List of collection names to unassign the item from.
807
- :return: True if the unassignment was successful, otherwise False.
808
- """
809
- return self.collections.unassign(dataset_id=self.dataset_id, item_id=self.id, collections=collections)
810
-
811
- def list_collections(self) -> List[dict]:
812
- """
813
- List all collections associated with this item.
814
-
815
- :return: A list of dictionaries containing collection keys and their respective names.
816
- Each dictionary has the structure: {"key": <collection_key>, "name": <collection_name>}.
817
- """
818
- collections = self.metadata.get("system", {}).get("collections", {})
819
- if not isinstance(collections, dict):
820
- # Ensure collections is a dictionary
821
- return []
822
-
823
- # Retrieve collection names by their keys
824
- return [
825
- {"key": key, "name": self.collections.get_name_by_key(key)}
826
- for key in collections.keys()
827
- ]
828
-
829
- def task_scores(self, task_id: str, page_offset: int = None, page_size: int = None):
830
- """
831
- Get the scores of the item in a specific task.
832
- :param task_id: The ID of the task.
833
- :return: page of scores
834
- """
835
- return self.items.task_scores(item_id=self.id, task_id=task_id, page_offset=page_offset, page_size=page_size)
836
-
837
-
838
- class ModalityTypeEnum(str, Enum):
839
- """
840
- State enum
841
- """
842
- OVERLAY = "overlay"
843
- REPLACE = "replace"
844
- PREVIEW = "preview"
845
-
846
-
847
- class ModalityRefTypeEnum(str, Enum):
848
- """
849
- State enum
850
- """
851
- ID = "id"
852
- URL = "url"
853
-
854
-
855
- class Modality:
856
- def __init__(self, _json=None, modality_type=None, ref=None, ref_type=ModalityRefTypeEnum.ID,
857
- name=None, timestamp=None, mimetype=None):
858
- """
859
- :param _json: json represent of all modality params
860
- :param modality_type: ModalityTypeEnum.OVERLAY,ModalityTypeEnum.REPLACE
861
- :param ref: id or url of the item reference
862
- :param ref_type: ModalityRefTypeEnum.ID, ModalityRefTypeEnum.URL
863
- :param name:
864
- :param timestamp: ISOString, epoch of UTC
865
- :param mimetype: str - type of the file
866
- """
867
- if _json is None:
868
- _json = dict()
869
- self.type = _json.get('type', modality_type)
870
- self.ref_type = _json.get('refType', ref_type)
871
- self.ref = _json.get('ref', ref)
872
- self.name = _json.get('name', name)
873
- self.timestamp = _json.get('timestamp', timestamp)
874
- self.mimetype = _json.get('mimetype', mimetype)
875
-
876
- def to_json(self):
877
- _json = {"type": self.type,
878
- "ref": self.ref,
879
- "refType": self.ref_type}
880
- if self.name is not None:
881
- _json['name'] = self.name
882
- if self.timestamp is not None:
883
- _json['timestamp'] = self.timestamp
884
- if self.mimetype is not None:
885
- _json['mimetype'] = self.mimetype
886
- return _json
887
-
888
-
889
- class Modalities:
890
- def __init__(self, item):
891
- assert isinstance(item, Item)
892
- self.item = item
893
- if 'system' not in self.item.metadata:
894
- self.item.metadata['system'] = dict()
895
-
896
- @property
897
- def modalities(self):
898
- mod = None
899
- if 'system' in self.item.metadata:
900
- mod = self.item.metadata['system'].get('modalities', None)
901
- return mod
902
-
903
- def create(self, name, ref,
904
- ref_type: ModalityRefTypeEnum = ModalityRefTypeEnum.ID,
905
- modality_type: ModalityTypeEnum = ModalityTypeEnum.OVERLAY,
906
- timestamp=None,
907
- mimetype=None,
908
- ):
909
- """
910
- create Modalities entity
911
-
912
- :param name: name
913
- :param ref: id or url of the item reference
914
- :param ref_type: ModalityRefTypeEnum.ID, ModalityRefTypeEnum.URL
915
- :param modality_type: ModalityTypeEnum.OVERLAY,ModalityTypeEnum.REPLACE
916
- :param timestamp: ISOString, epoch of UTC
917
- :param mimetype: str - type of the file
918
- """
919
- if self.modalities is None:
920
- self.item.metadata['system']['modalities'] = list()
921
-
922
- _json = {"type": modality_type,
923
- "ref": ref,
924
- "refType": ref_type}
925
- if name is not None:
926
- _json['name'] = name
927
- if timestamp is not None:
928
- _json['timestamp'] = timestamp
929
- if mimetype is not None:
930
- _json['mimetype'] = mimetype
931
-
932
- self.item.metadata['system']['modalities'].append(_json)
933
-
934
- return Modality(_json=_json)
935
-
936
- def delete(self, name):
937
- """
938
- :param name:
939
- """
940
- if self.modalities is not None:
941
- for modality in self.item.metadata['system']['modalities']:
942
- if name == modality['name']:
943
- self.item.metadata['system']['modalities'].remove(modality)
944
- return Modality(_json=modality)
945
- return None
946
-
947
- def list(self):
948
- modalities = list()
949
- if self.modalities is not None:
950
- modalities = list()
951
- for modality in self.item.metadata['system']['modalities']:
952
- modalities.append(Modality(_json=modality))
953
- return modalities
1
+ import warnings
2
+ from collections import namedtuple
3
+ from enum import Enum
4
+ import traceback
5
+ import logging
6
+ import attr
7
+ import copy
8
+ import os
9
+ import io
10
+ from .. import repositories, entities, exceptions
11
+ from .annotation import ViewAnnotationOptions, ExportVersion
12
+ from ..services.api_client import ApiClient
13
+ from ..services.api_client import client as client_api
14
+ import json
15
+ from typing import List
16
+ import requests
17
+
18
+ logger = logging.getLogger(name='dtlpy')
19
+
20
+
21
+ class ExportMetadata(Enum):
22
+ FROM_JSON = 'from_json'
23
+
24
+
25
+ class ItemStatus(str, Enum):
26
+ COMPLETED = "completed"
27
+ APPROVED = "approved"
28
+ DISCARDED = "discard"
29
+
30
+
31
+ @attr.s
32
+ class Item(entities.BaseEntity):
33
+ """
34
+ Item object
35
+ """
36
+ # item information
37
+ annotations_link = attr.ib(repr=False)
38
+ dataset_url = attr.ib()
39
+ thumbnail = attr.ib(repr=False)
40
+ created_at = attr.ib()
41
+ updated_at = attr.ib()
42
+ updated_by = attr.ib()
43
+ dataset_id = attr.ib()
44
+ annotated = attr.ib(repr=False)
45
+ metadata = attr.ib(repr=False)
46
+ filename = attr.ib()
47
+ stream = attr.ib(repr=False)
48
+ name = attr.ib()
49
+ type = attr.ib()
50
+ url = attr.ib(repr=False)
51
+ id = attr.ib()
52
+ hidden = attr.ib(repr=False)
53
+ dir = attr.ib(repr=False)
54
+ spec = attr.ib()
55
+ creator = attr.ib()
56
+ _description = attr.ib()
57
+ _src_item = attr.ib(repr=False)
58
+
59
+ # name change
60
+ annotations_count = attr.ib()
61
+
62
+ # api
63
+ _client_api = attr.ib(type=ApiClient, repr=False)
64
+ _platform_dict = attr.ib(repr=False)
65
+
66
+ # entities
67
+ _dataset = attr.ib(repr=False)
68
+ _model = attr.ib(repr=False)
69
+ _project = attr.ib(repr=False)
70
+ _project_id = attr.ib(repr=False)
71
+
72
+ # repositories
73
+ _repositories = attr.ib(repr=False)
74
+
75
+ @property
76
+ def createdAt(self):
77
+ return self.created_at
78
+
79
+ @property
80
+ def datasetId(self):
81
+ return self.dataset_id
82
+
83
+ @staticmethod
84
+ def _protected_from_json(_json, client_api, dataset=None):
85
+ """
86
+ Same as from_json but with try-except to catch if error
87
+ :param _json: platform json
88
+ :param client_api: ApiClient entity
89
+ :param dataset: dataset entity
90
+ :return:
91
+ """
92
+ try:
93
+ item = Item.from_json(_json=_json,
94
+ client_api=client_api,
95
+ dataset=dataset)
96
+ status = True
97
+ except Exception:
98
+ item = traceback.format_exc()
99
+ status = False
100
+ return status, item
101
+
102
+ @classmethod
103
+ def from_json(cls, _json, client_api, dataset=None, project=None, model=None, is_fetched=True):
104
+ """
105
+ Build an item entity object from a json
106
+
107
+ :param dtlpy.entities.project.Project project: project entity
108
+ :param dict _json: _json response from host
109
+ :param dtlpy.entities.dataset.Dataset dataset: dataset in which the annotation's item is located
110
+ :param dtlpy.entities.dataset.Model model: the model entity if item is an artifact of a model
111
+ :param dlApiClient client_api: ApiClient entity
112
+ :param bool is_fetched: is Entity fetched from Platform
113
+ :return: Item object
114
+ :rtype: dtlpy.entities.item.Item
115
+ """
116
+ dataset_id = None
117
+ if dataset is not None:
118
+ dataset_id = _json.get('datasetId', None)
119
+ if dataset.id != dataset_id and dataset_id is not None:
120
+ logger.warning('Item has been fetched from a dataset that is not belong to it')
121
+ dataset = None
122
+ else:
123
+ dataset_id = dataset.id
124
+
125
+ metadata = _json.get('metadata', dict())
126
+ project_id = _json.get('projectId', None)
127
+ if project_id is None:
128
+ project_id = project.id if project else None
129
+ inst = cls(
130
+ # sdk
131
+ platform_dict=copy.deepcopy(_json),
132
+ client_api=client_api,
133
+ dataset=dataset,
134
+ project=project,
135
+ model=model,
136
+ # params
137
+ annotations_link=_json.get('annotations', None),
138
+ thumbnail=_json.get('thumbnail', None),
139
+ dataset_id=_json.get('datasetId', dataset_id),
140
+ annotated=_json.get('annotated', None),
141
+ dataset_url=_json.get('dataset', None),
142
+ created_at=_json.get('createdAt', None),
143
+ annotations_count=_json.get('annotationsCount', None),
144
+ hidden=_json.get('hidden', False),
145
+ stream=_json.get('stream', None),
146
+ dir=_json.get('dir', None),
147
+ filename=_json.get('filename', None),
148
+ metadata=metadata,
149
+ name=_json.get('name', None),
150
+ type=_json.get('type', None),
151
+ url=_json.get('url', None),
152
+ id=_json.get('id', None),
153
+ spec=_json.get('spec', None),
154
+ creator=_json.get('creator', None),
155
+ project_id=project_id,
156
+ description=_json.get('description', None),
157
+ src_item=_json.get('srcItem', None),
158
+ updated_at=_json.get('updatedAt', None),
159
+ updated_by=_json.get('updatedBy', None)
160
+ )
161
+ inst.is_fetched = is_fetched
162
+ return inst
163
+
164
+ def __getstate__(self):
165
+ # dump state to json
166
+ return self.to_json()
167
+
168
+ def __setstate__(self, state):
169
+ # create a new item, and update the current one with the same state
170
+ # this way we can have _client_api, and all the repositories and entities which are not picklable
171
+ self.__dict__.update(entities.Item.from_json(_json=state,
172
+ client_api=client_api).__dict__)
173
+
174
+ ############
175
+ # entities #
176
+ ############
177
+ @property
178
+ def dataset(self):
179
+ if self._dataset is None:
180
+ self._dataset = self.datasets.get(dataset_id=self.dataset_id, fetch=None)
181
+ assert isinstance(self._dataset, entities.Dataset)
182
+ return self._dataset
183
+
184
+ @property
185
+ def model(self):
186
+ return self._model
187
+
188
+ def __update_item_binary(self, _json):
189
+ binary = io.BytesIO()
190
+ binary.write(json.dumps(_json).encode())
191
+ binary.seek(0)
192
+ binary.name = self.name
193
+ success, resp = client_api.gen_request(req_type='post',
194
+ path=f'/items/{self.id}/revisions',
195
+ files={'file': (binary.name, binary)})
196
+ if not success:
197
+ raise exceptions.PlatformException(resp)
198
+
199
+ @property
200
+ def project(self):
201
+ if self._project is None:
202
+ if self._dataset is None:
203
+ self._dataset = self.datasets.get(dataset_id=self.dataset_id, fetch=None)
204
+ self._project = self._dataset.project
205
+ if self._project is None:
206
+ raise exceptions.PlatformException(error='2001',
207
+ message='Missing entity "project".')
208
+ assert isinstance(self._project, entities.Project)
209
+ return self._project
210
+
211
+ @property
212
+ def project_id(self):
213
+ if self._project_id is None:
214
+ if self._dataset is None:
215
+ self._dataset = self.datasets.get(dataset_id=self.dataset_id, fetch=None)
216
+ self._project_id = self._dataset.project.id
217
+ return self._project_id
218
+
219
+ ################
220
+ # repositories #
221
+ ################
222
+
223
+ @_repositories.default
224
+ def set_repositories(self):
225
+ reps = namedtuple('repositories',
226
+ field_names=['annotations', 'datasets', 'items', 'codebases', 'artifacts', 'modalities',
227
+ 'features', 'assignments', 'tasks', 'resource_executions', 'collections'])
228
+ reps.__new__.__defaults__ = (None, None, None, None, None, None, None, None, None)
229
+
230
+ if self._dataset is None:
231
+ items = repositories.Items(
232
+ client_api=self._client_api,
233
+ dataset=self._dataset,
234
+ dataset_id=self.dataset_id,
235
+ datasets=repositories.Datasets(client_api=self._client_api, project=None)
236
+ )
237
+ datasets = items.datasets
238
+
239
+ else:
240
+ items = self.dataset.items
241
+ datasets = self.dataset.datasets
242
+
243
+ r = reps(
244
+ annotations=repositories.Annotations(
245
+ client_api=self._client_api,
246
+ dataset_id=self.dataset_id,
247
+ item=self,
248
+ dataset=self._dataset
249
+ ),
250
+ items=items,
251
+ datasets=datasets,
252
+ codebases=None,
253
+ artifacts=None,
254
+ modalities=Modalities(item=self),
255
+ features=repositories.Features(
256
+ client_api=self._client_api,
257
+ project=self._project,
258
+ item=self
259
+ ),
260
+ tasks=repositories.Tasks(
261
+ client_api=self._client_api,
262
+ project=self._project,
263
+ dataset=self._dataset
264
+ ),
265
+ assignments=repositories.Assignments(
266
+ client_api=self._client_api,
267
+ project=self._project,
268
+ dataset=self._dataset
269
+ ),
270
+ resource_executions=repositories.ResourceExecutions(
271
+ client_api=self._client_api,
272
+ project=self._project,
273
+ resource=self
274
+ ),
275
+ collections=repositories.Collections(client_api=self._client_api, item=self, dataset=self._dataset)
276
+ )
277
+ return r
278
+
279
+ @property
280
+ def modalities(self):
281
+ assert isinstance(self._repositories.modalities, Modalities)
282
+ return self._repositories.modalities
283
+
284
+ @property
285
+ def annotations(self):
286
+ assert isinstance(self._repositories.annotations, repositories.Annotations)
287
+ return self._repositories.annotations
288
+
289
+ @property
290
+ def datasets(self):
291
+ assert isinstance(self._repositories.datasets, repositories.Datasets)
292
+ return self._repositories.datasets
293
+
294
+ @property
295
+ def assignments(self):
296
+ assert isinstance(self._repositories.assignments, repositories.Assignments)
297
+ return self._repositories.assignments
298
+
299
+ @property
300
+ def tasks(self):
301
+ assert isinstance(self._repositories.tasks, repositories.Tasks)
302
+ return self._repositories.tasks
303
+
304
+ @property
305
+ def resource_executions(self):
306
+ assert isinstance(self._repositories.resource_executions, repositories.ResourceExecutions)
307
+ return self._repositories.resource_executions
308
+
309
+ @property
310
+ def items(self):
311
+ assert isinstance(self._repositories.items, repositories.Items)
312
+ return self._repositories.items
313
+
314
+ @property
315
+ def features(self):
316
+ assert isinstance(self._repositories.features, repositories.Features)
317
+ return self._repositories.features
318
+
319
+ @property
320
+ def collections(self):
321
+ assert isinstance(self._repositories.collections, repositories.Collections)
322
+ return self._repositories.collections
323
+
324
+ ##############
325
+ # Properties #
326
+ ##############
327
+ @property
328
+ def height(self):
329
+ return self.metadata.get('system', dict()).get('height', None)
330
+
331
+ @height.setter
332
+ def height(self, val):
333
+ if 'system' not in self.metadata:
334
+ self.metadata['system'] = dict()
335
+ self.metadata['system']['height'] = val
336
+
337
+ @property
338
+ def width(self):
339
+ return self.metadata.get('system', dict()).get('width', None)
340
+
341
+ @width.setter
342
+ def width(self, val):
343
+ if 'system' not in self.metadata:
344
+ self.metadata['system'] = dict()
345
+ self.metadata['system']['width'] = val
346
+
347
+ @property
348
+ def fps(self):
349
+ return self.metadata.get('fps', None)
350
+
351
+ @fps.setter
352
+ def fps(self, val):
353
+ self.metadata['fps'] = val
354
+
355
+ @property
356
+ def mimetype(self):
357
+ return self.metadata.get('system', dict()).get('mimetype', None)
358
+
359
+ @property
360
+ def size(self):
361
+ return self.metadata.get('system', dict()).get('size', None)
362
+
363
+ @property
364
+ def system(self):
365
+ return self.metadata.get('system', dict())
366
+
367
+ @property
368
+ def description(self):
369
+ description = None
370
+ if self._description is not None:
371
+ description = self._description
372
+ elif 'description' in self.metadata:
373
+ description = self.metadata['description'].get('text', None)
374
+ return description
375
+
376
+ @property
377
+ def platform_url(self):
378
+ return self._client_api._get_resource_url(
379
+ "projects/{}/datasets/{}/items/{}".format(self.dataset.projects[-1], self.dataset.id, self.id))
380
+
381
+ @description.setter
382
+ def description(self, text: str):
383
+ """
384
+ Update Item description
385
+
386
+ :param text: if None or "" description will be deleted
387
+ :return
388
+ """
389
+ self.set_description(text=text)
390
+
391
+ ###########
392
+ # Functions #
393
+ ###########
394
+ def to_json(self):
395
+ """
396
+ Returns platform _json format of object
397
+
398
+ :return: platform json format of object
399
+ :rtype: dict
400
+ """
401
+ _json = attr.asdict(self,
402
+ filter=attr.filters.exclude(attr.fields(Item)._repositories,
403
+ attr.fields(Item)._dataset,
404
+ attr.fields(Item)._model,
405
+ attr.fields(Item)._project,
406
+ attr.fields(Item)._client_api,
407
+ attr.fields(Item)._platform_dict,
408
+ attr.fields(Item).annotations_count,
409
+ attr.fields(Item).dataset_url,
410
+ attr.fields(Item).annotations_link,
411
+ attr.fields(Item).spec,
412
+ attr.fields(Item).creator,
413
+ attr.fields(Item).created_at,
414
+ attr.fields(Item).dataset_id,
415
+ attr.fields(Item)._project_id,
416
+ attr.fields(Item)._description,
417
+ attr.fields(Item)._src_item,
418
+ attr.fields(Item).updated_at,
419
+ attr.fields(Item).updated_by
420
+ ))
421
+
422
+ _json.update({'annotations': self.annotations_link,
423
+ 'annotationsCount': self.annotations_count,
424
+ 'dataset': self.dataset_url,
425
+ 'createdAt': self.created_at,
426
+ 'datasetId': self.dataset_id,
427
+ })
428
+ if self.spec is not None:
429
+ _json['spec'] = self.spec
430
+ if self.creator is not None:
431
+ _json['creator'] = self.creator
432
+ if self._description is not None:
433
+ _json['description'] = self.description
434
+ if self._src_item is not None:
435
+ _json['srcItem'] = self._src_item
436
+
437
+ _json['updatedAt'] = self.updated_at
438
+ _json['updatedBy'] = self.updated_by
439
+
440
+ return _json
441
+
442
+ def download(
443
+ self,
444
+ # download options
445
+ local_path=None,
446
+ file_types=None,
447
+ save_locally=True,
448
+ to_array=False,
449
+ annotation_options: ViewAnnotationOptions = None,
450
+ overwrite=False,
451
+ to_items_folder=True,
452
+ thickness=1,
453
+ with_text=False,
454
+ annotation_filters=None,
455
+ alpha=1,
456
+ export_version=ExportVersion.V1,
457
+ dataset_lock=False,
458
+ lock_timeout_sec=None,
459
+ export_summary=False,
460
+ ):
461
+ """
462
+ Download dataset by filters.
463
+ Filtering the dataset for items and save them local
464
+ Optional - also download annotation, mask, instance and image mask of the item
465
+
466
+ :param str local_path: local folder or filename to save to.
467
+ :param list file_types: a list of file type to download. e.g ['video/webm', 'video/mp4', 'image/jpeg', 'image/png']
468
+ :param bool save_locally: bool. save to disk or return a buffer
469
+ :param bool to_array: returns Ndarray when True and local_path = False
470
+ :param list annotation_options: download annotations options: list(dl.ViewAnnotationOptions)
471
+ :param dtlpy.entities.filters.Filters annotation_filters: Filters entity to filter annotations for download
472
+ :param bool overwrite: optional - default = False
473
+ :param bool dataset_lock: optional - default = False
474
+ :param bool export_summary: optional - default = False
475
+ :param int lock_timeout_sec: optional
476
+ :param bool to_items_folder: Create 'items' folder and download items to it
477
+ :param int thickness: optional - line thickness, if -1 annotation will be filled, default =1
478
+ :param bool with_text: optional - add text to annotations, default = False
479
+ :param float alpha: opacity value [0 1], default 1
480
+ :param str export_version: exported items will have original extension in filename, `V1` - no original extension in filenames
481
+ :return: generator of local_path per each downloaded item
482
+ :rtype: generator or single item
483
+
484
+ **Example**:
485
+
486
+ .. code-block:: python
487
+
488
+ item.download(local_path='local_path',
489
+ annotation_options=dl.ViewAnnotationOptions.MASK,
490
+ overwrite=False,
491
+ thickness=1,
492
+ with_text=False,
493
+ alpha=1,
494
+ save_locally=True,
495
+ dataset_lock=False
496
+ lock_timeout_sec=300,
497
+ export_summary=False
498
+ )
499
+ """
500
+ # if dir - concatenate local path and item name
501
+ if local_path is not None:
502
+ if os.path.isdir(local_path):
503
+ local_path = os.path.join(local_path, self.name)
504
+ else:
505
+ _, ext = os.path.splitext(local_path)
506
+ _, item_ext = os.path.splitext(self.name)
507
+ if not ext or ext != item_ext:
508
+ os.makedirs(local_path, exist_ok=True)
509
+ local_path = os.path.join(local_path, self.name)
510
+
511
+ # download
512
+ filters = None
513
+ items = self
514
+ if self.type == 'dir':
515
+ filters = self.datasets._bulid_folder_filter(folder_path=self.filename)
516
+ items = None
517
+
518
+ return self.items.download(items=items,
519
+ local_path=local_path,
520
+ file_types=file_types,
521
+ save_locally=save_locally,
522
+ to_array=to_array,
523
+ annotation_options=annotation_options,
524
+ overwrite=overwrite,
525
+ to_items_folder=to_items_folder,
526
+ annotation_filters=annotation_filters,
527
+ thickness=thickness,
528
+ alpha=alpha,
529
+ with_text=with_text,
530
+ export_version=export_version,
531
+ filters=filters,
532
+ dataset_lock=dataset_lock,
533
+ lock_timeout_sec=lock_timeout_sec,
534
+ export_summary=export_summary)
535
+
536
+ def delete(self):
537
+ """
538
+ Delete item from platform
539
+
540
+ :return: True
541
+ :rtype: bool
542
+ """
543
+ return self.items.delete(item_id=self.id)
544
+
545
+ def update(self, system_metadata=False):
546
+ """
547
+ Update items metadata
548
+
549
+ :param bool system_metadata: bool - True, if you want to change metadata system
550
+ :return: Item object
551
+ :rtype: dtlpy.entities.item.Item
552
+ """
553
+ return self.items.update(item=self, system_metadata=system_metadata)
554
+
555
+ def move(self, new_path):
556
+ """
557
+ Move item from one folder to another in Platform
558
+ If the directory doesn't exist it will be created
559
+
560
+ :param str new_path: new full path to move item to.
561
+ :return: True if update successfully
562
+ :rtype: bool
563
+ """
564
+ assert isinstance(new_path, str)
565
+ if not new_path.startswith('/'):
566
+ new_path = '/' + new_path
567
+ if new_path.endswith('/'):
568
+ self.filename = new_path + self.name
569
+ else:
570
+ try:
571
+ self.items.get(filepath=new_path, is_dir=True)
572
+ self.filename = new_path + '/' + self.name
573
+ except exceptions.NotFound:
574
+ self.filename = new_path
575
+
576
+ return self.update(system_metadata=True)
577
+
578
+ def clone(self, dst_dataset_id=None, remote_filepath=None, metadata=None, with_annotations=True,
579
+ with_metadata=True, with_task_annotations_status=False, allow_many=False, wait=True):
580
+ """
581
+ Clone item
582
+
583
+ :param str dst_dataset_id: destination dataset id
584
+ :param str remote_filepath: complete filepath
585
+ :param dict metadata: new metadata to add
586
+ :param bool with_annotations: clone annotations
587
+ :param bool with_metadata: clone metadata
588
+ :param bool with_task_annotations_status: clone task annotations status
589
+ :param bool allow_many: `bool` if True, using multiple clones in single dataset is allowed, (default=False)
590
+ :param bool wait: wait for the command to finish
591
+ :return: Item object
592
+ :rtype: dtlpy.entities.item.Item
593
+
594
+ **Example**:
595
+
596
+ .. code-block:: python
597
+
598
+ item.clone(item_id='item_id',
599
+ dst_dataset_id='dist_dataset_id',
600
+ with_metadata=True,
601
+ with_task_annotations_status=False,
602
+ with_annotations=False)
603
+ """
604
+ if remote_filepath is None:
605
+ remote_filepath = self.filename
606
+ if dst_dataset_id is None:
607
+ dst_dataset_id = self.dataset_id
608
+ return self.items.clone(item_id=self.id,
609
+ dst_dataset_id=dst_dataset_id,
610
+ remote_filepath=remote_filepath,
611
+ metadata=metadata,
612
+ with_annotations=with_annotations,
613
+ with_metadata=with_metadata,
614
+ with_task_annotations_status=with_task_annotations_status,
615
+ allow_many=allow_many,
616
+ wait=wait)
617
+
618
+ def open_in_web(self):
619
+ """
620
+ Open the items in web platform
621
+
622
+ :return:
623
+ """
624
+ self._client_api._open_in_web(url=self.platform_url)
625
+
626
+ def _set_action(self, status: str, operation: str, assignment_id: str = None, task_id: str = None):
627
+ """
628
+ update item status
629
+
630
+ :param status: str - string the describes the status
631
+ :param operation: str - 'create' or 'delete'
632
+ :param assignment_id: str - assignment id
633
+ :param task_id: str - task id
634
+
635
+ :return :True/False
636
+ """
637
+ if assignment_id:
638
+ success = self.assignments.set_status(
639
+ status=status,
640
+ operation=operation,
641
+ item_id=self.id,
642
+ assignment_id=assignment_id
643
+ )
644
+ elif task_id:
645
+ success = self.tasks.set_status(
646
+ status=status,
647
+ operation=operation,
648
+ item_ids=[self.id],
649
+ task_id=task_id
650
+ )
651
+
652
+ else:
653
+ raise exceptions.PlatformException('400', 'Must provide task_id or assignment_id')
654
+
655
+ return success
656
+
657
+ def update_status(self, status: str, clear: bool = False, assignment_id: str = None, task_id: str = None):
658
+ """
659
+ update item status
660
+
661
+ :param str status: "completed" ,"approved" ,"discard"
662
+ :param bool clear: if true delete status
663
+ :param str assignment_id: assignment id
664
+ :param str task_id: task id
665
+
666
+ :return :True/False
667
+ :rtype: bool
668
+
669
+ **Example**:
670
+
671
+ .. code-block:: python
672
+
673
+ item.update_status(status='complete',
674
+ task_id='task_id')
675
+
676
+ """
677
+ if not assignment_id and not task_id:
678
+ system_metadata = self.metadata.get('system', dict())
679
+ if 'refs' in system_metadata:
680
+ refs = system_metadata['refs']
681
+ if len(refs) <= 2:
682
+ for ref in refs:
683
+ if ref.get('type', '') == 'assignment':
684
+ assignment_id = ref['id']
685
+ if ref.get('type', '') == 'task':
686
+ task_id = ref['id']
687
+
688
+ if assignment_id or task_id:
689
+ if clear:
690
+ self._set_action(status=status, operation='delete', assignment_id=assignment_id, task_id=task_id)
691
+ else:
692
+ self._set_action(status=status, operation='create', assignment_id=assignment_id, task_id=task_id)
693
+ else:
694
+ raise exceptions.PlatformException('400', 'must provide assignment_id or task_id')
695
+
696
+ def status(self, assignment_id: str = None, task_id: str = None):
697
+ """
698
+ Get item status
699
+
700
+ :param str assignment_id: assignment id
701
+ :param str task_id: task id
702
+
703
+ :return: status
704
+ :rtype: str
705
+
706
+ **Example**:
707
+
708
+ .. code-block:: python
709
+
710
+ status = item.status(task_id='task_id')
711
+ """
712
+ if not assignment_id and not task_id:
713
+ raise exceptions.PlatformException('400', 'must provide assignment_id or task_id')
714
+ status = None
715
+ resource_id = assignment_id if assignment_id else task_id
716
+ for ref in self.metadata.get('system', dict()).get('refs', []):
717
+ if ref.get('id') == resource_id:
718
+ status = ref.get('metadata', {}).get('status', None)
719
+ break
720
+ return status
721
+
722
+ def set_description(self, text: str):
723
+ """
724
+ Update Item description
725
+
726
+ :param str text: if None or "" description will be deleted
727
+
728
+ :return
729
+ """
730
+ if text is None:
731
+ text = ""
732
+ if not isinstance(text, str):
733
+ raise ValueError("Description must get string")
734
+ self._description = text
735
+ self._platform_dict = self.update()._platform_dict
736
+ return self
737
+
738
+ def assign_subset(self, subset: str):
739
+ """
740
+ Assign a single ML subset (train/validation/test) to this item.
741
+ Sets the chosen subset to True and the others to None.
742
+ Then calls item.update(system_metadata=True).
743
+
744
+ :param str subset: 'train', 'validation', or 'test'
745
+ """
746
+ if subset not in ['train', 'validation', 'test']:
747
+ raise ValueError("subset must be one of: 'train', 'validation', 'test'")
748
+
749
+ if 'system' not in self.metadata:
750
+ self.metadata['system'] = {}
751
+ if 'tags' not in self.metadata['system']:
752
+ self.metadata['system']['tags'] = {}
753
+
754
+ self.metadata['system']['tags']['train'] = True if subset == 'train' else None
755
+ self.metadata['system']['tags']['validation'] = True if subset == 'validation' else None
756
+ self.metadata['system']['tags']['test'] = True if subset == 'test' else None
757
+
758
+ return self.update(system_metadata=True)
759
+
760
+
761
+ def remove_subset(self):
762
+ """
763
+ Remove any ML subset assignment from this item.
764
+ Sets train, validation, and test to None.
765
+ Then calls item.update(system_metadata=True).
766
+ """
767
+ if 'system' not in self.metadata:
768
+ self.metadata['system'] = {}
769
+ if 'tags' not in self.metadata['system']:
770
+ self.metadata['system']['tags'] = {}
771
+
772
+ self.metadata['system']['tags']['train'] = None
773
+ self.metadata['system']['tags']['validation'] = None
774
+ self.metadata['system']['tags']['test'] = None
775
+
776
+ return self.update(system_metadata=True)
777
+
778
+
779
+ def get_current_subset(self) -> str:
780
+ """
781
+ Get the current ML subset assignment of this item.
782
+ Returns 'train', 'validation', 'test', or None if not assigned.
783
+
784
+ :return: subset name or None
785
+ :rtype: str or None
786
+ """
787
+ tags = self.metadata.get('system', {}).get('tags', {})
788
+ for subset in ['train', 'validation', 'test']:
789
+ if tags.get(subset) is True:
790
+ return subset
791
+ return None
792
+
793
+ def assign_collection(self, collections: List[str]) -> bool:
794
+ """
795
+ Assign this item to one or more collections.
796
+
797
+ :param collections: List of collection names to assign the item to.
798
+ :return: True if the assignment was successful, otherwise False.
799
+ """
800
+ return self.collections.assign(dataset_id=self.dataset_id, collections=collections, item_id=self.id,)
801
+
802
+ def unassign_collection(self, collections: List[str]) -> bool:
803
+ """
804
+ Unassign this item from one or more collections.
805
+
806
+ :param collections: List of collection names to unassign the item from.
807
+ :return: True if the unassignment was successful, otherwise False.
808
+ """
809
+ return self.collections.unassign(dataset_id=self.dataset_id, item_id=self.id, collections=collections)
810
+
811
+ def list_collections(self) -> List[dict]:
812
+ """
813
+ List all collections associated with this item.
814
+
815
+ :return: A list of dictionaries containing collection keys and their respective names.
816
+ Each dictionary has the structure: {"key": <collection_key>, "name": <collection_name>}.
817
+ """
818
+ collections = self.metadata.get("system", {}).get("collections", {})
819
+ if not isinstance(collections, dict):
820
+ # Ensure collections is a dictionary
821
+ return []
822
+
823
+ # Retrieve collection names by their keys
824
+ return [
825
+ {"key": key, "name": self.collections.get_name_by_key(key)}
826
+ for key in collections.keys()
827
+ ]
828
+
829
+ def task_scores(self, task_id: str, page_offset: int = None, page_size: int = None):
830
+ """
831
+ Get the scores of the item in a specific task.
832
+ :param task_id: The ID of the task.
833
+ :return: page of scores
834
+ """
835
+ return self.items.task_scores(item_id=self.id, task_id=task_id, page_offset=page_offset, page_size=page_size)
836
+
837
+
838
+ class ModalityTypeEnum(str, Enum):
839
+ """
840
+ State enum
841
+ """
842
+ OVERLAY = "overlay"
843
+ REPLACE = "replace"
844
+ PREVIEW = "preview"
845
+
846
+
847
+ class ModalityRefTypeEnum(str, Enum):
848
+ """
849
+ State enum
850
+ """
851
+ ID = "id"
852
+ URL = "url"
853
+
854
+
855
+ class Modality:
856
+ def __init__(self, _json=None, modality_type=None, ref=None, ref_type=ModalityRefTypeEnum.ID,
857
+ name=None, timestamp=None, mimetype=None):
858
+ """
859
+ :param _json: json represent of all modality params
860
+ :param modality_type: ModalityTypeEnum.OVERLAY,ModalityTypeEnum.REPLACE
861
+ :param ref: id or url of the item reference
862
+ :param ref_type: ModalityRefTypeEnum.ID, ModalityRefTypeEnum.URL
863
+ :param name:
864
+ :param timestamp: ISOString, epoch of UTC
865
+ :param mimetype: str - type of the file
866
+ """
867
+ if _json is None:
868
+ _json = dict()
869
+ self.type = _json.get('type', modality_type)
870
+ self.ref_type = _json.get('refType', ref_type)
871
+ self.ref = _json.get('ref', ref)
872
+ self.name = _json.get('name', name)
873
+ self.timestamp = _json.get('timestamp', timestamp)
874
+ self.mimetype = _json.get('mimetype', mimetype)
875
+
876
+ def to_json(self):
877
+ _json = {"type": self.type,
878
+ "ref": self.ref,
879
+ "refType": self.ref_type}
880
+ if self.name is not None:
881
+ _json['name'] = self.name
882
+ if self.timestamp is not None:
883
+ _json['timestamp'] = self.timestamp
884
+ if self.mimetype is not None:
885
+ _json['mimetype'] = self.mimetype
886
+ return _json
887
+
888
+
889
+ class Modalities:
890
+ def __init__(self, item):
891
+ assert isinstance(item, Item)
892
+ self.item = item
893
+ if 'system' not in self.item.metadata:
894
+ self.item.metadata['system'] = dict()
895
+
896
+ @property
897
+ def modalities(self):
898
+ mod = None
899
+ if 'system' in self.item.metadata:
900
+ mod = self.item.metadata['system'].get('modalities', None)
901
+ return mod
902
+
903
+ def create(self, name, ref,
904
+ ref_type: ModalityRefTypeEnum = ModalityRefTypeEnum.ID,
905
+ modality_type: ModalityTypeEnum = ModalityTypeEnum.OVERLAY,
906
+ timestamp=None,
907
+ mimetype=None,
908
+ ):
909
+ """
910
+ create Modalities entity
911
+
912
+ :param name: name
913
+ :param ref: id or url of the item reference
914
+ :param ref_type: ModalityRefTypeEnum.ID, ModalityRefTypeEnum.URL
915
+ :param modality_type: ModalityTypeEnum.OVERLAY,ModalityTypeEnum.REPLACE
916
+ :param timestamp: ISOString, epoch of UTC
917
+ :param mimetype: str - type of the file
918
+ """
919
+ if self.modalities is None:
920
+ self.item.metadata['system']['modalities'] = list()
921
+
922
+ _json = {"type": modality_type,
923
+ "ref": ref,
924
+ "refType": ref_type}
925
+ if name is not None:
926
+ _json['name'] = name
927
+ if timestamp is not None:
928
+ _json['timestamp'] = timestamp
929
+ if mimetype is not None:
930
+ _json['mimetype'] = mimetype
931
+
932
+ self.item.metadata['system']['modalities'].append(_json)
933
+
934
+ return Modality(_json=_json)
935
+
936
+ def delete(self, name):
937
+ """
938
+ :param name:
939
+ """
940
+ if self.modalities is not None:
941
+ for modality in self.item.metadata['system']['modalities']:
942
+ if name == modality['name']:
943
+ self.item.metadata['system']['modalities'].remove(modality)
944
+ return Modality(_json=modality)
945
+ return None
946
+
947
+ def list(self):
948
+ modalities = list()
949
+ if self.modalities is not None:
950
+ modalities = list()
951
+ for modality in self.item.metadata['system']['modalities']:
952
+ modalities.append(Modality(_json=modality))
953
+ return modalities