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