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
@@ -1,699 +1,699 @@
1
- import mimetypes
2
- import traceback
3
- import datetime
4
- import webvtt
5
- import logging
6
- import attr
7
- import json
8
- import os
9
-
10
- import numpy as np
11
- from PIL import Image
12
-
13
- from .. import entities, PlatformException, miscellaneous
14
-
15
- logger = logging.getLogger(name='dtlpy')
16
-
17
-
18
- @attr.s
19
- class AnnotationCollection(entities.BaseEntity):
20
- """
21
- Collection of Annotation entity
22
- """
23
- item = attr.ib(default=None)
24
- annotations = attr.ib() # type: miscellaneous.List[entities.Annotation]
25
- _dataset = attr.ib(repr=False, default=None)
26
- _colors = attr.ib(repr=False, default=None)
27
-
28
- @property
29
- def dataset(self):
30
- if self._dataset is None:
31
- self._dataset = self.item.dataset
32
- assert isinstance(self._dataset, entities.Dataset)
33
- return self._dataset
34
-
35
- @annotations.default
36
- def set_annotations(self):
37
- return list()
38
-
39
- def __iter__(self):
40
- for annotation in self.annotations:
41
- yield annotation
42
-
43
- def __getitem__(self, index: int):
44
- return self.annotations[index]
45
-
46
- def __len__(self):
47
- return len(self.annotations)
48
-
49
- def add(self,
50
- annotation_definition,
51
- object_id=None,
52
- frame_num=None,
53
- end_frame_num=None,
54
- start_time=None,
55
- end_time=None,
56
- automated=True,
57
- fixed=True,
58
- object_visible=True,
59
- metadata=None,
60
- parent_id=None,
61
- prompt_id=None,
62
- model_info=None):
63
- """
64
- Add annotations to collection
65
-
66
- :param annotation_definition: dl.Polygon, dl.Segmentation, dl.Point, dl.Box etc.
67
- :param object_id: Object id (any id given by user). If video - must input to match annotations between frames
68
- :param frame_num: video only, number of frame
69
- :param end_frame_num: video only, the end frame of the annotation
70
- :param start_time: video only, start time of the annotation
71
- :param end_time: video only, end time of the annotation
72
- :param automated:
73
- :param fixed: video only, mark frame as fixed
74
- :param object_visible: video only, does the annotated object is visible
75
- :param metadata: optional, metadata dictionary for annotation
76
- :param parent_id: set a parent for this annotation (parent annotation ID)
77
- :param prompt_id: Connect the annotation with a specific prompt in a dl.PromptItem
78
- :param model_info: optional - set model on annotation {'confidence': 0, # [Mandatory], (Float between 0-1)
79
- 'name': '', # [Optional], ('name' refers to 'model_name')
80
- 'model_id': ''} # [Optional]
81
- :return:
82
- """
83
- if model_info is not None:
84
- if not isinstance(model_info, dict) or 'confidence' not in model_info:
85
- raise ValueError('"model_info" must be a dict with key: "confidence"')
86
- if metadata is None:
87
- metadata = dict()
88
- if 'user' not in metadata:
89
- metadata['user'] = dict()
90
- confidence = float(model_info['confidence'])
91
- if confidence < 0 or confidence > 1:
92
- raise ValueError('"confidence" must be a float between 0 and 1')
93
- metadata['user']['model'] = {'confidence': confidence,
94
- 'name': model_info.get('name'),
95
- 'model_id': model_info.get('model_id'),
96
- }
97
- if prompt_id is not None:
98
- if metadata is None:
99
- metadata = dict()
100
- if 'system' not in metadata:
101
- metadata['system'] = dict()
102
- metadata['system']['promptId'] = prompt_id
103
-
104
- # to support list of definitions with same parameters
105
- if not isinstance(annotation_definition, list):
106
- annotation_definition = [annotation_definition]
107
-
108
- for single_definition in annotation_definition:
109
- annotation = entities.Annotation.new(item=self.item,
110
- annotation_definition=single_definition,
111
- frame_num=frame_num,
112
- automated=automated,
113
- metadata=metadata,
114
- object_id=object_id,
115
- parent_id=parent_id,
116
- start_time=start_time,
117
- end_time=end_time)
118
- # add frame if exists
119
- if (frame_num is not None or start_time is not None) and (
120
- self.item is None or 'audio' not in self.item.metadata.get('system').get('mimetype', '')):
121
- if object_id is None:
122
- raise ValueError('Video Annotation must have object_id.')
123
- else:
124
- if isinstance(object_id, int):
125
- object_id = '{}'.format(object_id)
126
- elif not isinstance(object_id, str) or not object_id.isnumeric():
127
- raise ValueError('Object id must be an int or a string containing only numbers.')
128
- # find matching element_id
129
- matched_ind = [i_annotation
130
- for i_annotation, annotation in enumerate(self.annotations)
131
- if annotation.object_id == object_id]
132
- if len(matched_ind) == 0:
133
- # no matching object id found - create new one
134
- self.annotations.append(annotation)
135
- matched_ind = len(self.annotations) - 1
136
- elif len(matched_ind) == 1:
137
- matched_ind = matched_ind[0]
138
- else:
139
- raise PlatformException(error='400',
140
- message='more than one annotation with same object id: {}'.format(
141
- object_id))
142
-
143
- self.annotations[matched_ind].add_frames(annotation_definition=single_definition,
144
- frame_num=frame_num,
145
- end_frame_num=end_frame_num,
146
- start_time=start_time,
147
- end_time=end_time,
148
- fixed=fixed,
149
- object_visible=object_visible)
150
- else:
151
- # add new annotation to list
152
- self.annotations.append(annotation)
153
-
154
- ############
155
- # Plotting #
156
- ############
157
- def show(self,
158
- image=None,
159
- thickness=None,
160
- with_text=False,
161
- height=None,
162
- width=None,
163
- annotation_format: entities.ViewAnnotationOptions = entities.ViewAnnotationOptions.MASK,
164
- label_instance_dict=None,
165
- color=None,
166
- alpha=1.,
167
- frame_num=None):
168
- """
169
- Show annotations according to annotation_format
170
-
171
- **Prerequisites**: Any user can upload annotations.
172
-
173
- :param ndarray image: empty or image to draw on
174
- :param int height: height
175
- :param int width: width
176
- :param int thickness: line thickness
177
- :param bool with_text: add label to annotation
178
- :param dl.ViewAnnotationOptions annotation_format: how to show thw annotations. options: list(dl.ViewAnnotationOptions)
179
- :param dict label_instance_dict: instance label map {'Label': 1, 'More': 2}
180
- :param tuple color: optional - color tuple
181
- :param float alpha: opacity value [0 1], default 1
182
- :param int frame_num: for video annotation, show specific frame
183
- :return: ndarray of the annotations
184
-
185
- **Example**:
186
-
187
- .. code-block:: python
188
-
189
- image = builder.show(image='ndarray',
190
- thickness=1,
191
- annotation_format=dl.VIEW_ANNOTATION_OPTIONS_MASK)
192
- """
193
- # if 'video' in self.item.mimetype and (annotation_format != 'json' or annotation_format != ['json']):
194
- # raise PlatformException('400', 'Cannot show mask or instance of video item')
195
- # height/weight
196
- try:
197
- import cv2
198
- except (ImportError, ModuleNotFoundError):
199
- logger.error(
200
- 'Import Error! Cant import cv2. Annotations operations will be limited. import manually and fix errors')
201
- raise
202
-
203
- # split the annotations to binary and not binary to put the binaries first
204
- segment_annotations = list()
205
- rest_annotations = list()
206
- for annotation in self.annotations:
207
- if annotation.type == 'binary':
208
- segment_annotations.append(annotation)
209
- else:
210
- rest_annotations.append(annotation)
211
- all_annotations = segment_annotations + rest_annotations
212
- # gor over all annotations and put the id where the annotations are
213
- for annotation in all_annotations:
214
- # get the mask of the annotation
215
- image = annotation.show(thickness=thickness,
216
- with_text=with_text,
217
- height=height,
218
- width=width,
219
- label_instance_dict=label_instance_dict,
220
- annotation_format=annotation_format,
221
- image=image,
222
- alpha=alpha,
223
- color=color,
224
- frame_num=frame_num)
225
- return image
226
-
227
- def _video_maker(self,
228
- input_filepath,
229
- output_filepath,
230
- thickness=1,
231
- annotation_format=entities.ViewAnnotationOptions.ANNOTATION_ON_IMAGE,
232
- with_text=False,
233
- alpha=1.):
234
- """
235
- create a video from frames
236
-
237
- :param input_filepath: str - input file path
238
- :param output_filepath: str - out put file path
239
- :param thickness: int - thickness of the annotations
240
- :param annotation_format: str - ViewAnnotationOptions - annotations format
241
- :param with_text: bool - if True show the label in the output
242
- :param float alpha: opacity value [0 1], default 1
243
-
244
- """
245
- try:
246
- import cv2
247
- except (ImportError, ModuleNotFoundError):
248
- logger.error(
249
- 'Import Error! Cant import cv2. Annotations operations will be limited. import manually and fix errors')
250
- raise
251
-
252
- is_color = True
253
- if annotation_format in [entities.ViewAnnotationOptions.INSTANCE,
254
- entities.ViewAnnotationOptions.OBJECT_ID]:
255
- is_color = False
256
- # read input video
257
- fps = self.item.system.get('fps', 0)
258
- height = self.item.system.get('height', 0)
259
- width = self.item.system.get('width', 0)
260
- nb_frames = int(self.item.system.get('ffmpeg', {}).get('nb_read_frames'))
261
- writer = cv2.VideoWriter(filename=output_filepath,
262
- fourcc=cv2.VideoWriter_fourcc(*"mp4v"),
263
- fps=fps,
264
- frameSize=(width, height),
265
- isColor=is_color)
266
- if input_filepath is not None and is_color:
267
- reader = cv2.VideoCapture(input_filepath)
268
- else:
269
- reader = None
270
-
271
- for frame_num in range(nb_frames):
272
- if reader is not None:
273
- ret, frame = reader.read()
274
- frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
275
- else:
276
- frame = None
277
- frame = self.show(image=frame,
278
- annotation_format=annotation_format,
279
- thickness=thickness,
280
- alpha=alpha,
281
- height=height,
282
- width=width,
283
- with_text=with_text,
284
- frame_num=frame_num)
285
- if is_color:
286
- frame = cv2.cvtColor(frame, cv2.COLOR_RGBA2BGR)
287
- writer.write(frame)
288
- writer.release()
289
- if reader is not None:
290
- reader.release()
291
-
292
- @staticmethod
293
- def _set_flip_args(orientation):
294
- try:
295
- import cv2
296
- except (ImportError, ModuleNotFoundError):
297
- logger.error(
298
- 'Import Error! Cant import cv2. that make the exif orientation not supported')
299
- raise
300
- if orientation == 3:
301
- return cv2.ROTATE_180, cv2.ROTATE_180, 0
302
- elif orientation == 4:
303
- return cv2.ROTATE_180, cv2.ROTATE_180, 1
304
- elif orientation == 5:
305
- return cv2.ROTATE_90_CLOCKWISE, cv2.ROTATE_90_COUNTERCLOCKWISE, 1
306
- elif orientation == 6:
307
- return cv2.ROTATE_90_CLOCKWISE, cv2.ROTATE_90_COUNTERCLOCKWISE, 0
308
- elif orientation == 7:
309
- return cv2.ROTATE_90_COUNTERCLOCKWISE, cv2.ROTATE_90_CLOCKWISE, 1
310
- else:
311
- return cv2.ROTATE_90_COUNTERCLOCKWISE, cv2.ROTATE_90_CLOCKWISE, 0
312
-
313
- def download(self,
314
- filepath,
315
- img_filepath=None,
316
- annotation_format: entities.ViewAnnotationOptions = entities.ViewAnnotationOptions.JSON,
317
- height=None,
318
- width=None,
319
- thickness=1,
320
- with_text=False,
321
- orientation=0,
322
- alpha=1):
323
- """
324
- Save annotations to file
325
-
326
- **Prerequisites**: Any user can upload annotations.
327
-
328
- :param str filepath: path to save annotation
329
- :param str img_filepath: img file path - needed for img_mask
330
- :param dl.ViewAnnotationOptions annotation_format: how to show thw annotations. options: list(dl.ViewAnnotationOptions)
331
- :param int height: height
332
- :param int width: width
333
- :param int thickness: thickness
334
- :param bool with_text: add a text to an image
335
- :param int orientation: the image orientation
336
- :param float alpha: opacity value [0 1], default 1
337
- :return: file path of the download annotation
338
- :rtype: str
339
-
340
- **Example**:
341
-
342
- .. code-block:: python
343
-
344
- filepath = builder.download(filepath='filepath', annotation_format=dl.ViewAnnotationOptions.MASK)
345
- """
346
- dir_name, ex = os.path.splitext(filepath)
347
- if annotation_format == entities.ViewAnnotationOptions.JSON:
348
- if not ex:
349
- filepath = os.path.join(dir_name, '{}.json'.format(os.path.splitext(self.item.name)[0]))
350
- _json = dict()
351
- if self.item is not None:
352
- _json = {'_id': self.item.id,
353
- 'filename': self.item.filename}
354
- annotations = list()
355
- for ann in self.annotations:
356
- annotations.append(ann.to_json())
357
- _json['annotations'] = annotations
358
- if self.item is not None:
359
- _json['metadata'] = self.item.metadata
360
- with open(filepath, 'w+') as f:
361
- json.dump(_json, f, indent=2)
362
- elif annotation_format in [entities.ViewAnnotationOptions.MASK,
363
- entities.ViewAnnotationOptions.INSTANCE,
364
- entities.ViewAnnotationOptions.OBJECT_ID,
365
- entities.ViewAnnotationOptions.ANNOTATION_ON_IMAGE]:
366
- if not ex:
367
- if 'video' in self.item.mimetype:
368
- filepath = os.path.join(dir_name, '{}.mp4'.format(os.path.splitext(self.item.name)[0]))
369
- else:
370
- filepath = os.path.join(dir_name, '{}.png'.format(os.path.splitext(self.item.name)[0]))
371
- image = None
372
- if 'video' in self.item.mimetype:
373
- if annotation_format == entities.ViewAnnotationOptions.ANNOTATION_ON_IMAGE:
374
- if img_filepath is None:
375
- img_filepath = self.item.download()
376
- annotation_format = entities.ViewAnnotationOptions.MASK
377
- self._video_maker(input_filepath=img_filepath,
378
- output_filepath=filepath,
379
- thickness=thickness,
380
- annotation_format=annotation_format,
381
- with_text=with_text,
382
- alpha=alpha
383
- )
384
- return filepath
385
- if annotation_format == entities.ViewAnnotationOptions.ANNOTATION_ON_IMAGE:
386
- if img_filepath is None:
387
- img_filepath = self.item.download()
388
- annotation_format = entities.ViewAnnotationOptions.MASK
389
- image = np.asarray(Image.open(img_filepath))
390
- if orientation in [3, 4, 5, 6, 7, 8]:
391
- try:
392
- import cv2
393
- except (ImportError, ModuleNotFoundError):
394
- logger.error(
395
- 'Import Error! Cant import cv2. that make the exif orientation not supported')
396
- raise
397
- first_rotate, second_rotate, flip = self._set_flip_args(orientation=orientation)
398
- image = cv2.rotate(image, first_rotate)
399
- if flip:
400
- image = np.flip(image, 1)
401
- mask = self.show(image=image,
402
- thickness=thickness,
403
- with_text=with_text,
404
- height=height,
405
- width=width,
406
- alpha=alpha,
407
- annotation_format=annotation_format)
408
- if orientation not in [3, 4, 5, 6, 7, 8]:
409
- img = Image.fromarray(mask.astype(np.uint8))
410
- else:
411
- img = Image.fromarray(cv2.rotate(mask.astype(np.uint8), second_rotate))
412
- img.save(filepath)
413
- elif annotation_format == entities.ViewAnnotationOptions.VTT:
414
- if not ex:
415
- filepath = '{}/{}.vtt'.format(dir_name, os.path.splitext(self.item.name)[0])
416
- annotations_dict = [{'start_time': annotation.start_time,
417
- 'end_time': annotation.end_time,
418
- 'text': annotation.coordinates['text']} for annotation in self.annotations if
419
- annotation.type in ['subtitle']]
420
- sorted_by_start_time = sorted(annotations_dict, key=lambda i: i['start_time'])
421
- vtt = webvtt.WebVTT()
422
- for ann in sorted_by_start_time:
423
- s = str(datetime.timedelta(seconds=ann['start_time']))
424
- if len(s.split('.')) == 1:
425
- s += '.000'
426
- e = str(datetime.timedelta(seconds=ann['end_time']))
427
- if len(e.split('.')) == 1:
428
- e += '.000'
429
- caption = webvtt.Caption(
430
- '{}'.format(s),
431
- '{}'.format(e),
432
- '{}'.format(ann['text'])
433
- )
434
- vtt.captions.append(caption)
435
- vtt.save(filepath)
436
- else:
437
- raise PlatformException(error="400", message="Unknown annotation option: {}".format(annotation_format))
438
- return filepath
439
-
440
- ############
441
- # Platform #
442
- ############
443
- def update(self, system_metadata=True):
444
- """
445
- Update an existing annotation in host.
446
-
447
- **Prerequisites**: You must have an item that has been annotated. You must have the role of an *owner* or *developer* or be assigned a task that includes that item as an *annotation manager* or *annotator*.
448
-
449
- :param system_metadata: True, if you want to change metadata system
450
- :return: Annotation object
451
- :rtype: dtlpy.entities.annotation.Annotation
452
-
453
- **Example**:
454
-
455
- .. code-block:: python
456
-
457
- annotation = builder.update()
458
- """
459
- if self.item is None:
460
- raise PlatformException('400', 'missing item to perform platform update')
461
- return self.item.annotations.update(annotations=self.annotations, system_metadata=system_metadata)
462
-
463
- def delete(self):
464
- """
465
- Remove an annotation from item
466
-
467
- **Prerequisites**: You must have an item that has been annotated. You must have the role of an *owner* or *developer* or be assigned a task that includes that item as an *annotation manager* or *annotator*.
468
-
469
- :return: True if success
470
- :rtype: bool
471
-
472
- **Example**:
473
-
474
- .. code-block:: python
475
-
476
- is_deleted = builder.delete()
477
- """
478
- if self.item is None:
479
- raise PlatformException('400', 'missing item to perform platform delete')
480
- return [self.item.annotations.delete(annotation_id=annotation.id) for annotation in self.annotations]
481
-
482
- def upload(self):
483
- """
484
- Create a new annotation in host
485
-
486
- **Prerequisites**: Any user can upload annotations.
487
-
488
- :return: Annotation entity
489
- :rtype: dtlpy.entities.annotation.Annotation
490
-
491
- **Example**:
492
-
493
- .. code-block:: python
494
-
495
- annotation = builder.upload()
496
- """
497
- if self.item is None:
498
- raise PlatformException('400', 'missing item to perform platform upload')
499
- return self.item.annotations.upload(self.annotations)
500
-
501
- @staticmethod
502
- def _json_to_annotation(item: entities.Item, w_json: dict, is_video=None, fps=25, item_metadata=None,
503
- client_api=None):
504
- try:
505
- annotation = entities.Annotation.from_json(_json=w_json,
506
- fps=fps,
507
- item_metadata=item_metadata,
508
- is_video=is_video,
509
- item=item,
510
- client_api=client_api)
511
- status = True
512
- except Exception:
513
- annotation = traceback.format_exc()
514
- status = False
515
- return status, annotation
516
-
517
- @classmethod
518
- def from_json(cls, _json: list, item=None, is_video=None, fps=25, height=None, width=None,
519
- client_api=None, is_audio=None) -> 'AnnotationCollection':
520
- """
521
- Create an annotation collection object from platform json
522
-
523
- :param dict _json: platform json
524
- :param dtlpy.entities.item.Item item: item
525
- :param client_api: ApiClient entity
526
- :param bool is_video: whether the item is video
527
- :param fps: frame rate of the video
528
- :param float height: height
529
- :param float width: width
530
- :param bool is_audio: is audio
531
- :return: annotation object
532
- :rtype: dtlpy.entities.annotation.Annotation
533
- """
534
- if item is None:
535
- if isinstance(_json, dict):
536
- metadata = _json.get('metadata', dict())
537
- system_metadata = metadata.get('system', dict())
538
- if is_video is None:
539
- if 'mimetype' in system_metadata:
540
- is_video = 'video' in system_metadata['mimetype']
541
- is_audio = 'audio' in system_metadata['mimetype']
542
- elif 'filename' in _json:
543
- ext = os.path.splitext(_json['filename'])[-1]
544
- try:
545
- is_video = 'video' in mimetypes.types_map[ext.lower()]
546
- is_audio = 'is_audio' in mimetypes.types_map[ext.lower()]
547
- except Exception:
548
- logger.info("Unknown annotation's item type. Default item type is set to: image")
549
- else:
550
- logger.info("Unknown annotation's item type. Default item type is set to: image")
551
- if is_video:
552
- fps = system_metadata.get('fps', fps)
553
- ffmpeg_info = system_metadata.get('ffmpeg', dict())
554
- height = ffmpeg_info.get('height', None)
555
- width = ffmpeg_info.get('width', None)
556
- elif is_audio:
557
- fps = system_metadata.get('fps', 1000)
558
- height = system_metadata.get('height', None)
559
- width = system_metadata.get('width', None)
560
- else:
561
- fps = 0
562
- height = system_metadata.get('height', None)
563
- width = system_metadata.get('width', None)
564
- else:
565
- if client_api is None:
566
- client_api = item._client_api
567
- fps = item.fps
568
- height = item.height
569
- width = item.width
570
-
571
- item_metadata = {
572
- 'fps': fps,
573
- 'height': height,
574
- 'width': width
575
- }
576
-
577
- if 'annotations' in _json:
578
- _json = _json['annotations']
579
-
580
- results = list([None for _ in range(len(_json))])
581
- for i_json, single_json in enumerate(_json):
582
- results[i_json] = cls._json_to_annotation(item=item,
583
- fps=fps,
584
- item_metadata=item_metadata,
585
- is_video=is_video,
586
- w_json=single_json,
587
- client_api=client_api)
588
- # log errors
589
- _ = [logger.warning(j[1]) for j in results if j[0] is False]
590
-
591
- # return good jobs
592
- annotations = [j[1] for j in results if j[0] is True]
593
-
594
- # sort
595
- if is_video:
596
- annotations.sort(key=lambda x: x.start_frame)
597
- else:
598
- annotations.sort(key=lambda x: x.label)
599
-
600
- return cls(annotations=miscellaneous.List(annotations), item=item)
601
-
602
- @classmethod
603
- def from_json_file(cls, filepath, item=None):
604
- with open(filepath, 'r', encoding='utf-8') as f:
605
- data = json.load(f)
606
- return cls.from_json(_json=data, item=item)
607
-
608
- def from_vtt_file(self, filepath):
609
- """
610
- convert annotation from vtt format
611
-
612
- :param str filepath: path to the file
613
- """
614
- for caption in webvtt.read(filepath):
615
- h, m, s = caption.start.split(':')
616
- start_time = datetime.timedelta(hours=float(h), minutes=float(m), seconds=float(s)).total_seconds()
617
- h, m, s = caption.end.split(':')
618
- end_time = datetime.timedelta(hours=float(h), minutes=float(m), seconds=float(s)).total_seconds()
619
- annotation_definition = entities.Subtitle(text=caption.text, label='Text')
620
- annotation = entities.Annotation.new(
621
- annotation_definition=annotation_definition,
622
- item=self.item,
623
- start_time=start_time)
624
-
625
- annotation.add_frames(annotation_definition=annotation_definition,
626
- start_time=start_time,
627
- end_time=end_time)
628
-
629
- self.annotations.append(annotation)
630
-
631
- def from_instance_mask(self, mask, instance_map=None):
632
- """
633
- convert annotation from instance mask format
634
-
635
- :param mask: the mask annotation
636
- :param instance_map: labels
637
- """
638
- if instance_map is None:
639
- instance_map = self.item.dataset.instance_map
640
- # go over all instance ids
641
- for label, instance_id in instance_map.items():
642
- # find a binary mask per instance
643
- class_mask = instance_id == mask
644
- if not np.any(class_mask):
645
- continue
646
- # add the binary mask to the annotation builder
647
- self.add(annotation_definition=entities.Segmentation(geo=class_mask, label=label))
648
-
649
- def to_json(self):
650
- """
651
- Convert annotation object to a platform json representation
652
-
653
- :return: platform json
654
- :rtype: dict
655
- """
656
- if self.item is None:
657
- item_id = None
658
- item_name = None
659
- else:
660
- item_id = self.item.id
661
- item_name = self.item.filename
662
-
663
- _json = {
664
- "_id": item_id,
665
- "filename": item_name,
666
- 'annotations': [annotation.to_json() for annotation in self.annotations]
667
- }
668
-
669
- return _json
670
-
671
- def print(self, to_return=False, columns=None):
672
- """
673
-
674
- :param to_return:
675
- :param columns:
676
- """
677
- return miscellaneous.List(self.annotations).print(to_return=to_return, columns=columns)
678
-
679
- #########################
680
- # For video annotations #
681
- #########################
682
- def get_frame(self, frame_num):
683
- """
684
- Get frame
685
-
686
- :param int frame_num: frame num
687
- :return: AnnotationCollection
688
- """
689
- frame_collection = AnnotationCollection(item=self.item)
690
- for annotation in self.annotations:
691
- if frame_num in annotation.frames:
692
- annotation.set_frame(frame=frame_num)
693
- frame_collection.annotations.append(annotation)
694
- return frame_collection
695
-
696
- def video_player(self):
697
- from ..utilities.videos.video_player import VideoPlayer
698
- _ = VideoPlayer(dataset_id=self.item.dataset.id,
699
- item_id=self.item.id)
1
+ import mimetypes
2
+ import traceback
3
+ import datetime
4
+ import webvtt
5
+ import logging
6
+ import attr
7
+ import json
8
+ import os
9
+
10
+ import numpy as np
11
+ from PIL import Image
12
+
13
+ from .. import entities, PlatformException, miscellaneous
14
+
15
+ logger = logging.getLogger(name='dtlpy')
16
+
17
+
18
+ @attr.s
19
+ class AnnotationCollection(entities.BaseEntity):
20
+ """
21
+ Collection of Annotation entity
22
+ """
23
+ item = attr.ib(default=None)
24
+ annotations = attr.ib() # type: miscellaneous.List[entities.Annotation]
25
+ _dataset = attr.ib(repr=False, default=None)
26
+ _colors = attr.ib(repr=False, default=None)
27
+
28
+ @property
29
+ def dataset(self):
30
+ if self._dataset is None:
31
+ self._dataset = self.item.dataset
32
+ assert isinstance(self._dataset, entities.Dataset)
33
+ return self._dataset
34
+
35
+ @annotations.default
36
+ def set_annotations(self):
37
+ return list()
38
+
39
+ def __iter__(self):
40
+ for annotation in self.annotations:
41
+ yield annotation
42
+
43
+ def __getitem__(self, index: int):
44
+ return self.annotations[index]
45
+
46
+ def __len__(self):
47
+ return len(self.annotations)
48
+
49
+ def add(self,
50
+ annotation_definition,
51
+ object_id=None,
52
+ frame_num=None,
53
+ end_frame_num=None,
54
+ start_time=None,
55
+ end_time=None,
56
+ automated=True,
57
+ fixed=True,
58
+ object_visible=True,
59
+ metadata=None,
60
+ parent_id=None,
61
+ prompt_id=None,
62
+ model_info=None):
63
+ """
64
+ Add annotations to collection
65
+
66
+ :param annotation_definition: dl.Polygon, dl.Segmentation, dl.Point, dl.Box etc.
67
+ :param object_id: Object id (any id given by user). If video - must input to match annotations between frames
68
+ :param frame_num: video only, number of frame
69
+ :param end_frame_num: video only, the end frame of the annotation
70
+ :param start_time: video only, start time of the annotation
71
+ :param end_time: video only, end time of the annotation
72
+ :param automated:
73
+ :param fixed: video only, mark frame as fixed
74
+ :param object_visible: video only, does the annotated object is visible
75
+ :param metadata: optional, metadata dictionary for annotation
76
+ :param parent_id: set a parent for this annotation (parent annotation ID)
77
+ :param prompt_id: Connect the annotation with a specific prompt in a dl.PromptItem
78
+ :param model_info: optional - set model on annotation {'confidence': 0, # [Mandatory], (Float between 0-1)
79
+ 'name': '', # [Optional], ('name' refers to 'model_name')
80
+ 'model_id': ''} # [Optional]
81
+ :return:
82
+ """
83
+ if model_info is not None:
84
+ if not isinstance(model_info, dict) or 'confidence' not in model_info:
85
+ raise ValueError('"model_info" must be a dict with key: "confidence"')
86
+ if metadata is None:
87
+ metadata = dict()
88
+ if 'user' not in metadata:
89
+ metadata['user'] = dict()
90
+ confidence = float(model_info['confidence'])
91
+ if confidence < 0 or confidence > 1:
92
+ raise ValueError('"confidence" must be a float between 0 and 1')
93
+ metadata['user']['model'] = {'confidence': confidence,
94
+ 'name': model_info.get('name'),
95
+ 'model_id': model_info.get('model_id'),
96
+ }
97
+ if prompt_id is not None:
98
+ if metadata is None:
99
+ metadata = dict()
100
+ if 'system' not in metadata:
101
+ metadata['system'] = dict()
102
+ metadata['system']['promptId'] = prompt_id
103
+
104
+ # to support list of definitions with same parameters
105
+ if not isinstance(annotation_definition, list):
106
+ annotation_definition = [annotation_definition]
107
+
108
+ for single_definition in annotation_definition:
109
+ annotation = entities.Annotation.new(item=self.item,
110
+ annotation_definition=single_definition,
111
+ frame_num=frame_num,
112
+ automated=automated,
113
+ metadata=metadata,
114
+ object_id=object_id,
115
+ parent_id=parent_id,
116
+ start_time=start_time,
117
+ end_time=end_time)
118
+ # add frame if exists
119
+ if (frame_num is not None or start_time is not None) and (
120
+ self.item is None or 'audio' not in self.item.metadata.get('system').get('mimetype', '')):
121
+ if object_id is None:
122
+ raise ValueError('Video Annotation must have object_id.')
123
+ else:
124
+ if isinstance(object_id, int):
125
+ object_id = '{}'.format(object_id)
126
+ elif not isinstance(object_id, str) or not object_id.isnumeric():
127
+ raise ValueError('Object id must be an int or a string containing only numbers.')
128
+ # find matching element_id
129
+ matched_ind = [i_annotation
130
+ for i_annotation, annotation in enumerate(self.annotations)
131
+ if annotation.object_id == object_id]
132
+ if len(matched_ind) == 0:
133
+ # no matching object id found - create new one
134
+ self.annotations.append(annotation)
135
+ matched_ind = len(self.annotations) - 1
136
+ elif len(matched_ind) == 1:
137
+ matched_ind = matched_ind[0]
138
+ else:
139
+ raise PlatformException(error='400',
140
+ message='more than one annotation with same object id: {}'.format(
141
+ object_id))
142
+
143
+ self.annotations[matched_ind].add_frames(annotation_definition=single_definition,
144
+ frame_num=frame_num,
145
+ end_frame_num=end_frame_num,
146
+ start_time=start_time,
147
+ end_time=end_time,
148
+ fixed=fixed,
149
+ object_visible=object_visible)
150
+ else:
151
+ # add new annotation to list
152
+ self.annotations.append(annotation)
153
+
154
+ ############
155
+ # Plotting #
156
+ ############
157
+ def show(self,
158
+ image=None,
159
+ thickness=None,
160
+ with_text=False,
161
+ height=None,
162
+ width=None,
163
+ annotation_format: entities.ViewAnnotationOptions = entities.ViewAnnotationOptions.MASK,
164
+ label_instance_dict=None,
165
+ color=None,
166
+ alpha=1.,
167
+ frame_num=None):
168
+ """
169
+ Show annotations according to annotation_format
170
+
171
+ **Prerequisites**: Any user can upload annotations.
172
+
173
+ :param ndarray image: empty or image to draw on
174
+ :param int height: height
175
+ :param int width: width
176
+ :param int thickness: line thickness
177
+ :param bool with_text: add label to annotation
178
+ :param dl.ViewAnnotationOptions annotation_format: how to show thw annotations. options: list(dl.ViewAnnotationOptions)
179
+ :param dict label_instance_dict: instance label map {'Label': 1, 'More': 2}
180
+ :param tuple color: optional - color tuple
181
+ :param float alpha: opacity value [0 1], default 1
182
+ :param int frame_num: for video annotation, show specific frame
183
+ :return: ndarray of the annotations
184
+
185
+ **Example**:
186
+
187
+ .. code-block:: python
188
+
189
+ image = builder.show(image='ndarray',
190
+ thickness=1,
191
+ annotation_format=dl.VIEW_ANNOTATION_OPTIONS_MASK)
192
+ """
193
+ # if 'video' in self.item.mimetype and (annotation_format != 'json' or annotation_format != ['json']):
194
+ # raise PlatformException('400', 'Cannot show mask or instance of video item')
195
+ # height/weight
196
+ try:
197
+ import cv2
198
+ except (ImportError, ModuleNotFoundError):
199
+ logger.error(
200
+ 'Import Error! Cant import cv2. Annotations operations will be limited. import manually and fix errors')
201
+ raise
202
+
203
+ # split the annotations to binary and not binary to put the binaries first
204
+ segment_annotations = list()
205
+ rest_annotations = list()
206
+ for annotation in self.annotations:
207
+ if annotation.type == 'binary':
208
+ segment_annotations.append(annotation)
209
+ else:
210
+ rest_annotations.append(annotation)
211
+ all_annotations = segment_annotations + rest_annotations
212
+ # gor over all annotations and put the id where the annotations are
213
+ for annotation in all_annotations:
214
+ # get the mask of the annotation
215
+ image = annotation.show(thickness=thickness,
216
+ with_text=with_text,
217
+ height=height,
218
+ width=width,
219
+ label_instance_dict=label_instance_dict,
220
+ annotation_format=annotation_format,
221
+ image=image,
222
+ alpha=alpha,
223
+ color=color,
224
+ frame_num=frame_num)
225
+ return image
226
+
227
+ def _video_maker(self,
228
+ input_filepath,
229
+ output_filepath,
230
+ thickness=1,
231
+ annotation_format=entities.ViewAnnotationOptions.ANNOTATION_ON_IMAGE,
232
+ with_text=False,
233
+ alpha=1.):
234
+ """
235
+ create a video from frames
236
+
237
+ :param input_filepath: str - input file path
238
+ :param output_filepath: str - out put file path
239
+ :param thickness: int - thickness of the annotations
240
+ :param annotation_format: str - ViewAnnotationOptions - annotations format
241
+ :param with_text: bool - if True show the label in the output
242
+ :param float alpha: opacity value [0 1], default 1
243
+
244
+ """
245
+ try:
246
+ import cv2
247
+ except (ImportError, ModuleNotFoundError):
248
+ logger.error(
249
+ 'Import Error! Cant import cv2. Annotations operations will be limited. import manually and fix errors')
250
+ raise
251
+
252
+ is_color = True
253
+ if annotation_format in [entities.ViewAnnotationOptions.INSTANCE,
254
+ entities.ViewAnnotationOptions.OBJECT_ID]:
255
+ is_color = False
256
+ # read input video
257
+ fps = self.item.system.get('fps', 0)
258
+ height = self.item.system.get('height', 0)
259
+ width = self.item.system.get('width', 0)
260
+ nb_frames = int(self.item.system.get('ffmpeg', {}).get('nb_read_frames'))
261
+ writer = cv2.VideoWriter(filename=output_filepath,
262
+ fourcc=cv2.VideoWriter_fourcc(*"mp4v"),
263
+ fps=fps,
264
+ frameSize=(width, height),
265
+ isColor=is_color)
266
+ if input_filepath is not None and is_color:
267
+ reader = cv2.VideoCapture(input_filepath)
268
+ else:
269
+ reader = None
270
+
271
+ for frame_num in range(nb_frames):
272
+ if reader is not None:
273
+ ret, frame = reader.read()
274
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
275
+ else:
276
+ frame = None
277
+ frame = self.show(image=frame,
278
+ annotation_format=annotation_format,
279
+ thickness=thickness,
280
+ alpha=alpha,
281
+ height=height,
282
+ width=width,
283
+ with_text=with_text,
284
+ frame_num=frame_num)
285
+ if is_color:
286
+ frame = cv2.cvtColor(frame, cv2.COLOR_RGBA2BGR)
287
+ writer.write(frame)
288
+ writer.release()
289
+ if reader is not None:
290
+ reader.release()
291
+
292
+ @staticmethod
293
+ def _set_flip_args(orientation):
294
+ try:
295
+ import cv2
296
+ except (ImportError, ModuleNotFoundError):
297
+ logger.error(
298
+ 'Import Error! Cant import cv2. that make the exif orientation not supported')
299
+ raise
300
+ if orientation == 3:
301
+ return cv2.ROTATE_180, cv2.ROTATE_180, 0
302
+ elif orientation == 4:
303
+ return cv2.ROTATE_180, cv2.ROTATE_180, 1
304
+ elif orientation == 5:
305
+ return cv2.ROTATE_90_CLOCKWISE, cv2.ROTATE_90_COUNTERCLOCKWISE, 1
306
+ elif orientation == 6:
307
+ return cv2.ROTATE_90_CLOCKWISE, cv2.ROTATE_90_COUNTERCLOCKWISE, 0
308
+ elif orientation == 7:
309
+ return cv2.ROTATE_90_COUNTERCLOCKWISE, cv2.ROTATE_90_CLOCKWISE, 1
310
+ else:
311
+ return cv2.ROTATE_90_COUNTERCLOCKWISE, cv2.ROTATE_90_CLOCKWISE, 0
312
+
313
+ def download(self,
314
+ filepath,
315
+ img_filepath=None,
316
+ annotation_format: entities.ViewAnnotationOptions = entities.ViewAnnotationOptions.JSON,
317
+ height=None,
318
+ width=None,
319
+ thickness=1,
320
+ with_text=False,
321
+ orientation=0,
322
+ alpha=1):
323
+ """
324
+ Save annotations to file
325
+
326
+ **Prerequisites**: Any user can upload annotations.
327
+
328
+ :param str filepath: path to save annotation
329
+ :param str img_filepath: img file path - needed for img_mask
330
+ :param dl.ViewAnnotationOptions annotation_format: how to show thw annotations. options: list(dl.ViewAnnotationOptions)
331
+ :param int height: height
332
+ :param int width: width
333
+ :param int thickness: thickness
334
+ :param bool with_text: add a text to an image
335
+ :param int orientation: the image orientation
336
+ :param float alpha: opacity value [0 1], default 1
337
+ :return: file path of the download annotation
338
+ :rtype: str
339
+
340
+ **Example**:
341
+
342
+ .. code-block:: python
343
+
344
+ filepath = builder.download(filepath='filepath', annotation_format=dl.ViewAnnotationOptions.MASK)
345
+ """
346
+ dir_name, ex = os.path.splitext(filepath)
347
+ if annotation_format == entities.ViewAnnotationOptions.JSON:
348
+ if not ex:
349
+ filepath = os.path.join(dir_name, '{}.json'.format(os.path.splitext(self.item.name)[0]))
350
+ _json = dict()
351
+ if self.item is not None:
352
+ _json = {'_id': self.item.id,
353
+ 'filename': self.item.filename}
354
+ annotations = list()
355
+ for ann in self.annotations:
356
+ annotations.append(ann.to_json())
357
+ _json['annotations'] = annotations
358
+ if self.item is not None:
359
+ _json['metadata'] = self.item.metadata
360
+ with open(filepath, 'w+') as f:
361
+ json.dump(_json, f, indent=2)
362
+ elif annotation_format in [entities.ViewAnnotationOptions.MASK,
363
+ entities.ViewAnnotationOptions.INSTANCE,
364
+ entities.ViewAnnotationOptions.OBJECT_ID,
365
+ entities.ViewAnnotationOptions.ANNOTATION_ON_IMAGE]:
366
+ if not ex:
367
+ if 'video' in self.item.mimetype:
368
+ filepath = os.path.join(dir_name, '{}.mp4'.format(os.path.splitext(self.item.name)[0]))
369
+ else:
370
+ filepath = os.path.join(dir_name, '{}.png'.format(os.path.splitext(self.item.name)[0]))
371
+ image = None
372
+ if 'video' in self.item.mimetype:
373
+ if annotation_format == entities.ViewAnnotationOptions.ANNOTATION_ON_IMAGE:
374
+ if img_filepath is None:
375
+ img_filepath = self.item.download()
376
+ annotation_format = entities.ViewAnnotationOptions.MASK
377
+ self._video_maker(input_filepath=img_filepath,
378
+ output_filepath=filepath,
379
+ thickness=thickness,
380
+ annotation_format=annotation_format,
381
+ with_text=with_text,
382
+ alpha=alpha
383
+ )
384
+ return filepath
385
+ if annotation_format == entities.ViewAnnotationOptions.ANNOTATION_ON_IMAGE:
386
+ if img_filepath is None:
387
+ img_filepath = self.item.download()
388
+ annotation_format = entities.ViewAnnotationOptions.MASK
389
+ image = np.asarray(Image.open(img_filepath))
390
+ if orientation in [3, 4, 5, 6, 7, 8]:
391
+ try:
392
+ import cv2
393
+ except (ImportError, ModuleNotFoundError):
394
+ logger.error(
395
+ 'Import Error! Cant import cv2. that make the exif orientation not supported')
396
+ raise
397
+ first_rotate, second_rotate, flip = self._set_flip_args(orientation=orientation)
398
+ image = cv2.rotate(image, first_rotate)
399
+ if flip:
400
+ image = np.flip(image, 1)
401
+ mask = self.show(image=image,
402
+ thickness=thickness,
403
+ with_text=with_text,
404
+ height=height,
405
+ width=width,
406
+ alpha=alpha,
407
+ annotation_format=annotation_format)
408
+ if orientation not in [3, 4, 5, 6, 7, 8]:
409
+ img = Image.fromarray(mask.astype(np.uint8))
410
+ else:
411
+ img = Image.fromarray(cv2.rotate(mask.astype(np.uint8), second_rotate))
412
+ img.save(filepath)
413
+ elif annotation_format == entities.ViewAnnotationOptions.VTT:
414
+ if not ex:
415
+ filepath = '{}/{}.vtt'.format(dir_name, os.path.splitext(self.item.name)[0])
416
+ annotations_dict = [{'start_time': annotation.start_time,
417
+ 'end_time': annotation.end_time,
418
+ 'text': annotation.coordinates['text']} for annotation in self.annotations if
419
+ annotation.type in ['subtitle']]
420
+ sorted_by_start_time = sorted(annotations_dict, key=lambda i: i['start_time'])
421
+ vtt = webvtt.WebVTT()
422
+ for ann in sorted_by_start_time:
423
+ s = str(datetime.timedelta(seconds=ann['start_time']))
424
+ if len(s.split('.')) == 1:
425
+ s += '.000'
426
+ e = str(datetime.timedelta(seconds=ann['end_time']))
427
+ if len(e.split('.')) == 1:
428
+ e += '.000'
429
+ caption = webvtt.Caption(
430
+ '{}'.format(s),
431
+ '{}'.format(e),
432
+ '{}'.format(ann['text'])
433
+ )
434
+ vtt.captions.append(caption)
435
+ vtt.save(filepath)
436
+ else:
437
+ raise PlatformException(error="400", message="Unknown annotation option: {}".format(annotation_format))
438
+ return filepath
439
+
440
+ ############
441
+ # Platform #
442
+ ############
443
+ def update(self, system_metadata=True):
444
+ """
445
+ Update an existing annotation in host.
446
+
447
+ **Prerequisites**: You must have an item that has been annotated. You must have the role of an *owner* or *developer* or be assigned a task that includes that item as an *annotation manager* or *annotator*.
448
+
449
+ :param system_metadata: True, if you want to change metadata system
450
+ :return: Annotation object
451
+ :rtype: dtlpy.entities.annotation.Annotation
452
+
453
+ **Example**:
454
+
455
+ .. code-block:: python
456
+
457
+ annotation = builder.update()
458
+ """
459
+ if self.item is None:
460
+ raise PlatformException('400', 'missing item to perform platform update')
461
+ return self.item.annotations.update(annotations=self.annotations, system_metadata=system_metadata)
462
+
463
+ def delete(self):
464
+ """
465
+ Remove an annotation from item
466
+
467
+ **Prerequisites**: You must have an item that has been annotated. You must have the role of an *owner* or *developer* or be assigned a task that includes that item as an *annotation manager* or *annotator*.
468
+
469
+ :return: True if success
470
+ :rtype: bool
471
+
472
+ **Example**:
473
+
474
+ .. code-block:: python
475
+
476
+ is_deleted = builder.delete()
477
+ """
478
+ if self.item is None:
479
+ raise PlatformException('400', 'missing item to perform platform delete')
480
+ return [self.item.annotations.delete(annotation_id=annotation.id) for annotation in self.annotations]
481
+
482
+ def upload(self):
483
+ """
484
+ Create a new annotation in host
485
+
486
+ **Prerequisites**: Any user can upload annotations.
487
+
488
+ :return: Annotation entity
489
+ :rtype: dtlpy.entities.annotation.Annotation
490
+
491
+ **Example**:
492
+
493
+ .. code-block:: python
494
+
495
+ annotation = builder.upload()
496
+ """
497
+ if self.item is None:
498
+ raise PlatformException('400', 'missing item to perform platform upload')
499
+ return self.item.annotations.upload(self.annotations)
500
+
501
+ @staticmethod
502
+ def _json_to_annotation(item: entities.Item, w_json: dict, is_video=None, fps=25, item_metadata=None,
503
+ client_api=None):
504
+ try:
505
+ annotation = entities.Annotation.from_json(_json=w_json,
506
+ fps=fps,
507
+ item_metadata=item_metadata,
508
+ is_video=is_video,
509
+ item=item,
510
+ client_api=client_api)
511
+ status = True
512
+ except Exception:
513
+ annotation = traceback.format_exc()
514
+ status = False
515
+ return status, annotation
516
+
517
+ @classmethod
518
+ def from_json(cls, _json: list, item=None, is_video=None, fps=25, height=None, width=None,
519
+ client_api=None, is_audio=None) -> 'AnnotationCollection':
520
+ """
521
+ Create an annotation collection object from platform json
522
+
523
+ :param dict _json: platform json
524
+ :param dtlpy.entities.item.Item item: item
525
+ :param client_api: ApiClient entity
526
+ :param bool is_video: whether the item is video
527
+ :param fps: frame rate of the video
528
+ :param float height: height
529
+ :param float width: width
530
+ :param bool is_audio: is audio
531
+ :return: annotation object
532
+ :rtype: dtlpy.entities.annotation.Annotation
533
+ """
534
+ if item is None:
535
+ if isinstance(_json, dict):
536
+ metadata = _json.get('metadata', dict())
537
+ system_metadata = metadata.get('system', dict())
538
+ if is_video is None:
539
+ if 'mimetype' in system_metadata:
540
+ is_video = 'video' in system_metadata['mimetype']
541
+ is_audio = 'audio' in system_metadata['mimetype']
542
+ elif 'filename' in _json:
543
+ ext = os.path.splitext(_json['filename'])[-1]
544
+ try:
545
+ is_video = 'video' in mimetypes.types_map[ext.lower()]
546
+ is_audio = 'is_audio' in mimetypes.types_map[ext.lower()]
547
+ except Exception:
548
+ logger.info("Unknown annotation's item type. Default item type is set to: image")
549
+ else:
550
+ logger.info("Unknown annotation's item type. Default item type is set to: image")
551
+ if is_video:
552
+ fps = system_metadata.get('fps', fps)
553
+ ffmpeg_info = system_metadata.get('ffmpeg', dict())
554
+ height = ffmpeg_info.get('height', None)
555
+ width = ffmpeg_info.get('width', None)
556
+ elif is_audio:
557
+ fps = system_metadata.get('fps', 1000)
558
+ height = system_metadata.get('height', None)
559
+ width = system_metadata.get('width', None)
560
+ else:
561
+ fps = 0
562
+ height = system_metadata.get('height', None)
563
+ width = system_metadata.get('width', None)
564
+ else:
565
+ if client_api is None:
566
+ client_api = item._client_api
567
+ fps = item.fps
568
+ height = item.height
569
+ width = item.width
570
+
571
+ item_metadata = {
572
+ 'fps': fps,
573
+ 'height': height,
574
+ 'width': width
575
+ }
576
+
577
+ if 'annotations' in _json:
578
+ _json = _json['annotations']
579
+
580
+ results = list([None for _ in range(len(_json))])
581
+ for i_json, single_json in enumerate(_json):
582
+ results[i_json] = cls._json_to_annotation(item=item,
583
+ fps=fps,
584
+ item_metadata=item_metadata,
585
+ is_video=is_video,
586
+ w_json=single_json,
587
+ client_api=client_api)
588
+ # log errors
589
+ _ = [logger.warning(j[1]) for j in results if j[0] is False]
590
+
591
+ # return good jobs
592
+ annotations = [j[1] for j in results if j[0] is True]
593
+
594
+ # sort
595
+ if is_video:
596
+ annotations.sort(key=lambda x: x.start_frame)
597
+ else:
598
+ annotations.sort(key=lambda x: x.label)
599
+
600
+ return cls(annotations=miscellaneous.List(annotations), item=item)
601
+
602
+ @classmethod
603
+ def from_json_file(cls, filepath, item=None):
604
+ with open(filepath, 'r', encoding='utf-8') as f:
605
+ data = json.load(f)
606
+ return cls.from_json(_json=data, item=item)
607
+
608
+ def from_vtt_file(self, filepath):
609
+ """
610
+ convert annotation from vtt format
611
+
612
+ :param str filepath: path to the file
613
+ """
614
+ for caption in webvtt.read(filepath):
615
+ h, m, s = caption.start.split(':')
616
+ start_time = datetime.timedelta(hours=float(h), minutes=float(m), seconds=float(s)).total_seconds()
617
+ h, m, s = caption.end.split(':')
618
+ end_time = datetime.timedelta(hours=float(h), minutes=float(m), seconds=float(s)).total_seconds()
619
+ annotation_definition = entities.Subtitle(text=caption.text, label='Text')
620
+ annotation = entities.Annotation.new(
621
+ annotation_definition=annotation_definition,
622
+ item=self.item,
623
+ start_time=start_time)
624
+
625
+ annotation.add_frames(annotation_definition=annotation_definition,
626
+ start_time=start_time,
627
+ end_time=end_time)
628
+
629
+ self.annotations.append(annotation)
630
+
631
+ def from_instance_mask(self, mask, instance_map=None):
632
+ """
633
+ convert annotation from instance mask format
634
+
635
+ :param mask: the mask annotation
636
+ :param instance_map: labels
637
+ """
638
+ if instance_map is None:
639
+ instance_map = self.item.dataset.instance_map
640
+ # go over all instance ids
641
+ for label, instance_id in instance_map.items():
642
+ # find a binary mask per instance
643
+ class_mask = instance_id == mask
644
+ if not np.any(class_mask):
645
+ continue
646
+ # add the binary mask to the annotation builder
647
+ self.add(annotation_definition=entities.Segmentation(geo=class_mask, label=label))
648
+
649
+ def to_json(self):
650
+ """
651
+ Convert annotation object to a platform json representation
652
+
653
+ :return: platform json
654
+ :rtype: dict
655
+ """
656
+ if self.item is None:
657
+ item_id = None
658
+ item_name = None
659
+ else:
660
+ item_id = self.item.id
661
+ item_name = self.item.filename
662
+
663
+ _json = {
664
+ "_id": item_id,
665
+ "filename": item_name,
666
+ 'annotations': [annotation.to_json() for annotation in self.annotations]
667
+ }
668
+
669
+ return _json
670
+
671
+ def print(self, to_return=False, columns=None):
672
+ """
673
+
674
+ :param to_return:
675
+ :param columns:
676
+ """
677
+ return miscellaneous.List(self.annotations).print(to_return=to_return, columns=columns)
678
+
679
+ #########################
680
+ # For video annotations #
681
+ #########################
682
+ def get_frame(self, frame_num):
683
+ """
684
+ Get frame
685
+
686
+ :param int frame_num: frame num
687
+ :return: AnnotationCollection
688
+ """
689
+ frame_collection = AnnotationCollection(item=self.item)
690
+ for annotation in self.annotations:
691
+ if frame_num in annotation.frames:
692
+ annotation.set_frame(frame=frame_num)
693
+ frame_collection.annotations.append(annotation)
694
+ return frame_collection
695
+
696
+ def video_player(self):
697
+ from ..utilities.videos.video_player import VideoPlayer
698
+ _ = VideoPlayer(dataset_id=self.item.dataset.id,
699
+ item_id=self.item.id)