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.
- dtlpy/__init__.py +491 -491
- dtlpy/__version__.py +1 -1
- dtlpy/assets/__init__.py +26 -26
- dtlpy/assets/code_server/config.yaml +2 -2
- dtlpy/assets/code_server/installation.sh +24 -24
- dtlpy/assets/code_server/launch.json +13 -13
- dtlpy/assets/code_server/settings.json +2 -2
- dtlpy/assets/main.py +53 -53
- dtlpy/assets/main_partial.py +18 -18
- dtlpy/assets/mock.json +11 -11
- dtlpy/assets/model_adapter.py +83 -83
- dtlpy/assets/package.json +61 -61
- dtlpy/assets/package_catalog.json +29 -29
- dtlpy/assets/package_gitignore +307 -307
- dtlpy/assets/service_runners/__init__.py +33 -33
- dtlpy/assets/service_runners/converter.py +96 -96
- dtlpy/assets/service_runners/multi_method.py +49 -49
- dtlpy/assets/service_runners/multi_method_annotation.py +54 -54
- dtlpy/assets/service_runners/multi_method_dataset.py +55 -55
- dtlpy/assets/service_runners/multi_method_item.py +52 -52
- dtlpy/assets/service_runners/multi_method_json.py +52 -52
- dtlpy/assets/service_runners/single_method.py +37 -37
- dtlpy/assets/service_runners/single_method_annotation.py +43 -43
- dtlpy/assets/service_runners/single_method_dataset.py +43 -43
- dtlpy/assets/service_runners/single_method_item.py +41 -41
- dtlpy/assets/service_runners/single_method_json.py +42 -42
- dtlpy/assets/service_runners/single_method_multi_input.py +45 -45
- dtlpy/assets/voc_annotation_template.xml +23 -23
- dtlpy/caches/base_cache.py +32 -32
- dtlpy/caches/cache.py +473 -473
- dtlpy/caches/dl_cache.py +201 -201
- dtlpy/caches/filesystem_cache.py +89 -89
- dtlpy/caches/redis_cache.py +84 -84
- dtlpy/dlp/__init__.py +20 -20
- dtlpy/dlp/cli_utilities.py +367 -367
- dtlpy/dlp/command_executor.py +764 -764
- dtlpy/dlp/dlp +1 -1
- dtlpy/dlp/dlp.bat +1 -1
- dtlpy/dlp/dlp.py +128 -128
- dtlpy/dlp/parser.py +651 -651
- dtlpy/entities/__init__.py +83 -83
- dtlpy/entities/analytic.py +347 -347
- dtlpy/entities/annotation.py +1879 -1879
- dtlpy/entities/annotation_collection.py +699 -699
- dtlpy/entities/annotation_definitions/__init__.py +20 -20
- dtlpy/entities/annotation_definitions/base_annotation_definition.py +100 -100
- dtlpy/entities/annotation_definitions/box.py +195 -195
- dtlpy/entities/annotation_definitions/classification.py +67 -67
- dtlpy/entities/annotation_definitions/comparison.py +72 -72
- dtlpy/entities/annotation_definitions/cube.py +204 -204
- dtlpy/entities/annotation_definitions/cube_3d.py +149 -149
- dtlpy/entities/annotation_definitions/description.py +32 -32
- dtlpy/entities/annotation_definitions/ellipse.py +124 -124
- dtlpy/entities/annotation_definitions/free_text.py +62 -62
- dtlpy/entities/annotation_definitions/gis.py +69 -69
- dtlpy/entities/annotation_definitions/note.py +139 -139
- dtlpy/entities/annotation_definitions/point.py +117 -117
- dtlpy/entities/annotation_definitions/polygon.py +182 -182
- dtlpy/entities/annotation_definitions/polyline.py +111 -111
- dtlpy/entities/annotation_definitions/pose.py +92 -92
- dtlpy/entities/annotation_definitions/ref_image.py +86 -86
- dtlpy/entities/annotation_definitions/segmentation.py +240 -240
- dtlpy/entities/annotation_definitions/subtitle.py +34 -34
- dtlpy/entities/annotation_definitions/text.py +85 -85
- dtlpy/entities/annotation_definitions/undefined_annotation.py +74 -74
- dtlpy/entities/app.py +220 -220
- dtlpy/entities/app_module.py +107 -107
- dtlpy/entities/artifact.py +174 -174
- dtlpy/entities/assignment.py +399 -399
- dtlpy/entities/base_entity.py +214 -214
- dtlpy/entities/bot.py +113 -113
- dtlpy/entities/codebase.py +292 -292
- dtlpy/entities/collection.py +38 -38
- dtlpy/entities/command.py +169 -169
- dtlpy/entities/compute.py +449 -449
- dtlpy/entities/dataset.py +1299 -1299
- dtlpy/entities/directory_tree.py +44 -44
- dtlpy/entities/dpk.py +470 -470
- dtlpy/entities/driver.py +235 -235
- dtlpy/entities/execution.py +397 -397
- dtlpy/entities/feature.py +124 -124
- dtlpy/entities/feature_set.py +145 -145
- dtlpy/entities/filters.py +798 -798
- dtlpy/entities/gis_item.py +107 -107
- dtlpy/entities/integration.py +184 -184
- dtlpy/entities/item.py +959 -959
- dtlpy/entities/label.py +123 -123
- dtlpy/entities/links.py +85 -85
- dtlpy/entities/message.py +175 -175
- dtlpy/entities/model.py +684 -684
- dtlpy/entities/node.py +1005 -1005
- dtlpy/entities/ontology.py +810 -803
- dtlpy/entities/organization.py +287 -287
- dtlpy/entities/package.py +657 -657
- dtlpy/entities/package_defaults.py +5 -5
- dtlpy/entities/package_function.py +185 -185
- dtlpy/entities/package_module.py +113 -113
- dtlpy/entities/package_slot.py +118 -118
- dtlpy/entities/paged_entities.py +299 -299
- dtlpy/entities/pipeline.py +624 -624
- dtlpy/entities/pipeline_execution.py +279 -279
- dtlpy/entities/project.py +394 -394
- dtlpy/entities/prompt_item.py +505 -505
- dtlpy/entities/recipe.py +301 -301
- dtlpy/entities/reflect_dict.py +102 -102
- dtlpy/entities/resource_execution.py +138 -138
- dtlpy/entities/service.py +963 -963
- dtlpy/entities/service_driver.py +117 -117
- dtlpy/entities/setting.py +294 -294
- dtlpy/entities/task.py +495 -495
- dtlpy/entities/time_series.py +143 -143
- dtlpy/entities/trigger.py +426 -426
- dtlpy/entities/user.py +118 -118
- dtlpy/entities/webhook.py +124 -124
- dtlpy/examples/__init__.py +19 -19
- dtlpy/examples/add_labels.py +135 -135
- dtlpy/examples/add_metadata_to_item.py +21 -21
- dtlpy/examples/annotate_items_using_model.py +65 -65
- dtlpy/examples/annotate_video_using_model_and_tracker.py +75 -75
- dtlpy/examples/annotations_convert_to_voc.py +9 -9
- dtlpy/examples/annotations_convert_to_yolo.py +9 -9
- dtlpy/examples/convert_annotation_types.py +51 -51
- dtlpy/examples/converter.py +143 -143
- dtlpy/examples/copy_annotations.py +22 -22
- dtlpy/examples/copy_folder.py +31 -31
- dtlpy/examples/create_annotations.py +51 -51
- dtlpy/examples/create_video_annotations.py +83 -83
- dtlpy/examples/delete_annotations.py +26 -26
- dtlpy/examples/filters.py +113 -113
- dtlpy/examples/move_item.py +23 -23
- dtlpy/examples/play_video_annotation.py +13 -13
- dtlpy/examples/show_item_and_mask.py +53 -53
- dtlpy/examples/triggers.py +49 -49
- dtlpy/examples/upload_batch_of_items.py +20 -20
- dtlpy/examples/upload_items_and_custom_format_annotations.py +55 -55
- dtlpy/examples/upload_items_with_modalities.py +43 -43
- dtlpy/examples/upload_segmentation_annotations_from_mask_image.py +44 -44
- dtlpy/examples/upload_yolo_format_annotations.py +70 -70
- dtlpy/exceptions.py +125 -125
- dtlpy/miscellaneous/__init__.py +20 -20
- dtlpy/miscellaneous/dict_differ.py +95 -95
- dtlpy/miscellaneous/git_utils.py +217 -217
- dtlpy/miscellaneous/json_utils.py +14 -14
- dtlpy/miscellaneous/list_print.py +105 -105
- dtlpy/miscellaneous/zipping.py +130 -130
- dtlpy/ml/__init__.py +20 -20
- dtlpy/ml/base_feature_extractor_adapter.py +27 -27
- dtlpy/ml/base_model_adapter.py +1257 -1230
- dtlpy/ml/metrics.py +461 -461
- dtlpy/ml/predictions_utils.py +274 -274
- dtlpy/ml/summary_writer.py +57 -57
- dtlpy/ml/train_utils.py +60 -60
- dtlpy/new_instance.py +252 -252
- dtlpy/repositories/__init__.py +56 -56
- dtlpy/repositories/analytics.py +85 -85
- dtlpy/repositories/annotations.py +916 -916
- dtlpy/repositories/apps.py +383 -383
- dtlpy/repositories/artifacts.py +452 -452
- dtlpy/repositories/assignments.py +599 -599
- dtlpy/repositories/bots.py +213 -213
- dtlpy/repositories/codebases.py +559 -559
- dtlpy/repositories/collections.py +332 -332
- dtlpy/repositories/commands.py +152 -152
- dtlpy/repositories/compositions.py +61 -61
- dtlpy/repositories/computes.py +439 -439
- dtlpy/repositories/datasets.py +1504 -1504
- dtlpy/repositories/downloader.py +976 -923
- dtlpy/repositories/dpks.py +433 -433
- dtlpy/repositories/drivers.py +482 -482
- dtlpy/repositories/executions.py +815 -815
- dtlpy/repositories/feature_sets.py +226 -226
- dtlpy/repositories/features.py +255 -255
- dtlpy/repositories/integrations.py +484 -484
- dtlpy/repositories/items.py +912 -912
- dtlpy/repositories/messages.py +94 -94
- dtlpy/repositories/models.py +1000 -1000
- dtlpy/repositories/nodes.py +80 -80
- dtlpy/repositories/ontologies.py +511 -511
- dtlpy/repositories/organizations.py +525 -525
- dtlpy/repositories/packages.py +1941 -1941
- dtlpy/repositories/pipeline_executions.py +451 -451
- dtlpy/repositories/pipelines.py +640 -640
- dtlpy/repositories/projects.py +539 -539
- dtlpy/repositories/recipes.py +419 -399
- dtlpy/repositories/resource_executions.py +137 -137
- dtlpy/repositories/schema.py +120 -120
- dtlpy/repositories/service_drivers.py +213 -213
- dtlpy/repositories/services.py +1704 -1704
- dtlpy/repositories/settings.py +339 -339
- dtlpy/repositories/tasks.py +1477 -1477
- dtlpy/repositories/times_series.py +278 -278
- dtlpy/repositories/triggers.py +536 -536
- dtlpy/repositories/upload_element.py +257 -257
- dtlpy/repositories/uploader.py +661 -661
- dtlpy/repositories/webhooks.py +249 -249
- dtlpy/services/__init__.py +22 -22
- dtlpy/services/aihttp_retry.py +131 -131
- dtlpy/services/api_client.py +1785 -1785
- dtlpy/services/api_reference.py +40 -40
- dtlpy/services/async_utils.py +133 -133
- dtlpy/services/calls_counter.py +44 -44
- dtlpy/services/check_sdk.py +68 -68
- dtlpy/services/cookie.py +115 -115
- dtlpy/services/create_logger.py +156 -156
- dtlpy/services/events.py +84 -84
- dtlpy/services/logins.py +235 -235
- dtlpy/services/reporter.py +256 -256
- dtlpy/services/service_defaults.py +91 -91
- dtlpy/utilities/__init__.py +20 -20
- dtlpy/utilities/annotations/__init__.py +16 -16
- dtlpy/utilities/annotations/annotation_converters.py +269 -269
- dtlpy/utilities/base_package_runner.py +285 -264
- dtlpy/utilities/converter.py +1650 -1650
- dtlpy/utilities/dataset_generators/__init__.py +1 -1
- dtlpy/utilities/dataset_generators/dataset_generator.py +670 -670
- dtlpy/utilities/dataset_generators/dataset_generator_tensorflow.py +23 -23
- dtlpy/utilities/dataset_generators/dataset_generator_torch.py +21 -21
- dtlpy/utilities/local_development/__init__.py +1 -1
- dtlpy/utilities/local_development/local_session.py +179 -179
- dtlpy/utilities/reports/__init__.py +2 -2
- dtlpy/utilities/reports/figures.py +343 -343
- dtlpy/utilities/reports/report.py +71 -71
- dtlpy/utilities/videos/__init__.py +17 -17
- dtlpy/utilities/videos/video_player.py +598 -598
- dtlpy/utilities/videos/videos.py +470 -470
- {dtlpy-1.115.44.data → dtlpy-1.116.6.data}/scripts/dlp +1 -1
- dtlpy-1.116.6.data/scripts/dlp.bat +2 -0
- {dtlpy-1.115.44.data → dtlpy-1.116.6.data}/scripts/dlp.py +128 -128
- {dtlpy-1.115.44.dist-info → dtlpy-1.116.6.dist-info}/METADATA +186 -186
- dtlpy-1.116.6.dist-info/RECORD +239 -0
- {dtlpy-1.115.44.dist-info → dtlpy-1.116.6.dist-info}/WHEEL +1 -1
- {dtlpy-1.115.44.dist-info → dtlpy-1.116.6.dist-info}/licenses/LICENSE +200 -200
- tests/features/environment.py +551 -551
- dtlpy/assets/__pycache__/__init__.cpython-310.pyc +0 -0
- dtlpy-1.115.44.data/scripts/dlp.bat +0 -2
- dtlpy-1.115.44.dist-info/RECORD +0 -240
- {dtlpy-1.115.44.dist-info → dtlpy-1.116.6.dist-info}/entry_points.txt +0 -0
- {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)
|