dtlpy 1.115.44__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 -347
  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 -292
  73. dtlpy/entities/collection.py +38 -38
  74. dtlpy/entities/command.py +169 -169
  75. dtlpy/entities/compute.py +449 -449
  76. dtlpy/entities/dataset.py +1299 -1299
  77. dtlpy/entities/directory_tree.py +44 -44
  78. dtlpy/entities/dpk.py +470 -470
  79. dtlpy/entities/driver.py +235 -235
  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 -798
  84. dtlpy/entities/gis_item.py +107 -107
  85. dtlpy/entities/integration.py +184 -184
  86. dtlpy/entities/item.py +959 -959
  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 -505
  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 -963
  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 -1230
  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 -152
  164. dtlpy/repositories/compositions.py +61 -61
  165. dtlpy/repositories/computes.py +439 -439
  166. dtlpy/repositories/datasets.py +1504 -1504
  167. dtlpy/repositories/downloader.py +976 -923
  168. dtlpy/repositories/dpks.py +433 -433
  169. dtlpy/repositories/drivers.py +482 -482
  170. dtlpy/repositories/executions.py +815 -815
  171. dtlpy/repositories/feature_sets.py +226 -226
  172. dtlpy/repositories/features.py +255 -255
  173. dtlpy/repositories/integrations.py +484 -484
  174. dtlpy/repositories/items.py +912 -912
  175. dtlpy/repositories/messages.py +94 -94
  176. dtlpy/repositories/models.py +1000 -1000
  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 -661
  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 -1785
  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.115.44.data → dtlpy-1.116.6.data}/scripts/dlp +1 -1
  227. dtlpy-1.116.6.data/scripts/dlp.bat +2 -0
  228. {dtlpy-1.115.44.data → dtlpy-1.116.6.data}/scripts/dlp.py +128 -128
  229. {dtlpy-1.115.44.dist-info → dtlpy-1.116.6.dist-info}/METADATA +186 -186
  230. dtlpy-1.116.6.dist-info/RECORD +239 -0
  231. {dtlpy-1.115.44.dist-info → dtlpy-1.116.6.dist-info}/WHEEL +1 -1
  232. {dtlpy-1.115.44.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.115.44.data/scripts/dlp.bat +0 -2
  236. dtlpy-1.115.44.dist-info/RECORD +0 -240
  237. {dtlpy-1.115.44.dist-info → dtlpy-1.116.6.dist-info}/entry_points.txt +0 -0
  238. {dtlpy-1.115.44.dist-info → dtlpy-1.116.6.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)