dtlpy 1.113.10__py3-none-any.whl → 1.114.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dtlpy/__init__.py +488 -488
- dtlpy/__version__.py +1 -1
- dtlpy/assets/__init__.py +26 -26
- dtlpy/assets/__pycache__/__init__.cpython-38.pyc +0 -0
- 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 +311 -311
- 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 +296 -296
- dtlpy/entities/collection.py +38 -38
- dtlpy/entities/command.py +169 -169
- dtlpy/entities/compute.py +442 -442
- dtlpy/entities/dataset.py +1285 -1285
- dtlpy/entities/directory_tree.py +44 -44
- dtlpy/entities/dpk.py +470 -470
- dtlpy/entities/driver.py +222 -222
- dtlpy/entities/execution.py +397 -397
- dtlpy/entities/feature.py +124 -124
- dtlpy/entities/feature_set.py +145 -145
- dtlpy/entities/filters.py +641 -641
- dtlpy/entities/gis_item.py +107 -107
- dtlpy/entities/integration.py +184 -184
- dtlpy/entities/item.py +953 -953
- dtlpy/entities/label.py +123 -123
- dtlpy/entities/links.py +85 -85
- dtlpy/entities/message.py +175 -175
- dtlpy/entities/model.py +694 -691
- dtlpy/entities/node.py +1005 -1005
- dtlpy/entities/ontology.py +803 -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 +290 -267
- dtlpy/entities/pipeline.py +593 -593
- dtlpy/entities/pipeline_execution.py +279 -279
- dtlpy/entities/project.py +394 -394
- dtlpy/entities/prompt_item.py +499 -499
- dtlpy/entities/recipe.py +301 -301
- dtlpy/entities/reflect_dict.py +102 -102
- dtlpy/entities/resource_execution.py +138 -138
- dtlpy/entities/service.py +958 -958
- dtlpy/entities/service_driver.py +117 -117
- dtlpy/entities/setting.py +294 -294
- dtlpy/entities/task.py +491 -491
- 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 +945 -940
- 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 -348
- dtlpy/repositories/commands.py +158 -158
- dtlpy/repositories/compositions.py +61 -61
- dtlpy/repositories/computes.py +434 -406
- dtlpy/repositories/datasets.py +1291 -1291
- dtlpy/repositories/downloader.py +895 -895
- dtlpy/repositories/dpks.py +433 -433
- dtlpy/repositories/drivers.py +266 -266
- dtlpy/repositories/executions.py +817 -817
- dtlpy/repositories/feature_sets.py +226 -226
- dtlpy/repositories/features.py +238 -238
- dtlpy/repositories/integrations.py +484 -484
- dtlpy/repositories/items.py +909 -915
- dtlpy/repositories/messages.py +94 -94
- dtlpy/repositories/models.py +877 -867
- 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 +448 -448
- dtlpy/repositories/pipelines.py +642 -642
- dtlpy/repositories/projects.py +539 -539
- dtlpy/repositories/recipes.py +399 -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 +1124 -1124
- dtlpy/repositories/times_series.py +278 -278
- dtlpy/repositories/triggers.py +536 -536
- dtlpy/repositories/upload_element.py +257 -257
- dtlpy/repositories/uploader.py +651 -651
- dtlpy/repositories/webhooks.py +249 -249
- dtlpy/services/__init__.py +22 -22
- dtlpy/services/aihttp_retry.py +131 -131
- dtlpy/services/api_client.py +1782 -1782
- 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 +264 -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.113.10.data → dtlpy-1.114.13.data}/scripts/dlp +1 -1
- dtlpy-1.114.13.data/scripts/dlp.bat +2 -0
- {dtlpy-1.113.10.data → dtlpy-1.114.13.data}/scripts/dlp.py +128 -128
- {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/LICENSE +200 -200
- {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/METADATA +172 -172
- dtlpy-1.114.13.dist-info/RECORD +240 -0
- {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/WHEEL +1 -1
- tests/features/environment.py +551 -550
- dtlpy-1.113.10.data/scripts/dlp.bat +0 -2
- dtlpy-1.113.10.dist-info/RECORD +0 -244
- tests/assets/__init__.py +0 -0
- tests/assets/models_flow/__init__.py +0 -0
- tests/assets/models_flow/failedmain.py +0 -52
- tests/assets/models_flow/main.py +0 -62
- tests/assets/models_flow/main_model.py +0 -54
- {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/entry_points.txt +0 -0
- {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/top_level.txt +0 -0
dtlpy/entities/annotation.py
CHANGED
|
@@ -1,1879 +1,1879 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
import traceback
|
|
3
|
-
import logging
|
|
4
|
-
import datetime
|
|
5
|
-
import webvtt
|
|
6
|
-
import copy
|
|
7
|
-
import attr
|
|
8
|
-
import json
|
|
9
|
-
import os
|
|
10
|
-
import warnings
|
|
11
|
-
|
|
12
|
-
from PIL import Image
|
|
13
|
-
from enum import Enum
|
|
14
|
-
|
|
15
|
-
from .. import entities, PlatformException, repositories, ApiClient, exceptions
|
|
16
|
-
|
|
17
|
-
logger = logging.getLogger(name='dtlpy')
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class ExportVersion(str, Enum):
|
|
21
|
-
V1 = "V1"
|
|
22
|
-
V2 = "V2"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class AnnotationStatus(str, Enum):
|
|
26
|
-
ISSUE = "issue"
|
|
27
|
-
APPROVED = "approved"
|
|
28
|
-
REVIEW = "review"
|
|
29
|
-
CLEAR = "clear"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class AnnotationType(str, Enum):
|
|
33
|
-
BOX = "box"
|
|
34
|
-
CUBE = "cube"
|
|
35
|
-
CUBE3D = "cube_3d"
|
|
36
|
-
CLASSIFICATION = "class"
|
|
37
|
-
COMPARISON = "comparison"
|
|
38
|
-
ELLIPSE = "ellipse"
|
|
39
|
-
NOTE = "note"
|
|
40
|
-
POINT = "point"
|
|
41
|
-
POLYGON = "segment"
|
|
42
|
-
POLYLINE = "polyline"
|
|
43
|
-
POSE = "pose"
|
|
44
|
-
SEGMENTATION = "binary"
|
|
45
|
-
SUBTITLE = "subtitle"
|
|
46
|
-
TEXT = "text_mark"
|
|
47
|
-
GIS = "gis"
|
|
48
|
-
SEMANTIC_3D = "ref_semantic_3d"
|
|
49
|
-
POLYLINE_3D = "polyline_3d"
|
|
50
|
-
FREE_TEXT = "text"
|
|
51
|
-
REF_IMAGE = "ref_image"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class ViewAnnotationOptions(str, Enum):
|
|
55
|
-
""" The Annotations file types to download (JSON, MASK, INSTANCE, ANNOTATION_ON_IMAGE, VTT, OBJECT_ID).
|
|
56
|
-
|
|
57
|
-
.. list-table::
|
|
58
|
-
:widths: 15 150
|
|
59
|
-
:header-rows: 1
|
|
60
|
-
|
|
61
|
-
* - State
|
|
62
|
-
- Description
|
|
63
|
-
* - JSON
|
|
64
|
-
- Dataloop json format
|
|
65
|
-
* - MASK
|
|
66
|
-
- PNG file that contains drawing annotations on it
|
|
67
|
-
* - INSTANCE
|
|
68
|
-
- An image file that contains 2D annotations
|
|
69
|
-
* - ANNOTATION_ON_IMAGE
|
|
70
|
-
- The source image with the annotations drawing in it
|
|
71
|
-
* - VTT
|
|
72
|
-
- An text file contains supplementary information about a web video
|
|
73
|
-
* - OBJECT_ID
|
|
74
|
-
- An image file that contains 2D annotations
|
|
75
|
-
|
|
76
|
-
"""
|
|
77
|
-
JSON = "json"
|
|
78
|
-
MASK = "mask"
|
|
79
|
-
INSTANCE = "instance"
|
|
80
|
-
ANNOTATION_ON_IMAGE = "img_mask"
|
|
81
|
-
VTT = "vtt"
|
|
82
|
-
OBJECT_ID = "object_id"
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
@attr.s
|
|
86
|
-
class Annotation(entities.BaseEntity):
|
|
87
|
-
"""
|
|
88
|
-
Annotations object
|
|
89
|
-
"""
|
|
90
|
-
# platform
|
|
91
|
-
id = attr.ib()
|
|
92
|
-
url = attr.ib(repr=False)
|
|
93
|
-
item_url = attr.ib(repr=False)
|
|
94
|
-
_item = attr.ib(repr=False)
|
|
95
|
-
item_id = attr.ib()
|
|
96
|
-
creator = attr.ib()
|
|
97
|
-
created_at = attr.ib()
|
|
98
|
-
updated_by = attr.ib(repr=False)
|
|
99
|
-
updated_at = attr.ib(repr=False)
|
|
100
|
-
type = attr.ib()
|
|
101
|
-
source = attr.ib(repr=False)
|
|
102
|
-
dataset_url = attr.ib(repr=False)
|
|
103
|
-
|
|
104
|
-
# api
|
|
105
|
-
_platform_dict = attr.ib(repr=False)
|
|
106
|
-
# meta
|
|
107
|
-
metadata = attr.ib(repr=False)
|
|
108
|
-
fps = attr.ib(repr=False)
|
|
109
|
-
hash = attr.ib(default=None, repr=False)
|
|
110
|
-
dataset_id = attr.ib(default=None, repr=False)
|
|
111
|
-
status = attr.ib(default=None, repr=False)
|
|
112
|
-
object_id = attr.ib(default=None, repr=False)
|
|
113
|
-
automated = attr.ib(default=None, repr=False)
|
|
114
|
-
item_height = attr.ib(default=None)
|
|
115
|
-
item_width = attr.ib(default=None)
|
|
116
|
-
label_suggestions = attr.ib(default=None)
|
|
117
|
-
|
|
118
|
-
# annotation definition
|
|
119
|
-
_annotation_definition = attr.ib(default=None, repr=False, type=entities.BaseAnnotationDefinition)
|
|
120
|
-
|
|
121
|
-
# snapshots
|
|
122
|
-
frames = attr.ib(default=None, repr=False)
|
|
123
|
-
current_frame = attr.ib(default=0, repr=False)
|
|
124
|
-
|
|
125
|
-
# video attributes
|
|
126
|
-
_end_frame = attr.ib(default=0, repr=False)
|
|
127
|
-
_end_time = attr.ib(default=0, repr=False)
|
|
128
|
-
_start_frame = attr.ib(default=0)
|
|
129
|
-
_start_time = attr.ib(default=0)
|
|
130
|
-
|
|
131
|
-
# sdk
|
|
132
|
-
_dataset = attr.ib(repr=False, default=None)
|
|
133
|
-
_datasets = attr.ib(repr=False, default=None)
|
|
134
|
-
_annotations = attr.ib(repr=False, default=None)
|
|
135
|
-
__client_api = attr.ib(default=None, repr=False)
|
|
136
|
-
_items = attr.ib(repr=False, default=None)
|
|
137
|
-
_recipe_1_attributes = attr.ib(repr=False, default=None)
|
|
138
|
-
|
|
139
|
-
############
|
|
140
|
-
# Platform #
|
|
141
|
-
############
|
|
142
|
-
|
|
143
|
-
@property
|
|
144
|
-
def annotation_definition(self):
|
|
145
|
-
return self._annotation_definition
|
|
146
|
-
|
|
147
|
-
@annotation_definition.setter
|
|
148
|
-
def annotation_definition(self, ann_def):
|
|
149
|
-
if ann_def is not None:
|
|
150
|
-
ann_def._annotation = self
|
|
151
|
-
self._annotation_definition = ann_def
|
|
152
|
-
|
|
153
|
-
@property
|
|
154
|
-
def createdAt(self):
|
|
155
|
-
return self.created_at
|
|
156
|
-
|
|
157
|
-
@property
|
|
158
|
-
def updatedAt(self):
|
|
159
|
-
return self.updated_at
|
|
160
|
-
|
|
161
|
-
@property
|
|
162
|
-
def updatedBy(self):
|
|
163
|
-
return self.updated_by
|
|
164
|
-
|
|
165
|
-
@property
|
|
166
|
-
def _client_api(self) -> ApiClient:
|
|
167
|
-
if self.__client_api is None:
|
|
168
|
-
if self._item is None:
|
|
169
|
-
raise PlatformException('400',
|
|
170
|
-
'This action cannot be performed without an item entity. Please set item')
|
|
171
|
-
else:
|
|
172
|
-
self.__client_api = self._item._client_api
|
|
173
|
-
assert isinstance(self.__client_api, ApiClient)
|
|
174
|
-
return self.__client_api
|
|
175
|
-
|
|
176
|
-
@property
|
|
177
|
-
def dataset(self):
|
|
178
|
-
if self._dataset is None:
|
|
179
|
-
if self._item is not None:
|
|
180
|
-
# get from item
|
|
181
|
-
self._dataset = self._item.dataset
|
|
182
|
-
else:
|
|
183
|
-
# get directly
|
|
184
|
-
self._dataset = self.datasets.get(dataset_id=self.dataset_id)
|
|
185
|
-
assert isinstance(self._dataset, entities.Dataset)
|
|
186
|
-
return self._dataset
|
|
187
|
-
|
|
188
|
-
@property
|
|
189
|
-
def item(self):
|
|
190
|
-
if self._item is None:
|
|
191
|
-
self._item = self.items.get(item_id=self.item_id)
|
|
192
|
-
assert isinstance(self._item, entities.Item)
|
|
193
|
-
return self._item
|
|
194
|
-
|
|
195
|
-
@property
|
|
196
|
-
def annotations(self):
|
|
197
|
-
if self._annotations is None:
|
|
198
|
-
self._annotations = repositories.Annotations(client_api=self._client_api, item=self._item)
|
|
199
|
-
assert isinstance(self._annotations, repositories.Annotations)
|
|
200
|
-
return self._annotations
|
|
201
|
-
|
|
202
|
-
@property
|
|
203
|
-
def datasets(self):
|
|
204
|
-
if self._datasets is None:
|
|
205
|
-
self._datasets = repositories.Datasets(client_api=self._client_api)
|
|
206
|
-
assert isinstance(self._datasets, repositories.Datasets)
|
|
207
|
-
return self._datasets
|
|
208
|
-
|
|
209
|
-
@property
|
|
210
|
-
def items(self):
|
|
211
|
-
if self._items is None:
|
|
212
|
-
if self._datasets is not None:
|
|
213
|
-
self._items = self._dataset.items
|
|
214
|
-
elif self._item is not None:
|
|
215
|
-
self._items = self._item.items
|
|
216
|
-
else:
|
|
217
|
-
self._items = repositories.Items(client_api=self._client_api, dataset=self._dataset)
|
|
218
|
-
assert isinstance(self._items, repositories.Items)
|
|
219
|
-
return self._items
|
|
220
|
-
|
|
221
|
-
#########################
|
|
222
|
-
# Annotation Properties #
|
|
223
|
-
#########################
|
|
224
|
-
@property
|
|
225
|
-
def parent_id(self):
|
|
226
|
-
try:
|
|
227
|
-
parent_id = self.metadata['system']['parentId']
|
|
228
|
-
except KeyError:
|
|
229
|
-
parent_id = None
|
|
230
|
-
return parent_id
|
|
231
|
-
|
|
232
|
-
@parent_id.setter
|
|
233
|
-
def parent_id(self, parent_id):
|
|
234
|
-
if 'system' not in self.metadata:
|
|
235
|
-
self.metadata['system'] = dict()
|
|
236
|
-
self.metadata['system']['parentId'] = parent_id
|
|
237
|
-
|
|
238
|
-
@property
|
|
239
|
-
def coordinates(self):
|
|
240
|
-
color = None
|
|
241
|
-
if self.type in ['binary']:
|
|
242
|
-
color = self.color
|
|
243
|
-
coordinates = self.annotation_definition.to_coordinates(color=color)
|
|
244
|
-
return coordinates
|
|
245
|
-
|
|
246
|
-
@property
|
|
247
|
-
def start_frame(self):
|
|
248
|
-
return self._start_frame
|
|
249
|
-
|
|
250
|
-
@start_frame.setter
|
|
251
|
-
def start_frame(self, val):
|
|
252
|
-
if not isinstance(val, float) and not isinstance(val, int):
|
|
253
|
-
raise ValueError('Must input a valid number')
|
|
254
|
-
self._start_frame = round(val)
|
|
255
|
-
self._start_time = val / self.fps if self.fps else 0
|
|
256
|
-
self.frames.start = self._start_frame
|
|
257
|
-
|
|
258
|
-
@property
|
|
259
|
-
def end_frame(self):
|
|
260
|
-
return self._end_frame
|
|
261
|
-
|
|
262
|
-
@end_frame.setter
|
|
263
|
-
def end_frame(self, val):
|
|
264
|
-
if not isinstance(val, float) and not isinstance(val, int):
|
|
265
|
-
raise ValueError('Must input a valid number')
|
|
266
|
-
self._end_frame = round(val)
|
|
267
|
-
self._end_time = val / self.fps if self.fps else 0
|
|
268
|
-
self.frames.end = self._end_frame
|
|
269
|
-
|
|
270
|
-
@property
|
|
271
|
-
def start_time(self):
|
|
272
|
-
return self._start_time
|
|
273
|
-
|
|
274
|
-
@start_time.setter
|
|
275
|
-
def start_time(self, val):
|
|
276
|
-
if not isinstance(val, float) and not isinstance(val, int):
|
|
277
|
-
raise ValueError('Must input a valid number')
|
|
278
|
-
self._start_frame = round(val * self.fps if self.fps else 0)
|
|
279
|
-
self._start_time = val
|
|
280
|
-
self.frames.start = self._start_frame
|
|
281
|
-
|
|
282
|
-
@property
|
|
283
|
-
def end_time(self):
|
|
284
|
-
return self._end_time
|
|
285
|
-
|
|
286
|
-
@end_time.setter
|
|
287
|
-
def end_time(self, val):
|
|
288
|
-
if not isinstance(val, float) and not isinstance(val, int):
|
|
289
|
-
raise ValueError('Must input a valid number')
|
|
290
|
-
self._end_frame = round(val * self.fps if self.fps else 0)
|
|
291
|
-
self._end_time = val
|
|
292
|
-
self.frames.end = self._end_frame
|
|
293
|
-
|
|
294
|
-
@property
|
|
295
|
-
def x(self):
|
|
296
|
-
return self.annotation_definition.x
|
|
297
|
-
|
|
298
|
-
@property
|
|
299
|
-
def y(self):
|
|
300
|
-
return self.annotation_definition.y
|
|
301
|
-
|
|
302
|
-
@property
|
|
303
|
-
def rx(self):
|
|
304
|
-
if self.annotation_definition.type == 'ellipse':
|
|
305
|
-
return self.annotation_definition.rx
|
|
306
|
-
else:
|
|
307
|
-
return None
|
|
308
|
-
|
|
309
|
-
@property
|
|
310
|
-
def ry(self):
|
|
311
|
-
if self.annotation_definition.type == 'ellipse':
|
|
312
|
-
return self.annotation_definition.ry
|
|
313
|
-
else:
|
|
314
|
-
return None
|
|
315
|
-
|
|
316
|
-
@property
|
|
317
|
-
def angle(self):
|
|
318
|
-
if self.annotation_definition.type in ['ellipse', 'cube', 'box']:
|
|
319
|
-
return self.annotation_definition.angle
|
|
320
|
-
else:
|
|
321
|
-
return None
|
|
322
|
-
|
|
323
|
-
@property
|
|
324
|
-
def messages(self):
|
|
325
|
-
if hasattr(self.annotation_definition, 'messages'):
|
|
326
|
-
return self.annotation_definition.messages
|
|
327
|
-
else:
|
|
328
|
-
return None
|
|
329
|
-
|
|
330
|
-
@messages.setter
|
|
331
|
-
def messages(self, messages):
|
|
332
|
-
if self.type == 'note':
|
|
333
|
-
self.annotation_definition.messages = messages
|
|
334
|
-
else:
|
|
335
|
-
raise PlatformException('400', 'Annotation of type {} does not have attribute messages'.format(self.type))
|
|
336
|
-
|
|
337
|
-
def add_message(self, body: str = None):
|
|
338
|
-
if self.type == 'note':
|
|
339
|
-
return self.annotation_definition.add_message(body=body)
|
|
340
|
-
else:
|
|
341
|
-
raise PlatformException('400', 'Annotation of type {} does not have method add_message'.format(self.type))
|
|
342
|
-
|
|
343
|
-
@property
|
|
344
|
-
def geo(self):
|
|
345
|
-
return self.annotation_definition.geo
|
|
346
|
-
|
|
347
|
-
@geo.setter
|
|
348
|
-
def geo(self, geo):
|
|
349
|
-
self.annotation_definition.geo = geo
|
|
350
|
-
|
|
351
|
-
@property
|
|
352
|
-
def top(self):
|
|
353
|
-
return self.annotation_definition.top
|
|
354
|
-
|
|
355
|
-
@top.setter
|
|
356
|
-
def top(self, top):
|
|
357
|
-
self.annotation_definition.top = top
|
|
358
|
-
|
|
359
|
-
@property
|
|
360
|
-
def bottom(self):
|
|
361
|
-
return self.annotation_definition.bottom
|
|
362
|
-
|
|
363
|
-
@bottom.setter
|
|
364
|
-
def bottom(self, bottom):
|
|
365
|
-
self.annotation_definition.bottom = bottom
|
|
366
|
-
|
|
367
|
-
@property
|
|
368
|
-
def left(self):
|
|
369
|
-
return self.annotation_definition.left
|
|
370
|
-
|
|
371
|
-
@left.setter
|
|
372
|
-
def left(self, left):
|
|
373
|
-
self.annotation_definition.left = left
|
|
374
|
-
|
|
375
|
-
@property
|
|
376
|
-
def right(self):
|
|
377
|
-
return self.annotation_definition.right
|
|
378
|
-
|
|
379
|
-
@right.setter
|
|
380
|
-
def right(self, right):
|
|
381
|
-
self.annotation_definition.right = right
|
|
382
|
-
|
|
383
|
-
@property
|
|
384
|
-
def height(self):
|
|
385
|
-
return self.annotation_definition.height
|
|
386
|
-
|
|
387
|
-
@height.setter
|
|
388
|
-
def height(self, height):
|
|
389
|
-
self.annotation_definition.height = height
|
|
390
|
-
|
|
391
|
-
@property
|
|
392
|
-
def width(self):
|
|
393
|
-
return self.annotation_definition.width
|
|
394
|
-
|
|
395
|
-
@width.setter
|
|
396
|
-
def width(self, width):
|
|
397
|
-
self.annotation_definition.width = width
|
|
398
|
-
|
|
399
|
-
@property
|
|
400
|
-
def description(self):
|
|
401
|
-
return self.annotation_definition.description
|
|
402
|
-
|
|
403
|
-
@description.setter
|
|
404
|
-
def description(self, description):
|
|
405
|
-
if description is None:
|
|
406
|
-
description = ""
|
|
407
|
-
if not isinstance(description, str):
|
|
408
|
-
raise ValueError("Description must get string")
|
|
409
|
-
self.annotation_definition.description = description
|
|
410
|
-
|
|
411
|
-
@property
|
|
412
|
-
def last_frame(self):
|
|
413
|
-
if len(self.frames.actual_keys()) == 0:
|
|
414
|
-
return 0
|
|
415
|
-
return max(self.frames.actual_keys())
|
|
416
|
-
|
|
417
|
-
@property
|
|
418
|
-
def label(self):
|
|
419
|
-
return self.annotation_definition.label
|
|
420
|
-
|
|
421
|
-
@label.setter
|
|
422
|
-
def label(self, label):
|
|
423
|
-
self.annotation_definition.label = label
|
|
424
|
-
|
|
425
|
-
@property
|
|
426
|
-
def attributes(self):
|
|
427
|
-
return self.annotation_definition.attributes
|
|
428
|
-
|
|
429
|
-
@attributes.setter
|
|
430
|
-
def attributes(self, attributes):
|
|
431
|
-
self.annotation_definition.attributes = attributes
|
|
432
|
-
|
|
433
|
-
@property
|
|
434
|
-
def color(self):
|
|
435
|
-
# if "dataset" is not in self - this will always get the dataset
|
|
436
|
-
try:
|
|
437
|
-
colors = self.dataset._get_ontology().color_map
|
|
438
|
-
except (exceptions.BadRequest, exceptions.NotFound):
|
|
439
|
-
colors = None
|
|
440
|
-
logger.warning('Cant get dataset for annotation color. using default.')
|
|
441
|
-
if colors is not None and self.label in colors:
|
|
442
|
-
color = colors[self.label]
|
|
443
|
-
else:
|
|
444
|
-
if self.type == 'binary' and self.annotation_definition._color is not None:
|
|
445
|
-
color = self.annotation_definition._color
|
|
446
|
-
else:
|
|
447
|
-
color = (255, 255, 255)
|
|
448
|
-
return color
|
|
449
|
-
|
|
450
|
-
@color.setter
|
|
451
|
-
def color(self, color):
|
|
452
|
-
if self.type == 'binary':
|
|
453
|
-
if not isinstance(color, tuple) or len(color) != 3:
|
|
454
|
-
raise ValueError("Color must get tuple of length 3")
|
|
455
|
-
self.annotation_definition._color = color
|
|
456
|
-
else:
|
|
457
|
-
raise exceptions.BadRequest(
|
|
458
|
-
status_code='400',
|
|
459
|
-
message='Invalid annotation type - Updating color is only available to binary annotation'
|
|
460
|
-
)
|
|
461
|
-
|
|
462
|
-
####################
|
|
463
|
-
# frame attributes #
|
|
464
|
-
####################
|
|
465
|
-
@property
|
|
466
|
-
def frame_num(self):
|
|
467
|
-
if len(self.frames.actual_keys()) > 0:
|
|
468
|
-
return self.current_frame
|
|
469
|
-
else:
|
|
470
|
-
return self.start_frame
|
|
471
|
-
|
|
472
|
-
@frame_num.setter
|
|
473
|
-
def frame_num(self, frame_num):
|
|
474
|
-
if frame_num != self.current_frame:
|
|
475
|
-
self.frames[self.current_frame].frame_num = frame_num
|
|
476
|
-
self.frames[frame_num] = self.frames[self.current_frame]
|
|
477
|
-
self.frames.pop(self.current_frame)
|
|
478
|
-
|
|
479
|
-
@property
|
|
480
|
-
def fixed(self):
|
|
481
|
-
if len(self.frames.actual_keys()) > 0:
|
|
482
|
-
return self.frames[self.current_frame].fixed
|
|
483
|
-
else:
|
|
484
|
-
return False
|
|
485
|
-
|
|
486
|
-
@fixed.setter
|
|
487
|
-
def fixed(self, fixed):
|
|
488
|
-
if len(self.frames.actual_keys()) > 0:
|
|
489
|
-
self.frames[self.current_frame].fixed = fixed
|
|
490
|
-
|
|
491
|
-
@property
|
|
492
|
-
def object_visible(self):
|
|
493
|
-
if len(self.frames.actual_keys()) > 0:
|
|
494
|
-
return self.frames[self.current_frame].object_visible
|
|
495
|
-
else:
|
|
496
|
-
return False
|
|
497
|
-
|
|
498
|
-
@object_visible.setter
|
|
499
|
-
def object_visible(self, object_visible):
|
|
500
|
-
if len(self.frames.actual_keys()) > 0:
|
|
501
|
-
self.frames[self.current_frame].object_visible = object_visible
|
|
502
|
-
|
|
503
|
-
@property
|
|
504
|
-
def is_video(self):
|
|
505
|
-
if len(self.frames.actual_keys()) == 0:
|
|
506
|
-
return False
|
|
507
|
-
else:
|
|
508
|
-
return True
|
|
509
|
-
|
|
510
|
-
##################
|
|
511
|
-
# entity methods #
|
|
512
|
-
##################
|
|
513
|
-
def update_status(self, status: AnnotationStatus = AnnotationStatus.ISSUE):
|
|
514
|
-
"""
|
|
515
|
-
Set status on annotation
|
|
516
|
-
|
|
517
|
-
**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*.
|
|
518
|
-
|
|
519
|
-
:param str status: can be AnnotationStatus.ISSUE, AnnotationStatus.APPROVED, AnnotationStatus.REVIEW, AnnotationStatus.CLEAR
|
|
520
|
-
:return: Annotation object
|
|
521
|
-
:rtype: dtlpy.entities.annotation.Annotation
|
|
522
|
-
|
|
523
|
-
**Example**:
|
|
524
|
-
|
|
525
|
-
.. code-block:: python
|
|
526
|
-
|
|
527
|
-
annotation = annotation.update_status(status=dl.AnnotationStatus.ISSUE)
|
|
528
|
-
"""
|
|
529
|
-
return self.annotations.update_status(annotation=self, status=status)
|
|
530
|
-
|
|
531
|
-
def delete(self):
|
|
532
|
-
"""
|
|
533
|
-
Remove an annotation from item
|
|
534
|
-
|
|
535
|
-
**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*.
|
|
536
|
-
|
|
537
|
-
:return: True if success
|
|
538
|
-
:rtype: bool
|
|
539
|
-
|
|
540
|
-
**Example**:
|
|
541
|
-
|
|
542
|
-
.. code-block:: python
|
|
543
|
-
|
|
544
|
-
is_deleted = annotation.delete()
|
|
545
|
-
"""
|
|
546
|
-
if self.id is None:
|
|
547
|
-
raise PlatformException(
|
|
548
|
-
'400',
|
|
549
|
-
'Cannot delete annotation because it was not fetched from platform and therefore does not have an id'
|
|
550
|
-
)
|
|
551
|
-
return self.annotations.delete(annotation_id=self.id)
|
|
552
|
-
|
|
553
|
-
def update(self, system_metadata=False):
|
|
554
|
-
"""
|
|
555
|
-
Update an existing annotation in host.
|
|
556
|
-
|
|
557
|
-
**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*.
|
|
558
|
-
|
|
559
|
-
:param system_metadata: True, if you want to change metadata system
|
|
560
|
-
:return: Annotation object
|
|
561
|
-
:rtype: dtlpy.entities.annotation.Annotation
|
|
562
|
-
|
|
563
|
-
**Example**:
|
|
564
|
-
|
|
565
|
-
.. code-block:: python
|
|
566
|
-
|
|
567
|
-
annotation = annotation.update()
|
|
568
|
-
"""
|
|
569
|
-
return self.annotations.update(annotations=self,
|
|
570
|
-
system_metadata=system_metadata)[0]
|
|
571
|
-
|
|
572
|
-
def upload(self):
|
|
573
|
-
"""
|
|
574
|
-
Create a new annotation in host
|
|
575
|
-
|
|
576
|
-
**Prerequisites**: Any user can upload annotations.
|
|
577
|
-
|
|
578
|
-
:return: Annotation entity
|
|
579
|
-
:rtype: dtlpy.entities.annotation.Annotation
|
|
580
|
-
"""
|
|
581
|
-
return self.annotations.upload(annotations=self)[0]
|
|
582
|
-
|
|
583
|
-
def download(self,
|
|
584
|
-
filepath: str,
|
|
585
|
-
annotation_format: ViewAnnotationOptions = ViewAnnotationOptions.JSON,
|
|
586
|
-
height: float = None,
|
|
587
|
-
width: float = None,
|
|
588
|
-
thickness: int = 1,
|
|
589
|
-
with_text: bool = False,
|
|
590
|
-
alpha: float = 1):
|
|
591
|
-
"""
|
|
592
|
-
Save annotation to file
|
|
593
|
-
|
|
594
|
-
**Prerequisites**: Any user can upload annotations.
|
|
595
|
-
|
|
596
|
-
:param str filepath: local path to where annotation will be downloaded to
|
|
597
|
-
:param list annotation_format: options: list(dl.ViewAnnotationOptions)
|
|
598
|
-
:param float height: image height
|
|
599
|
-
:param float width: image width
|
|
600
|
-
:param int thickness: line thickness
|
|
601
|
-
:param bool with_text: get mask with text
|
|
602
|
-
:param float alpha: opacity value [0 1], default 1
|
|
603
|
-
:return: filepath
|
|
604
|
-
:rtype: str
|
|
605
|
-
|
|
606
|
-
**Example**:
|
|
607
|
-
|
|
608
|
-
.. code-block:: python
|
|
609
|
-
|
|
610
|
-
filepath = annotation.download(filepath='filepath', annotation_format=dl.ViewAnnotationOptions.MASK)
|
|
611
|
-
"""
|
|
612
|
-
_, ext = os.path.splitext(filepath)
|
|
613
|
-
if ext == '':
|
|
614
|
-
if annotation_format == ViewAnnotationOptions.JSON:
|
|
615
|
-
ext = '.json'
|
|
616
|
-
else:
|
|
617
|
-
if self.is_video:
|
|
618
|
-
ext = '.mp4'
|
|
619
|
-
else:
|
|
620
|
-
ext = '.png'
|
|
621
|
-
filepath = os.path.join(filepath, self.id + ext)
|
|
622
|
-
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
623
|
-
if annotation_format == ViewAnnotationOptions.JSON:
|
|
624
|
-
with open(filepath, 'w') as f:
|
|
625
|
-
json.dump(self.to_json(), f, indent=2)
|
|
626
|
-
elif annotation_format == entities.ViewAnnotationOptions.VTT:
|
|
627
|
-
_, ext = os.path.splitext(filepath)
|
|
628
|
-
if ext != '.vtt':
|
|
629
|
-
raise PlatformException(error='400', message='file extension must be vtt')
|
|
630
|
-
vtt = webvtt.WebVTT()
|
|
631
|
-
if self.type in ['subtitle']:
|
|
632
|
-
s = str(datetime.timedelta(seconds=self.start_time))
|
|
633
|
-
if len(s.split('.')) == 1:
|
|
634
|
-
s += '.000'
|
|
635
|
-
e = str(datetime.timedelta(seconds=self.end_time))
|
|
636
|
-
if len(e.split('.')) == 1:
|
|
637
|
-
e += '.000'
|
|
638
|
-
caption = webvtt.Caption(
|
|
639
|
-
'{}'.format(s),
|
|
640
|
-
'{}'.format(e),
|
|
641
|
-
'{}'.format(self.coordinates['text'])
|
|
642
|
-
)
|
|
643
|
-
vtt.captions.append(caption)
|
|
644
|
-
vtt.save(filepath)
|
|
645
|
-
else:
|
|
646
|
-
if self.is_video:
|
|
647
|
-
entities.AnnotationCollection(item=self.item, annotations=[self])._video_maker(
|
|
648
|
-
input_filepath=None,
|
|
649
|
-
output_filepath=filepath,
|
|
650
|
-
annotation_format=annotation_format,
|
|
651
|
-
thickness=thickness,
|
|
652
|
-
alpha=alpha,
|
|
653
|
-
with_text=with_text
|
|
654
|
-
)
|
|
655
|
-
else:
|
|
656
|
-
mask = self.show(thickness=thickness,
|
|
657
|
-
alpha=alpha,
|
|
658
|
-
with_text=with_text,
|
|
659
|
-
height=height,
|
|
660
|
-
width=width,
|
|
661
|
-
annotation_format=annotation_format)
|
|
662
|
-
img = Image.fromarray(mask.astype(np.uint8))
|
|
663
|
-
img.save(filepath)
|
|
664
|
-
return filepath
|
|
665
|
-
|
|
666
|
-
def set_frame(self, frame):
|
|
667
|
-
"""
|
|
668
|
-
Set annotation to frame state
|
|
669
|
-
|
|
670
|
-
**Prerequisites**: Any user can upload annotations.
|
|
671
|
-
|
|
672
|
-
:param int frame: frame number
|
|
673
|
-
:return: True if success
|
|
674
|
-
:rtype: bool
|
|
675
|
-
|
|
676
|
-
**Example**:
|
|
677
|
-
|
|
678
|
-
.. code-block:: python
|
|
679
|
-
|
|
680
|
-
success = annotation.set_frame(frame=10)
|
|
681
|
-
"""
|
|
682
|
-
if frame in self.frames:
|
|
683
|
-
self.current_frame = frame
|
|
684
|
-
self.annotation_definition = self.frames[frame].annotation_definition
|
|
685
|
-
return True
|
|
686
|
-
else:
|
|
687
|
-
return False
|
|
688
|
-
|
|
689
|
-
############
|
|
690
|
-
# Plotting #
|
|
691
|
-
############
|
|
692
|
-
def show(self,
|
|
693
|
-
image=None,
|
|
694
|
-
thickness=None,
|
|
695
|
-
with_text=False,
|
|
696
|
-
height=None,
|
|
697
|
-
width=None,
|
|
698
|
-
annotation_format: ViewAnnotationOptions = ViewAnnotationOptions.MASK,
|
|
699
|
-
color=None,
|
|
700
|
-
label_instance_dict=None,
|
|
701
|
-
alpha=1,
|
|
702
|
-
frame_num=None
|
|
703
|
-
):
|
|
704
|
-
"""
|
|
705
|
-
Show annotations
|
|
706
|
-
mark the annotation of the image array and return it
|
|
707
|
-
|
|
708
|
-
**Prerequisites**: Any user can upload annotations.
|
|
709
|
-
|
|
710
|
-
:param image: empty or image to draw on
|
|
711
|
-
:param int thickness: line thickness
|
|
712
|
-
:param bool with_text: add label to annotation
|
|
713
|
-
:param float height: height
|
|
714
|
-
:param float width: width
|
|
715
|
-
:param dl.ViewAnnotationOptions annotation_format: list(dl.ViewAnnotationOptions)
|
|
716
|
-
:param tuple color: optional - color tuple
|
|
717
|
-
:param label_instance_dict: the instance labels
|
|
718
|
-
:param float alpha: opacity value [0 1], default 1
|
|
719
|
-
:param int frame_num: for video annotation, show specific fame
|
|
720
|
-
:return: list or single ndarray of the annotations
|
|
721
|
-
|
|
722
|
-
**Exampls**:
|
|
723
|
-
|
|
724
|
-
.. code-block:: python
|
|
725
|
-
|
|
726
|
-
image = annotation.show(image='ndarray',
|
|
727
|
-
thickness=1,
|
|
728
|
-
annotation_format=dl.VIEW_ANNOTATION_OPTIONS_MASK,
|
|
729
|
-
)
|
|
730
|
-
"""
|
|
731
|
-
try:
|
|
732
|
-
import cv2
|
|
733
|
-
except (ImportError, ModuleNotFoundError):
|
|
734
|
-
logger.error(
|
|
735
|
-
'Import Error! Cant import cv2. Annotations operations will be limited. import manually and fix errors')
|
|
736
|
-
raise
|
|
737
|
-
# sanity check for alpha
|
|
738
|
-
if alpha > 1 or alpha < 0:
|
|
739
|
-
raise ValueError(
|
|
740
|
-
'input variable alpha in annotation.show() should be between 0 and 1. got: {!r}'.format(alpha))
|
|
741
|
-
if height is None:
|
|
742
|
-
if self._item is None or self._item.height is None:
|
|
743
|
-
if image is None:
|
|
744
|
-
raise PlatformException(error='400', message='must provide item width and height')
|
|
745
|
-
else:
|
|
746
|
-
height = image.shape[0]
|
|
747
|
-
else:
|
|
748
|
-
height = self._item.height
|
|
749
|
-
if width is None:
|
|
750
|
-
if self._item is None or self._item.width is None:
|
|
751
|
-
if image is None:
|
|
752
|
-
raise PlatformException(error='400', message='must provide item width and height')
|
|
753
|
-
else:
|
|
754
|
-
width = image.shape[1]
|
|
755
|
-
else:
|
|
756
|
-
width = self._item.width
|
|
757
|
-
|
|
758
|
-
# try getting instance map from dataset, otherwise set to default
|
|
759
|
-
if annotation_format in [entities.ViewAnnotationOptions.INSTANCE, entities.ViewAnnotationOptions.OBJECT_ID] and \
|
|
760
|
-
label_instance_dict is None:
|
|
761
|
-
if self._dataset is not None:
|
|
762
|
-
label_instance_dict = self._dataset.instance_map
|
|
763
|
-
else:
|
|
764
|
-
if self._item is not None and self._item._dataset is not None:
|
|
765
|
-
label_instance_dict = self._item._dataset.instance_map
|
|
766
|
-
if label_instance_dict is None:
|
|
767
|
-
logger.warning("Couldn't set label_instance_dict in annotation.show(). All labels will be mapped to 1")
|
|
768
|
-
label_instance_dict = dict()
|
|
769
|
-
|
|
770
|
-
in_video_range = True
|
|
771
|
-
if frame_num is not None:
|
|
772
|
-
in_video_range = self.set_frame(frame_num)
|
|
773
|
-
|
|
774
|
-
if self.is_video and not in_video_range:
|
|
775
|
-
if image is None:
|
|
776
|
-
image = self._get_default_mask(annotation_format=annotation_format,
|
|
777
|
-
width=width,
|
|
778
|
-
height=height,
|
|
779
|
-
label_instance_dict=label_instance_dict)
|
|
780
|
-
return image
|
|
781
|
-
else:
|
|
782
|
-
image = self._show_single_frame(image=image,
|
|
783
|
-
thickness=thickness,
|
|
784
|
-
alpha=alpha,
|
|
785
|
-
with_text=with_text,
|
|
786
|
-
height=height,
|
|
787
|
-
width=width,
|
|
788
|
-
annotation_format=annotation_format,
|
|
789
|
-
color=color,
|
|
790
|
-
label_instance_dict=label_instance_dict)
|
|
791
|
-
return image
|
|
792
|
-
|
|
793
|
-
def _show_single_frame(self,
|
|
794
|
-
image=None,
|
|
795
|
-
thickness=None,
|
|
796
|
-
with_text=False,
|
|
797
|
-
height=None,
|
|
798
|
-
width=None,
|
|
799
|
-
annotation_format: ViewAnnotationOptions = ViewAnnotationOptions.MASK,
|
|
800
|
-
color=None,
|
|
801
|
-
label_instance_dict=None,
|
|
802
|
-
alpha=None):
|
|
803
|
-
"""
|
|
804
|
-
Show annotations
|
|
805
|
-
mark the annotation of the single frame array and return it
|
|
806
|
-
:param image: empty or image to draw on
|
|
807
|
-
:param thickness: line thickness
|
|
808
|
-
:param with_text: add label to annotation
|
|
809
|
-
:param height: height
|
|
810
|
-
:param width: width
|
|
811
|
-
:param annotation_format: list(dl.ViewAnnotationOptions)
|
|
812
|
-
:param color: optional - color tuple
|
|
813
|
-
:param label_instance_dict: the instance labels
|
|
814
|
-
:param alpha: opacity value [0 1], default 1
|
|
815
|
-
:return: ndarray of the annotations
|
|
816
|
-
"""
|
|
817
|
-
try:
|
|
818
|
-
import cv2
|
|
819
|
-
except (ImportError, ModuleNotFoundError):
|
|
820
|
-
logger.error(
|
|
821
|
-
'Import Error! Cant import cv2. Annotations operations will be limited. import manually and fix errors')
|
|
822
|
-
raise
|
|
823
|
-
# height/width
|
|
824
|
-
if self.annotation_definition.type == 'cube_3d':
|
|
825
|
-
logger.warning('the show for 3d_cube is not supported.')
|
|
826
|
-
return image
|
|
827
|
-
if annotation_format == entities.ViewAnnotationOptions.MASK:
|
|
828
|
-
# create an empty mask
|
|
829
|
-
if image is None:
|
|
830
|
-
image = self._get_default_mask(annotation_format=annotation_format,
|
|
831
|
-
width=width,
|
|
832
|
-
height=height,
|
|
833
|
-
label_instance_dict=label_instance_dict)
|
|
834
|
-
else:
|
|
835
|
-
if len(image.shape) == 2:
|
|
836
|
-
# image is gray
|
|
837
|
-
image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGBA)
|
|
838
|
-
elif image.shape[2] == 3:
|
|
839
|
-
image = cv2.cvtColor(image, cv2.COLOR_RGB2RGBA)
|
|
840
|
-
elif image.shape[2] == 4:
|
|
841
|
-
pass
|
|
842
|
-
else:
|
|
843
|
-
raise PlatformException(
|
|
844
|
-
error='1001',
|
|
845
|
-
message='Unknown image shape. expected depth: gray or RGB or RGBA. got: {}'.format(image.shape))
|
|
846
|
-
elif annotation_format == entities.ViewAnnotationOptions.ANNOTATION_ON_IMAGE:
|
|
847
|
-
if image is None:
|
|
848
|
-
raise PlatformException(error='1001',
|
|
849
|
-
message='Must input image with ANNOTATION_ON_IMAGE view option.')
|
|
850
|
-
elif annotation_format in [entities.ViewAnnotationOptions.INSTANCE, entities.ViewAnnotationOptions.OBJECT_ID]:
|
|
851
|
-
if image is None:
|
|
852
|
-
# create an empty mask
|
|
853
|
-
image = self._get_default_mask(annotation_format=annotation_format,
|
|
854
|
-
width=width,
|
|
855
|
-
height=height,
|
|
856
|
-
label_instance_dict=label_instance_dict)
|
|
857
|
-
else:
|
|
858
|
-
if len(image.shape) != 2:
|
|
859
|
-
raise PlatformException(
|
|
860
|
-
error='1001',
|
|
861
|
-
message='Image shape must be 2d array when trying to draw instance on image')
|
|
862
|
-
# create a dictionary of labels and ids
|
|
863
|
-
else:
|
|
864
|
-
raise PlatformException(error='1001',
|
|
865
|
-
message='unknown annotations format: "{}". known formats: "{}"'.format(
|
|
866
|
-
annotation_format, '", "'.join(list(entities.ViewAnnotationOptions))))
|
|
867
|
-
# color
|
|
868
|
-
if color is None:
|
|
869
|
-
if annotation_format in [entities.ViewAnnotationOptions.MASK,
|
|
870
|
-
entities.ViewAnnotationOptions.INSTANCE,
|
|
871
|
-
entities.ViewAnnotationOptions.OBJECT_ID]:
|
|
872
|
-
color = self._get_color_for_show(annotation_format=annotation_format,
|
|
873
|
-
alpha=alpha,
|
|
874
|
-
label_instance_dict=label_instance_dict)
|
|
875
|
-
else:
|
|
876
|
-
raise PlatformException(error='1001',
|
|
877
|
-
message='unknown annotations format: {}. known formats: "{}"'.format(
|
|
878
|
-
annotation_format, '", "'.join(list(entities.ViewAnnotationOptions))))
|
|
879
|
-
|
|
880
|
-
return self.annotation_definition.show(image=image,
|
|
881
|
-
thickness=thickness,
|
|
882
|
-
with_text=with_text,
|
|
883
|
-
height=height,
|
|
884
|
-
width=width,
|
|
885
|
-
annotation_format=annotation_format,
|
|
886
|
-
color=color,
|
|
887
|
-
alpha=alpha)
|
|
888
|
-
|
|
889
|
-
def _get_color_for_show(self, annotation_format, alpha=1, label_instance_dict=None):
|
|
890
|
-
if annotation_format == entities.ViewAnnotationOptions.MASK:
|
|
891
|
-
color = self.color
|
|
892
|
-
if len(color) == 3:
|
|
893
|
-
color = color + (int(alpha * 255),)
|
|
894
|
-
elif annotation_format == entities.ViewAnnotationOptions.INSTANCE:
|
|
895
|
-
color = label_instance_dict.get(self.label, 1)
|
|
896
|
-
elif annotation_format == entities.ViewAnnotationOptions.OBJECT_ID:
|
|
897
|
-
if self.object_id is None:
|
|
898
|
-
raise ValueError('Try to show object_id but annotation has no value. annotation id: {}'.format(self.id))
|
|
899
|
-
color = int(self.object_id)
|
|
900
|
-
else:
|
|
901
|
-
raise ValueError('cant find color for the annotation format: {}'.format(annotation_format))
|
|
902
|
-
return color
|
|
903
|
-
|
|
904
|
-
def _get_default_mask(self, annotation_format, height, width, label_instance_dict):
|
|
905
|
-
"""
|
|
906
|
-
Create a default mask take from the colors or instance map. tag should '$default'.
|
|
907
|
-
Default value is zeros
|
|
908
|
-
|
|
909
|
-
:param annotation_format: dl.AnnotationOptions to show
|
|
910
|
-
:param height: item height
|
|
911
|
-
:param width: item width
|
|
912
|
-
:return:
|
|
913
|
-
"""
|
|
914
|
-
if annotation_format == entities.ViewAnnotationOptions.MASK:
|
|
915
|
-
try:
|
|
916
|
-
colors = self.dataset._get_ontology().color_map
|
|
917
|
-
except exceptions.BadRequest:
|
|
918
|
-
colors = None
|
|
919
|
-
logger.warning('Cant get dataset for annotation color. using default.')
|
|
920
|
-
if colors is not None and '$default' in colors:
|
|
921
|
-
default_color = colors['$default']
|
|
922
|
-
else:
|
|
923
|
-
default_color = None
|
|
924
|
-
if default_color is None:
|
|
925
|
-
default_color = (0, 0, 0, 0)
|
|
926
|
-
if len(default_color) == 3:
|
|
927
|
-
default_color += (255,)
|
|
928
|
-
if len(default_color) != 4:
|
|
929
|
-
raise ValueError('default color for show() mask should be with len 3 or 4 (RGB/RGBA)')
|
|
930
|
-
image = np.full(shape=(height, width, 4), fill_value=default_color, dtype=np.uint8)
|
|
931
|
-
elif annotation_format in [entities.ViewAnnotationOptions.INSTANCE,
|
|
932
|
-
entities.ViewAnnotationOptions.OBJECT_ID]:
|
|
933
|
-
default_color = 0
|
|
934
|
-
if '$default' in label_instance_dict:
|
|
935
|
-
default_color = label_instance_dict['$default']
|
|
936
|
-
image = np.full(shape=(height, width), fill_value=default_color, dtype=np.uint8)
|
|
937
|
-
else:
|
|
938
|
-
raise ValueError('cant create default mask for annotation_format: {}'.format(annotation_format))
|
|
939
|
-
return image
|
|
940
|
-
|
|
941
|
-
#######
|
|
942
|
-
# I/O #
|
|
943
|
-
#######
|
|
944
|
-
@classmethod
|
|
945
|
-
def new(cls,
|
|
946
|
-
item=None,
|
|
947
|
-
annotation_definition=None,
|
|
948
|
-
object_id=None,
|
|
949
|
-
automated=True,
|
|
950
|
-
metadata=None,
|
|
951
|
-
frame_num=None,
|
|
952
|
-
parent_id=None,
|
|
953
|
-
start_time=None,
|
|
954
|
-
item_height=None,
|
|
955
|
-
item_width=None,
|
|
956
|
-
end_time=None):
|
|
957
|
-
"""
|
|
958
|
-
Create a new annotation object annotations
|
|
959
|
-
|
|
960
|
-
**Prerequisites**: Any user can upload annotations.
|
|
961
|
-
|
|
962
|
-
:param dtlpy.entities.item.Items item: item to annotate
|
|
963
|
-
:param annotation_definition: annotation type object
|
|
964
|
-
:param str object_id: object_id
|
|
965
|
-
:param bool automated: is automated
|
|
966
|
-
:param dict metadata: metadata
|
|
967
|
-
:param int frame_num: optional - first frame number if video annotation
|
|
968
|
-
:param str parent_id: add parent annotation ID
|
|
969
|
-
:param start_time: optional - start time if video annotation
|
|
970
|
-
:param float item_height: annotation item's height
|
|
971
|
-
:param float item_width: annotation item's width
|
|
972
|
-
:param end_time: optional - end time if video annotation
|
|
973
|
-
:return: annotation object
|
|
974
|
-
:rtype: dtlpy.entities.annotation.Annotation
|
|
975
|
-
|
|
976
|
-
**Example**:
|
|
977
|
-
|
|
978
|
-
.. code-block:: python
|
|
979
|
-
|
|
980
|
-
annotation = annotation.new(item='item_entity,
|
|
981
|
-
annotation_definition=dl.Box(top=10,left=10,bottom=100, right=100,label='labelName'))
|
|
982
|
-
)
|
|
983
|
-
"""
|
|
984
|
-
if frame_num is None:
|
|
985
|
-
frame_num = 0
|
|
986
|
-
|
|
987
|
-
if object_id is not None:
|
|
988
|
-
if isinstance(object_id, int):
|
|
989
|
-
object_id = '{}'.format(object_id)
|
|
990
|
-
elif not isinstance(object_id, str) or not object_id.isnumeric():
|
|
991
|
-
raise ValueError('Object id must be an int or a string containing only numbers.')
|
|
992
|
-
|
|
993
|
-
# init annotations
|
|
994
|
-
if metadata is None:
|
|
995
|
-
metadata = dict()
|
|
996
|
-
|
|
997
|
-
# add parent
|
|
998
|
-
if parent_id is not None:
|
|
999
|
-
if 'system' not in metadata:
|
|
1000
|
-
metadata['system'] = dict()
|
|
1001
|
-
metadata['system']['parentId'] = parent_id
|
|
1002
|
-
|
|
1003
|
-
# add note status to metadata
|
|
1004
|
-
if annotation_definition is not None and annotation_definition.type == 'note':
|
|
1005
|
-
if 'system' not in metadata:
|
|
1006
|
-
metadata['system'] = dict()
|
|
1007
|
-
metadata['system']['status'] = annotation_definition.status
|
|
1008
|
-
|
|
1009
|
-
# handle fps
|
|
1010
|
-
fps = None
|
|
1011
|
-
if item is not None:
|
|
1012
|
-
if item.fps is not None:
|
|
1013
|
-
fps = item.fps
|
|
1014
|
-
elif item.mimetype is not None and 'audio' in item.mimetype:
|
|
1015
|
-
fps = 1000
|
|
1016
|
-
|
|
1017
|
-
# get type
|
|
1018
|
-
ann_type = None
|
|
1019
|
-
if annotation_definition is not None:
|
|
1020
|
-
ann_type = annotation_definition.type
|
|
1021
|
-
|
|
1022
|
-
# dataset
|
|
1023
|
-
dataset_url = None
|
|
1024
|
-
dataset_id = None
|
|
1025
|
-
if item is not None:
|
|
1026
|
-
dataset_url = item.dataset_url
|
|
1027
|
-
dataset_id = item.dataset_id
|
|
1028
|
-
|
|
1029
|
-
if start_time is None:
|
|
1030
|
-
if fps is not None and frame_num is not None:
|
|
1031
|
-
start_time = frame_num / fps if fps != 0 else 0
|
|
1032
|
-
else:
|
|
1033
|
-
start_time = 0
|
|
1034
|
-
if end_time is None:
|
|
1035
|
-
end_time = start_time
|
|
1036
|
-
|
|
1037
|
-
if frame_num is None:
|
|
1038
|
-
frame_num = 0
|
|
1039
|
-
|
|
1040
|
-
# frames
|
|
1041
|
-
frames = entities.ReflectDict(
|
|
1042
|
-
value_type=FrameAnnotation,
|
|
1043
|
-
on_access=Annotation.on_access,
|
|
1044
|
-
start=frame_num,
|
|
1045
|
-
end=frame_num
|
|
1046
|
-
)
|
|
1047
|
-
|
|
1048
|
-
res = cls(
|
|
1049
|
-
# platform
|
|
1050
|
-
id=None,
|
|
1051
|
-
url=None,
|
|
1052
|
-
item_url=None,
|
|
1053
|
-
item=item,
|
|
1054
|
-
item_id=None,
|
|
1055
|
-
creator=None,
|
|
1056
|
-
created_at=None,
|
|
1057
|
-
updated_by=None,
|
|
1058
|
-
updated_at=None,
|
|
1059
|
-
object_id=object_id,
|
|
1060
|
-
type=ann_type,
|
|
1061
|
-
dataset_url=dataset_url,
|
|
1062
|
-
dataset_id=dataset_id,
|
|
1063
|
-
item_height=item_height,
|
|
1064
|
-
item_width=item_width,
|
|
1065
|
-
|
|
1066
|
-
# meta
|
|
1067
|
-
metadata=metadata,
|
|
1068
|
-
fps=fps,
|
|
1069
|
-
status=None,
|
|
1070
|
-
automated=automated,
|
|
1071
|
-
|
|
1072
|
-
# snapshots
|
|
1073
|
-
frames=frames,
|
|
1074
|
-
|
|
1075
|
-
# video only attributes
|
|
1076
|
-
end_frame=frame_num,
|
|
1077
|
-
end_time=end_time,
|
|
1078
|
-
start_frame=frame_num,
|
|
1079
|
-
start_time=start_time,
|
|
1080
|
-
|
|
1081
|
-
# temp
|
|
1082
|
-
platform_dict=dict(),
|
|
1083
|
-
source='sdk'
|
|
1084
|
-
)
|
|
1085
|
-
|
|
1086
|
-
if annotation_definition:
|
|
1087
|
-
res.annotation_definition = annotation_definition
|
|
1088
|
-
|
|
1089
|
-
if annotation_definition and annotation_definition.attributes is not None:
|
|
1090
|
-
res.attributes = annotation_definition.attributes
|
|
1091
|
-
|
|
1092
|
-
return res
|
|
1093
|
-
|
|
1094
|
-
def add_frames(self,
|
|
1095
|
-
annotation_definition,
|
|
1096
|
-
frame_num=None,
|
|
1097
|
-
end_frame_num=None,
|
|
1098
|
-
start_time=None,
|
|
1099
|
-
end_time=None,
|
|
1100
|
-
fixed=True,
|
|
1101
|
-
object_visible=True):
|
|
1102
|
-
"""
|
|
1103
|
-
Add a frames state to annotation
|
|
1104
|
-
|
|
1105
|
-
**Prerequisites**: Any user can upload annotations.
|
|
1106
|
-
|
|
1107
|
-
:param annotation_definition: annotation type object - must be same type as annotation
|
|
1108
|
-
:param int frame_num: first frame number
|
|
1109
|
-
:param int end_frame_num: last frame number
|
|
1110
|
-
:param start_time: starting time for video
|
|
1111
|
-
:param end_time: ending time for video
|
|
1112
|
-
:param bool fixed: is fixed
|
|
1113
|
-
:param bool object_visible: does the annotated object is visible
|
|
1114
|
-
:return:
|
|
1115
|
-
|
|
1116
|
-
**Example**:
|
|
1117
|
-
|
|
1118
|
-
.. code-block:: python
|
|
1119
|
-
|
|
1120
|
-
annotation.add_frames(frame_num=10,
|
|
1121
|
-
annotation_definition=dl.Box(top=10,left=10,bottom=100, right=100,label='labelName'))
|
|
1122
|
-
)
|
|
1123
|
-
"""
|
|
1124
|
-
# handle fps
|
|
1125
|
-
if self.fps is None:
|
|
1126
|
-
if self._item is not None:
|
|
1127
|
-
if self._item.fps is not None:
|
|
1128
|
-
self.fps = self._item.fps
|
|
1129
|
-
if self.fps is None:
|
|
1130
|
-
raise PlatformException('400', 'Annotation must have fps in order to perform this action')
|
|
1131
|
-
|
|
1132
|
-
if frame_num is None:
|
|
1133
|
-
frame_num = int(np.round(start_time * self.fps))
|
|
1134
|
-
|
|
1135
|
-
if end_frame_num is None:
|
|
1136
|
-
if end_time is not None:
|
|
1137
|
-
end_frame_num = int(np.round(end_time * self.fps))
|
|
1138
|
-
else:
|
|
1139
|
-
end_frame_num = frame_num
|
|
1140
|
-
|
|
1141
|
-
for frame in range(frame_num, end_frame_num + 1):
|
|
1142
|
-
self.add_frame(annotation_definition=annotation_definition,
|
|
1143
|
-
frame_num=frame,
|
|
1144
|
-
fixed=fixed,
|
|
1145
|
-
object_visible=object_visible)
|
|
1146
|
-
|
|
1147
|
-
def add_frame(self,
|
|
1148
|
-
annotation_definition,
|
|
1149
|
-
frame_num=None,
|
|
1150
|
-
fixed=True,
|
|
1151
|
-
object_visible=True):
|
|
1152
|
-
"""
|
|
1153
|
-
Add a frame state to annotation
|
|
1154
|
-
|
|
1155
|
-
:param annotation_definition: annotation type object - must be same type as annotation
|
|
1156
|
-
:param int frame_num: frame number
|
|
1157
|
-
:param bool fixed: is fixed
|
|
1158
|
-
:param bool object_visible: does the annotated object is visible
|
|
1159
|
-
:return: True if success
|
|
1160
|
-
:rtype: bool
|
|
1161
|
-
|
|
1162
|
-
**Example**:
|
|
1163
|
-
|
|
1164
|
-
.. code-block:: python
|
|
1165
|
-
|
|
1166
|
-
success = annotation.add_frame(frame_num=10,
|
|
1167
|
-
annotation_definition=dl.Box(top=10,left=10,bottom=100, right=100,label='labelName'))
|
|
1168
|
-
)
|
|
1169
|
-
"""
|
|
1170
|
-
# handle fps
|
|
1171
|
-
if self.fps is None:
|
|
1172
|
-
if self._item is not None:
|
|
1173
|
-
if self._item.fps is not None:
|
|
1174
|
-
self.fps = self._item.fps
|
|
1175
|
-
if self.fps is None:
|
|
1176
|
-
raise PlatformException('400', 'Annotation must have fps in order to perform this action')
|
|
1177
|
-
|
|
1178
|
-
# if this is first frame
|
|
1179
|
-
if self.annotation_definition is None:
|
|
1180
|
-
|
|
1181
|
-
if frame_num is None:
|
|
1182
|
-
frame_num = 0
|
|
1183
|
-
|
|
1184
|
-
frame = FrameAnnotation.new(annotation_definition=annotation_definition,
|
|
1185
|
-
frame_num=frame_num,
|
|
1186
|
-
fixed=fixed,
|
|
1187
|
-
object_visible=object_visible,
|
|
1188
|
-
annotation=self)
|
|
1189
|
-
|
|
1190
|
-
self.frames[frame_num] = frame
|
|
1191
|
-
self.set_frame(frame_num)
|
|
1192
|
-
self.current_frame = frame_num
|
|
1193
|
-
self.end_frame = frame_num
|
|
1194
|
-
self.type = annotation_definition.type
|
|
1195
|
-
|
|
1196
|
-
return True
|
|
1197
|
-
|
|
1198
|
-
# check if type matches annotation
|
|
1199
|
-
if not isinstance(annotation_definition, type(self.annotation_definition)):
|
|
1200
|
-
raise PlatformException('400', 'All frames in annotation must have same type')
|
|
1201
|
-
|
|
1202
|
-
# find frame number
|
|
1203
|
-
if frame_num is None:
|
|
1204
|
-
frame_num = self.last_frame + 1
|
|
1205
|
-
elif frame_num < self.start_frame:
|
|
1206
|
-
self.start_frame = frame_num
|
|
1207
|
-
|
|
1208
|
-
# add frame to annotation
|
|
1209
|
-
if not self.is_video:
|
|
1210
|
-
# create first frame from annotation definition
|
|
1211
|
-
frame = FrameAnnotation.new(annotation_definition=self.annotation_definition,
|
|
1212
|
-
frame_num=self.last_frame,
|
|
1213
|
-
fixed=fixed,
|
|
1214
|
-
object_visible=object_visible,
|
|
1215
|
-
annotation=self)
|
|
1216
|
-
|
|
1217
|
-
self.frames[self.start_frame] = frame
|
|
1218
|
-
|
|
1219
|
-
# create new time annotations
|
|
1220
|
-
frame = FrameAnnotation.new(annotation_definition=annotation_definition,
|
|
1221
|
-
frame_num=frame_num,
|
|
1222
|
-
fixed=fixed,
|
|
1223
|
-
object_visible=object_visible,
|
|
1224
|
-
annotation=self)
|
|
1225
|
-
|
|
1226
|
-
self.frames[frame_num] = frame
|
|
1227
|
-
if not self.frames[frame_num].fixed and self.start_frame < frame_num:
|
|
1228
|
-
frame_last = self.frames[frame_num - 1].to_snapshot()
|
|
1229
|
-
frame_current = self.frames[frame_num].to_snapshot()
|
|
1230
|
-
frame_last.pop('frame')
|
|
1231
|
-
frame_current.pop('frame')
|
|
1232
|
-
if frame_last == frame_current:
|
|
1233
|
-
self.frames[frame_num]._interpolation = True
|
|
1234
|
-
self.end_frame = max(self.last_frame, frame_num)
|
|
1235
|
-
|
|
1236
|
-
return True
|
|
1237
|
-
|
|
1238
|
-
@staticmethod
|
|
1239
|
-
def _protected_from_json(_json,
|
|
1240
|
-
item=None,
|
|
1241
|
-
client_api=None,
|
|
1242
|
-
annotations=None,
|
|
1243
|
-
is_video=None,
|
|
1244
|
-
fps=None,
|
|
1245
|
-
item_metadata=None,
|
|
1246
|
-
dataset=None):
|
|
1247
|
-
"""
|
|
1248
|
-
Same as from_json but with try-except to catch if error
|
|
1249
|
-
|
|
1250
|
-
:param _json: platform json
|
|
1251
|
-
:param item: item
|
|
1252
|
-
:param client_api: ApiClient entity
|
|
1253
|
-
:param annotations:
|
|
1254
|
-
:param is_video:
|
|
1255
|
-
:param fps:
|
|
1256
|
-
:param item_metadata:
|
|
1257
|
-
:param dataset
|
|
1258
|
-
:return: annotation object
|
|
1259
|
-
"""
|
|
1260
|
-
try:
|
|
1261
|
-
annotation = Annotation.from_json(_json=_json,
|
|
1262
|
-
item=item,
|
|
1263
|
-
client_api=client_api,
|
|
1264
|
-
annotations=annotations,
|
|
1265
|
-
is_video=is_video,
|
|
1266
|
-
fps=fps,
|
|
1267
|
-
item_metadata=item_metadata,
|
|
1268
|
-
dataset=dataset)
|
|
1269
|
-
status = True
|
|
1270
|
-
except Exception:
|
|
1271
|
-
annotation = traceback.format_exc()
|
|
1272
|
-
status = False
|
|
1273
|
-
return status, annotation
|
|
1274
|
-
|
|
1275
|
-
@classmethod
|
|
1276
|
-
def from_json(cls,
|
|
1277
|
-
_json,
|
|
1278
|
-
item=None,
|
|
1279
|
-
client_api=None,
|
|
1280
|
-
annotations=None,
|
|
1281
|
-
is_video=None,
|
|
1282
|
-
fps=None,
|
|
1283
|
-
item_metadata=None,
|
|
1284
|
-
dataset=None,
|
|
1285
|
-
is_audio=None):
|
|
1286
|
-
"""
|
|
1287
|
-
Create an annotation object from platform json
|
|
1288
|
-
|
|
1289
|
-
:param dict _json: platform json
|
|
1290
|
-
:param dtlpy.entities.item.Item item: item
|
|
1291
|
-
:param client_api: ApiClient entity
|
|
1292
|
-
:param annotations:
|
|
1293
|
-
:param bool is_video: is video
|
|
1294
|
-
:param fps: video fps
|
|
1295
|
-
:param item_metadata: item metadata
|
|
1296
|
-
:param dataset: dataset entity
|
|
1297
|
-
:param bool is_audio: is audio
|
|
1298
|
-
:return: annotation object
|
|
1299
|
-
:rtype: dtlpy.entities.annotation.Annotation
|
|
1300
|
-
"""
|
|
1301
|
-
if item_metadata is None:
|
|
1302
|
-
item_metadata = dict()
|
|
1303
|
-
|
|
1304
|
-
if is_video is None:
|
|
1305
|
-
if item is None:
|
|
1306
|
-
if _json['metadata'].get('system', {}).get('endFrame', 0) > 0:
|
|
1307
|
-
is_video = True
|
|
1308
|
-
else:
|
|
1309
|
-
is_video = False
|
|
1310
|
-
else:
|
|
1311
|
-
# get item type
|
|
1312
|
-
if item.mimetype is not None and 'video' in item.mimetype:
|
|
1313
|
-
is_video = True
|
|
1314
|
-
|
|
1315
|
-
if is_audio is None:
|
|
1316
|
-
if item is None:
|
|
1317
|
-
is_audio = False
|
|
1318
|
-
else:
|
|
1319
|
-
# get item type
|
|
1320
|
-
if item.mimetype is not None and 'audio' in item.mimetype:
|
|
1321
|
-
is_audio = True
|
|
1322
|
-
|
|
1323
|
-
item_url = _json.get('item', item.url if item is not None else None)
|
|
1324
|
-
item_id = _json.get('itemId', item.id if item is not None else None)
|
|
1325
|
-
dataset_url = _json.get('dataset', item.dataset_url if item is not None else None)
|
|
1326
|
-
dataset_id = _json.get('datasetId', item.dataset_id if item is not None else None)
|
|
1327
|
-
|
|
1328
|
-
if item is not None:
|
|
1329
|
-
if item.id != item_id:
|
|
1330
|
-
logger.warning('Annotation has been fetched from a item that is not belong to it')
|
|
1331
|
-
item = None
|
|
1332
|
-
|
|
1333
|
-
if dataset is not None:
|
|
1334
|
-
if dataset.id != dataset_id:
|
|
1335
|
-
logger.warning('Annotation has been fetched from a dataset that is not belong to it')
|
|
1336
|
-
dataset = None
|
|
1337
|
-
|
|
1338
|
-
# get id
|
|
1339
|
-
annotation_id = None
|
|
1340
|
-
if 'id' in _json:
|
|
1341
|
-
annotation_id = _json['id']
|
|
1342
|
-
elif '_id' in _json:
|
|
1343
|
-
annotation_id = _json['_id']
|
|
1344
|
-
|
|
1345
|
-
metadata = _json.get('metadata', dict())
|
|
1346
|
-
|
|
1347
|
-
# get metadata, status, attributes and object id
|
|
1348
|
-
object_id = None
|
|
1349
|
-
status = None
|
|
1350
|
-
if 'system' in metadata and metadata['system'] is not None:
|
|
1351
|
-
object_id = _json['metadata']['system'].get('objectId', object_id)
|
|
1352
|
-
status = _json['metadata']['system'].get('status', status)
|
|
1353
|
-
|
|
1354
|
-
named_attributes = metadata.get('system', dict()).get('attributes', None)
|
|
1355
|
-
recipe_1_attributes = _json.get('attributes', None)
|
|
1356
|
-
|
|
1357
|
-
first_frame_attributes = recipe_1_attributes
|
|
1358
|
-
first_frame_coordinates = list()
|
|
1359
|
-
first_frame_number = 0
|
|
1360
|
-
first_frame_start_time = 0
|
|
1361
|
-
automated = None
|
|
1362
|
-
end_frame = None
|
|
1363
|
-
start_time = 0
|
|
1364
|
-
start_frame = 0
|
|
1365
|
-
end_time = None
|
|
1366
|
-
annotation_definition = None
|
|
1367
|
-
|
|
1368
|
-
############
|
|
1369
|
-
# if video #
|
|
1370
|
-
############
|
|
1371
|
-
if is_video or is_audio:
|
|
1372
|
-
# get fps
|
|
1373
|
-
if item is not None and item.fps is not None:
|
|
1374
|
-
fps = item.fps
|
|
1375
|
-
if fps is None:
|
|
1376
|
-
if is_video:
|
|
1377
|
-
fps = item_metadata.get('fps', 25)
|
|
1378
|
-
else:
|
|
1379
|
-
item_metadata.get('fps', 1000)
|
|
1380
|
-
|
|
1381
|
-
# get video-only attributes
|
|
1382
|
-
end_time = 1.5
|
|
1383
|
-
# get first frame attribute
|
|
1384
|
-
first_frame_attributes = first_frame_attributes
|
|
1385
|
-
# get first frame coordinates
|
|
1386
|
-
first_frame_coordinates = _json.get('coordinates', first_frame_coordinates)
|
|
1387
|
-
if 'system' in metadata:
|
|
1388
|
-
# get first frame number
|
|
1389
|
-
first_frame_number = _json['metadata']['system'].get('frame', first_frame_number)
|
|
1390
|
-
# get first frame start time
|
|
1391
|
-
start_time = _json['metadata']['system'].get('startTime', first_frame_start_time)
|
|
1392
|
-
# get first frame number
|
|
1393
|
-
start_frame = _json['metadata']['system'].get('frame', start_frame)
|
|
1394
|
-
automated = _json['metadata']['system'].get('automated', automated)
|
|
1395
|
-
end_frame = _json['metadata']['system'].get('endFrame', end_frame)
|
|
1396
|
-
end_time = _json['metadata']['system'].get('endTime', end_time)
|
|
1397
|
-
################
|
|
1398
|
-
# if not video #
|
|
1399
|
-
################
|
|
1400
|
-
if not is_video or is_audio:
|
|
1401
|
-
# get coordinates
|
|
1402
|
-
coordinates = _json.get('coordinates', list())
|
|
1403
|
-
# set video only attributes
|
|
1404
|
-
if not is_audio:
|
|
1405
|
-
end_time = 0
|
|
1406
|
-
# get automated
|
|
1407
|
-
if 'system' in metadata and metadata['system'] is not None:
|
|
1408
|
-
automated = metadata['system'].get('automated', automated)
|
|
1409
|
-
# set annotation definition
|
|
1410
|
-
def_dict = {'type': _json['type'],
|
|
1411
|
-
'coordinates': coordinates,
|
|
1412
|
-
'label': _json['label'],
|
|
1413
|
-
'attributes': named_attributes}
|
|
1414
|
-
annotation_definition = FrameAnnotation.json_to_annotation_definition(def_dict)
|
|
1415
|
-
|
|
1416
|
-
frames = entities.ReflectDict(
|
|
1417
|
-
value_type=FrameAnnotation,
|
|
1418
|
-
on_access=Annotation.on_access,
|
|
1419
|
-
start=start_frame,
|
|
1420
|
-
end=end_frame
|
|
1421
|
-
)
|
|
1422
|
-
|
|
1423
|
-
# init annotation
|
|
1424
|
-
annotation = cls(
|
|
1425
|
-
# temp
|
|
1426
|
-
platform_dict=copy.deepcopy(_json),
|
|
1427
|
-
# platform
|
|
1428
|
-
id=annotation_id,
|
|
1429
|
-
url=_json.get('url', None),
|
|
1430
|
-
item_url=item_url,
|
|
1431
|
-
item=item,
|
|
1432
|
-
item_id=item_id,
|
|
1433
|
-
dataset=dataset,
|
|
1434
|
-
dataset_url=dataset_url,
|
|
1435
|
-
dataset_id=dataset_id,
|
|
1436
|
-
creator=_json['creator'],
|
|
1437
|
-
created_at=_json['createdAt'],
|
|
1438
|
-
updated_by=_json['updatedBy'],
|
|
1439
|
-
updated_at=_json['updatedAt'],
|
|
1440
|
-
hash=_json.get('hash', None),
|
|
1441
|
-
object_id=object_id,
|
|
1442
|
-
type=_json['type'],
|
|
1443
|
-
item_width=item_metadata.get('width', None),
|
|
1444
|
-
item_height=item_metadata.get('height', None),
|
|
1445
|
-
# meta
|
|
1446
|
-
metadata=metadata,
|
|
1447
|
-
fps=fps,
|
|
1448
|
-
status=status,
|
|
1449
|
-
# snapshots
|
|
1450
|
-
frames=frames,
|
|
1451
|
-
# video attributes
|
|
1452
|
-
automated=automated,
|
|
1453
|
-
end_frame=end_frame,
|
|
1454
|
-
end_time=end_time,
|
|
1455
|
-
start_frame=start_frame,
|
|
1456
|
-
annotations=annotations,
|
|
1457
|
-
start_time=start_time,
|
|
1458
|
-
label_suggestions=_json.get('labelSuggestions', None),
|
|
1459
|
-
source=_json.get('source', None),
|
|
1460
|
-
recipe_1_attributes=recipe_1_attributes,
|
|
1461
|
-
)
|
|
1462
|
-
annotation.annotation_definition = annotation_definition
|
|
1463
|
-
annotation.__client_api = client_api
|
|
1464
|
-
if annotation_definition:
|
|
1465
|
-
description = _json.get('description', None)
|
|
1466
|
-
if description is not None:
|
|
1467
|
-
annotation.description = description
|
|
1468
|
-
|
|
1469
|
-
#################
|
|
1470
|
-
# if has frames #
|
|
1471
|
-
#################
|
|
1472
|
-
if is_video:
|
|
1473
|
-
# set first frame
|
|
1474
|
-
snapshot = {
|
|
1475
|
-
'attributes': first_frame_attributes,
|
|
1476
|
-
'coordinates': first_frame_coordinates,
|
|
1477
|
-
'fixed': True,
|
|
1478
|
-
'objectVisible': True,
|
|
1479
|
-
'frame': first_frame_number,
|
|
1480
|
-
'label': _json['label'],
|
|
1481
|
-
'type': annotation.type,
|
|
1482
|
-
'namedAttributes': named_attributes
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
# add first frame
|
|
1486
|
-
frame = FrameAnnotation.from_snapshot(
|
|
1487
|
-
_json=snapshot,
|
|
1488
|
-
annotation=annotation,
|
|
1489
|
-
fps=fps
|
|
1490
|
-
)
|
|
1491
|
-
annotation.frames[frame.frame_num] = frame
|
|
1492
|
-
annotation.annotation_definition = frame.annotation_definition
|
|
1493
|
-
|
|
1494
|
-
# put snapshots if there are any
|
|
1495
|
-
for snapshot in _json['metadata']['system'].get('snapshots_', []):
|
|
1496
|
-
frame = FrameAnnotation.from_snapshot(
|
|
1497
|
-
_json=snapshot,
|
|
1498
|
-
annotation=annotation,
|
|
1499
|
-
fps=fps
|
|
1500
|
-
)
|
|
1501
|
-
|
|
1502
|
-
annotation.frames[frame.frame_num] = frame
|
|
1503
|
-
|
|
1504
|
-
annotation.annotation_definition = annotation.frames[min(frames.actual_keys())].annotation_definition
|
|
1505
|
-
if annotation.annotation_definition:
|
|
1506
|
-
description = _json.get('description', None)
|
|
1507
|
-
if description is not None:
|
|
1508
|
-
annotation.description = description
|
|
1509
|
-
|
|
1510
|
-
return annotation
|
|
1511
|
-
|
|
1512
|
-
@staticmethod
|
|
1513
|
-
def on_access(reflect_dict, actual_key: int, requested_key: int, val):
|
|
1514
|
-
val = copy.copy(val)
|
|
1515
|
-
val._interpolation = True
|
|
1516
|
-
val.fixed = False
|
|
1517
|
-
val.frame_num = requested_key
|
|
1518
|
-
reflect_dict[requested_key] = val
|
|
1519
|
-
return val
|
|
1520
|
-
|
|
1521
|
-
def to_json(self):
|
|
1522
|
-
"""
|
|
1523
|
-
Convert annotation object to a platform json representatio
|
|
1524
|
-
|
|
1525
|
-
:return: platform json
|
|
1526
|
-
:rtype: dict
|
|
1527
|
-
"""
|
|
1528
|
-
if len(self.frames.actual_keys()) > 0:
|
|
1529
|
-
self.set_frame(min(self.frames.actual_keys()))
|
|
1530
|
-
_json = attr.asdict(self,
|
|
1531
|
-
filter=attr.filters.include(attr.fields(Annotation).id,
|
|
1532
|
-
attr.fields(Annotation).url,
|
|
1533
|
-
attr.fields(Annotation).metadata,
|
|
1534
|
-
attr.fields(Annotation).creator,
|
|
1535
|
-
attr.fields(Annotation).hash,
|
|
1536
|
-
attr.fields(Annotation).metadata))
|
|
1537
|
-
|
|
1538
|
-
# property attributes
|
|
1539
|
-
item_id = self.item_id
|
|
1540
|
-
if item_id is None and self._item is not None:
|
|
1541
|
-
item_id = self._item.id
|
|
1542
|
-
|
|
1543
|
-
_json['itemId'] = item_id
|
|
1544
|
-
_json['item'] = self.item_url
|
|
1545
|
-
_json['label'] = self.label
|
|
1546
|
-
|
|
1547
|
-
# Need to put back after transition to attributes 2.0
|
|
1548
|
-
# _json['attributes'] = self.attributes
|
|
1549
|
-
|
|
1550
|
-
_json['dataset'] = self.dataset_url
|
|
1551
|
-
|
|
1552
|
-
_json['createdAt'] = self.created_at
|
|
1553
|
-
_json['updatedBy'] = self.updated_by
|
|
1554
|
-
_json['updatedAt'] = self.updated_at
|
|
1555
|
-
_json['source'] = self.source
|
|
1556
|
-
|
|
1557
|
-
if self.description is not None:
|
|
1558
|
-
_json['description'] = self.description
|
|
1559
|
-
|
|
1560
|
-
if self.label_suggestions:
|
|
1561
|
-
_json['labelSuggestions'] = self.label_suggestions
|
|
1562
|
-
|
|
1563
|
-
if self._item is not None and self.dataset_id is None:
|
|
1564
|
-
_json['datasetId'] = self._item.dataset_id
|
|
1565
|
-
else:
|
|
1566
|
-
_json['datasetId'] = self.dataset_id
|
|
1567
|
-
|
|
1568
|
-
_json['type'] = self.type
|
|
1569
|
-
if self.type != 'class':
|
|
1570
|
-
_json['coordinates'] = self.coordinates
|
|
1571
|
-
|
|
1572
|
-
# add system metadata
|
|
1573
|
-
if _json['metadata'].get('system', None) is None:
|
|
1574
|
-
_json['metadata']['system'] = dict()
|
|
1575
|
-
if self.automated is not None:
|
|
1576
|
-
_json['metadata']['system']['automated'] = self.automated
|
|
1577
|
-
if self.object_id is not None:
|
|
1578
|
-
_json['metadata']['system']['objectId'] = self.object_id
|
|
1579
|
-
if self.status is not None:
|
|
1580
|
-
# if status is CLEAR need to set status to None so it will be deleted in backend
|
|
1581
|
-
_json['metadata']['system']['status'] = self.status if self.status != AnnotationStatus.CLEAR else None
|
|
1582
|
-
|
|
1583
|
-
if isinstance(self.annotation_definition, entities.Description):
|
|
1584
|
-
_json['metadata']['system']['system'] = True
|
|
1585
|
-
|
|
1586
|
-
_json['metadata']['system']['attributes'] = self.attributes if self.attributes is not None else dict()
|
|
1587
|
-
_json['attributes'] = self._recipe_1_attributes
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
# add frame info
|
|
1591
|
-
if self.is_video or (self.end_time and self.end_time > 0) or (self.end_frame and self.end_frame > 0):
|
|
1592
|
-
# get all snapshots but the first one
|
|
1593
|
-
snapshots = list()
|
|
1594
|
-
frame_numbers = self.frames.actual_keys() if len(self.frames.actual_keys()) else []
|
|
1595
|
-
for frame_num in sorted(frame_numbers):
|
|
1596
|
-
if frame_num <= self.frames.start:
|
|
1597
|
-
continue
|
|
1598
|
-
if frame_num > self.frames.end:
|
|
1599
|
-
break
|
|
1600
|
-
if not self.frames[frame_num]._interpolation or self.frames[frame_num].fixed:
|
|
1601
|
-
snapshots.append(self.frames[frame_num].to_snapshot())
|
|
1602
|
-
self.frames[frame_num]._interpolation = False
|
|
1603
|
-
# add metadata to json
|
|
1604
|
-
_json['metadata']['system']['frame'] = self.start_frame
|
|
1605
|
-
_json['metadata']['system']['startTime'] = self.start_time
|
|
1606
|
-
_json['metadata']['system']['endTime'] = self.end_time
|
|
1607
|
-
if self.end_frame is not None:
|
|
1608
|
-
_json['metadata']['system']['endFrame'] = self.end_frame
|
|
1609
|
-
|
|
1610
|
-
# add snapshots only if classification
|
|
1611
|
-
if self.type not in ['subtitle']:
|
|
1612
|
-
_json['metadata']['system']['snapshots_'] = snapshots
|
|
1613
|
-
|
|
1614
|
-
return _json
|
|
1615
|
-
|
|
1616
|
-
def task_scores(self, task_id: str, page_offset: int = None, page_size: int = None):
|
|
1617
|
-
"""
|
|
1618
|
-
Get the scores of the annotation in a specific task.
|
|
1619
|
-
:param task_id: The ID of the task.
|
|
1620
|
-
:param page_offset: The page offset.
|
|
1621
|
-
:param page_size: The page size.
|
|
1622
|
-
:return: page of scores
|
|
1623
|
-
"""
|
|
1624
|
-
return self.annotations.task_scores(annotation_id=self.id ,task_id=task_id, page_offset=page_offset, page_size=page_size)
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
@attr.s
|
|
1629
|
-
class FrameAnnotation(entities.BaseEntity):
|
|
1630
|
-
"""
|
|
1631
|
-
FrameAnnotation object
|
|
1632
|
-
"""
|
|
1633
|
-
# parent annotation
|
|
1634
|
-
annotation = attr.ib()
|
|
1635
|
-
|
|
1636
|
-
# annotations
|
|
1637
|
-
annotation_definition = attr.ib()
|
|
1638
|
-
|
|
1639
|
-
# multi
|
|
1640
|
-
frame_num = attr.ib()
|
|
1641
|
-
fixed = attr.ib()
|
|
1642
|
-
object_visible = attr.ib()
|
|
1643
|
-
|
|
1644
|
-
# temp
|
|
1645
|
-
_interpolation = attr.ib(repr=False, default=False)
|
|
1646
|
-
_recipe_1_attributes = attr.ib(repr=False, default=None)
|
|
1647
|
-
|
|
1648
|
-
################################
|
|
1649
|
-
# parent annotation attributes #
|
|
1650
|
-
################################
|
|
1651
|
-
|
|
1652
|
-
@property
|
|
1653
|
-
def status(self):
|
|
1654
|
-
return self.annotation.status
|
|
1655
|
-
|
|
1656
|
-
@property
|
|
1657
|
-
def timestamp(self):
|
|
1658
|
-
if self.annotation.fps is not None and self.frame_num is not None:
|
|
1659
|
-
return self.frame_num / self.annotation.fps if self.annotation.fps != 0 else None
|
|
1660
|
-
|
|
1661
|
-
####################################
|
|
1662
|
-
# annotation definition attributes #
|
|
1663
|
-
####################################
|
|
1664
|
-
@property
|
|
1665
|
-
def type(self):
|
|
1666
|
-
return self.annotation.type
|
|
1667
|
-
|
|
1668
|
-
@property
|
|
1669
|
-
def label(self):
|
|
1670
|
-
return self.annotation_definition.label
|
|
1671
|
-
|
|
1672
|
-
@label.setter
|
|
1673
|
-
def label(self, label):
|
|
1674
|
-
self.annotation_definition.label = label
|
|
1675
|
-
|
|
1676
|
-
@property
|
|
1677
|
-
def attributes(self):
|
|
1678
|
-
return self.annotation_definition.attributes
|
|
1679
|
-
|
|
1680
|
-
@attributes.setter
|
|
1681
|
-
def attributes(self, attributes):
|
|
1682
|
-
self.annotation_definition.attributes = attributes
|
|
1683
|
-
|
|
1684
|
-
@property
|
|
1685
|
-
def geo(self):
|
|
1686
|
-
return self.annotation_definition.geo
|
|
1687
|
-
|
|
1688
|
-
@property
|
|
1689
|
-
def top(self):
|
|
1690
|
-
return self.annotation_definition.top
|
|
1691
|
-
|
|
1692
|
-
@property
|
|
1693
|
-
def bottom(self):
|
|
1694
|
-
return self.annotation_definition.bottom
|
|
1695
|
-
|
|
1696
|
-
@property
|
|
1697
|
-
def left(self):
|
|
1698
|
-
return self.annotation_definition.left
|
|
1699
|
-
|
|
1700
|
-
@property
|
|
1701
|
-
def right(self):
|
|
1702
|
-
return self.annotation_definition.right
|
|
1703
|
-
|
|
1704
|
-
@property
|
|
1705
|
-
def color(self):
|
|
1706
|
-
if self.annotation.item is None:
|
|
1707
|
-
return 255, 255, 255
|
|
1708
|
-
else:
|
|
1709
|
-
label = None
|
|
1710
|
-
for label in self.annotation.item.dataset.labels:
|
|
1711
|
-
if label.tag.lower() == self.label.lower():
|
|
1712
|
-
return label.rgb
|
|
1713
|
-
if label is None:
|
|
1714
|
-
return 255, 255, 255
|
|
1715
|
-
|
|
1716
|
-
@property
|
|
1717
|
-
def coordinates(self):
|
|
1718
|
-
coordinates = self.annotation_definition.to_coordinates(color=self.color)
|
|
1719
|
-
return coordinates
|
|
1720
|
-
|
|
1721
|
-
@property
|
|
1722
|
-
def x(self):
|
|
1723
|
-
return self.annotation_definition.x
|
|
1724
|
-
|
|
1725
|
-
@property
|
|
1726
|
-
def y(self):
|
|
1727
|
-
return self.annotation_definition.y
|
|
1728
|
-
|
|
1729
|
-
@property
|
|
1730
|
-
def rx(self):
|
|
1731
|
-
if self.annotation_definition.type == 'ellipse':
|
|
1732
|
-
return self.annotation_definition.rx
|
|
1733
|
-
else:
|
|
1734
|
-
return None
|
|
1735
|
-
|
|
1736
|
-
@property
|
|
1737
|
-
def ry(self):
|
|
1738
|
-
if self.annotation_definition.type == 'ellipse':
|
|
1739
|
-
return self.annotation_definition.ry
|
|
1740
|
-
else:
|
|
1741
|
-
return None
|
|
1742
|
-
|
|
1743
|
-
@property
|
|
1744
|
-
def angle(self):
|
|
1745
|
-
if self.annotation_definition.type in ['ellipse', 'box']:
|
|
1746
|
-
return self.annotation_definition.angle
|
|
1747
|
-
else:
|
|
1748
|
-
return None
|
|
1749
|
-
|
|
1750
|
-
######################
|
|
1751
|
-
# annotation methods #
|
|
1752
|
-
######################
|
|
1753
|
-
def show(self, **kwargs):
|
|
1754
|
-
"""
|
|
1755
|
-
Show annotation as ndarray
|
|
1756
|
-
:param kwargs: see annotation definition
|
|
1757
|
-
:return: ndarray of the annotation
|
|
1758
|
-
"""
|
|
1759
|
-
return self.annotation_definition.show(**kwargs)
|
|
1760
|
-
|
|
1761
|
-
@staticmethod
|
|
1762
|
-
def json_to_annotation_definition(_json):
|
|
1763
|
-
if 'namedAttributes' in _json:
|
|
1764
|
-
_json['attributes'] = _json['namedAttributes']
|
|
1765
|
-
else:
|
|
1766
|
-
if not isinstance(_json.get('attributes'), dict):
|
|
1767
|
-
_json['attributes'] = None
|
|
1768
|
-
if _json['type'] == 'segment':
|
|
1769
|
-
annotation = entities.Polygon.from_json(_json)
|
|
1770
|
-
elif _json['type'] == 'polyline':
|
|
1771
|
-
annotation = entities.Polyline.from_json(_json)
|
|
1772
|
-
elif _json['type'] == 'box':
|
|
1773
|
-
annotation = entities.Box.from_json(_json)
|
|
1774
|
-
elif _json['type'] == 'cube':
|
|
1775
|
-
annotation = entities.Cube.from_json(_json)
|
|
1776
|
-
elif _json['type'] == 'cube_3d':
|
|
1777
|
-
annotation = entities.Cube3d.from_json(_json)
|
|
1778
|
-
elif _json['type'] == 'point':
|
|
1779
|
-
annotation = entities.Point.from_json(_json)
|
|
1780
|
-
elif _json['type'] == 'binary':
|
|
1781
|
-
annotation = entities.Segmentation.from_json(_json)
|
|
1782
|
-
elif _json['type'] == 'class':
|
|
1783
|
-
annotation = entities.Classification.from_json(_json)
|
|
1784
|
-
elif _json['type'] == 'subtitle':
|
|
1785
|
-
annotation = entities.Subtitle.from_json(_json)
|
|
1786
|
-
elif _json['type'] == 'ellipse':
|
|
1787
|
-
annotation = entities.Ellipse.from_json(_json)
|
|
1788
|
-
elif _json['type'] == 'comparison':
|
|
1789
|
-
annotation = entities.Comparison.from_json(_json)
|
|
1790
|
-
elif _json['type'] == 'note':
|
|
1791
|
-
annotation = entities.Note.from_json(_json)
|
|
1792
|
-
elif _json['type'] == 'pose':
|
|
1793
|
-
annotation = entities.Pose.from_json(_json)
|
|
1794
|
-
elif _json['type'] == 'gis':
|
|
1795
|
-
annotation = entities.Gis.from_json(_json)
|
|
1796
|
-
else:
|
|
1797
|
-
annotation = entities.UndefinedAnnotationType.from_json(_json)
|
|
1798
|
-
return annotation
|
|
1799
|
-
|
|
1800
|
-
#######
|
|
1801
|
-
# I/O #
|
|
1802
|
-
#######
|
|
1803
|
-
@classmethod
|
|
1804
|
-
def new(cls, annotation, annotation_definition, frame_num, fixed, object_visible=True):
|
|
1805
|
-
"""
|
|
1806
|
-
new frame state to annotation
|
|
1807
|
-
|
|
1808
|
-
:param annotation: annotation
|
|
1809
|
-
:param annotation_definition: annotation type object - must be same type as annotation
|
|
1810
|
-
:param frame_num: frame number
|
|
1811
|
-
:param fixed: is fixed
|
|
1812
|
-
:param object_visible: does the annotated object is visible
|
|
1813
|
-
:return: FrameAnnotation object
|
|
1814
|
-
"""
|
|
1815
|
-
frame = cls(
|
|
1816
|
-
# annotations
|
|
1817
|
-
annotation=annotation,
|
|
1818
|
-
annotation_definition=annotation_definition,
|
|
1819
|
-
|
|
1820
|
-
# multi
|
|
1821
|
-
frame_num=frame_num,
|
|
1822
|
-
fixed=fixed,
|
|
1823
|
-
object_visible=object_visible,
|
|
1824
|
-
)
|
|
1825
|
-
if annotation_definition.attributes:
|
|
1826
|
-
frame.attributes = annotation_definition.attributes
|
|
1827
|
-
return frame
|
|
1828
|
-
|
|
1829
|
-
@classmethod
|
|
1830
|
-
def from_snapshot(cls, annotation, _json, fps):
|
|
1831
|
-
"""
|
|
1832
|
-
new frame state to annotation
|
|
1833
|
-
|
|
1834
|
-
:param annotation: annotation
|
|
1835
|
-
:param _json: annotation type object - must be same type as annotation
|
|
1836
|
-
:param fps: frame number
|
|
1837
|
-
:return: FrameAnnotation object
|
|
1838
|
-
"""
|
|
1839
|
-
# get annotation class
|
|
1840
|
-
_json['type'] = annotation.type
|
|
1841
|
-
attrs = _json.get('attributes', None)
|
|
1842
|
-
annotation_definition = cls.json_to_annotation_definition(_json=_json)
|
|
1843
|
-
|
|
1844
|
-
frame_num = _json.get('frame', annotation.last_frame + 1)
|
|
1845
|
-
|
|
1846
|
-
return cls(
|
|
1847
|
-
# annotations
|
|
1848
|
-
annotation=annotation,
|
|
1849
|
-
annotation_definition=annotation_definition,
|
|
1850
|
-
|
|
1851
|
-
# multi
|
|
1852
|
-
frame_num=frame_num,
|
|
1853
|
-
fixed=_json.get('fixed', False),
|
|
1854
|
-
object_visible=_json.get('objectVisible', True),
|
|
1855
|
-
recipe_1_attributes=attrs,
|
|
1856
|
-
)
|
|
1857
|
-
|
|
1858
|
-
def to_snapshot(self):
|
|
1859
|
-
|
|
1860
|
-
snapshot_dict = {
|
|
1861
|
-
'frame': self.frame_num,
|
|
1862
|
-
'fixed': self.fixed,
|
|
1863
|
-
'label': self.label,
|
|
1864
|
-
'type': self.type,
|
|
1865
|
-
'objectVisible': self.object_visible,
|
|
1866
|
-
'data': self.coordinates
|
|
1867
|
-
}
|
|
1868
|
-
|
|
1869
|
-
if self.annotation_definition.description is not None:
|
|
1870
|
-
snapshot_dict['description'] = self.annotation_definition.description
|
|
1871
|
-
|
|
1872
|
-
if self.attributes is not None:
|
|
1873
|
-
snapshot_dict['namedAttributes'] = self.attributes
|
|
1874
|
-
|
|
1875
|
-
if self._recipe_1_attributes is not None:
|
|
1876
|
-
snapshot_dict['attributes'] = self._recipe_1_attributes
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
return snapshot_dict
|
|
1
|
+
import numpy as np
|
|
2
|
+
import traceback
|
|
3
|
+
import logging
|
|
4
|
+
import datetime
|
|
5
|
+
import webvtt
|
|
6
|
+
import copy
|
|
7
|
+
import attr
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import warnings
|
|
11
|
+
|
|
12
|
+
from PIL import Image
|
|
13
|
+
from enum import Enum
|
|
14
|
+
|
|
15
|
+
from .. import entities, PlatformException, repositories, ApiClient, exceptions
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(name='dtlpy')
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ExportVersion(str, Enum):
|
|
21
|
+
V1 = "V1"
|
|
22
|
+
V2 = "V2"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AnnotationStatus(str, Enum):
|
|
26
|
+
ISSUE = "issue"
|
|
27
|
+
APPROVED = "approved"
|
|
28
|
+
REVIEW = "review"
|
|
29
|
+
CLEAR = "clear"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AnnotationType(str, Enum):
|
|
33
|
+
BOX = "box"
|
|
34
|
+
CUBE = "cube"
|
|
35
|
+
CUBE3D = "cube_3d"
|
|
36
|
+
CLASSIFICATION = "class"
|
|
37
|
+
COMPARISON = "comparison"
|
|
38
|
+
ELLIPSE = "ellipse"
|
|
39
|
+
NOTE = "note"
|
|
40
|
+
POINT = "point"
|
|
41
|
+
POLYGON = "segment"
|
|
42
|
+
POLYLINE = "polyline"
|
|
43
|
+
POSE = "pose"
|
|
44
|
+
SEGMENTATION = "binary"
|
|
45
|
+
SUBTITLE = "subtitle"
|
|
46
|
+
TEXT = "text_mark"
|
|
47
|
+
GIS = "gis"
|
|
48
|
+
SEMANTIC_3D = "ref_semantic_3d"
|
|
49
|
+
POLYLINE_3D = "polyline_3d"
|
|
50
|
+
FREE_TEXT = "text"
|
|
51
|
+
REF_IMAGE = "ref_image"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ViewAnnotationOptions(str, Enum):
|
|
55
|
+
""" The Annotations file types to download (JSON, MASK, INSTANCE, ANNOTATION_ON_IMAGE, VTT, OBJECT_ID).
|
|
56
|
+
|
|
57
|
+
.. list-table::
|
|
58
|
+
:widths: 15 150
|
|
59
|
+
:header-rows: 1
|
|
60
|
+
|
|
61
|
+
* - State
|
|
62
|
+
- Description
|
|
63
|
+
* - JSON
|
|
64
|
+
- Dataloop json format
|
|
65
|
+
* - MASK
|
|
66
|
+
- PNG file that contains drawing annotations on it
|
|
67
|
+
* - INSTANCE
|
|
68
|
+
- An image file that contains 2D annotations
|
|
69
|
+
* - ANNOTATION_ON_IMAGE
|
|
70
|
+
- The source image with the annotations drawing in it
|
|
71
|
+
* - VTT
|
|
72
|
+
- An text file contains supplementary information about a web video
|
|
73
|
+
* - OBJECT_ID
|
|
74
|
+
- An image file that contains 2D annotations
|
|
75
|
+
|
|
76
|
+
"""
|
|
77
|
+
JSON = "json"
|
|
78
|
+
MASK = "mask"
|
|
79
|
+
INSTANCE = "instance"
|
|
80
|
+
ANNOTATION_ON_IMAGE = "img_mask"
|
|
81
|
+
VTT = "vtt"
|
|
82
|
+
OBJECT_ID = "object_id"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@attr.s
|
|
86
|
+
class Annotation(entities.BaseEntity):
|
|
87
|
+
"""
|
|
88
|
+
Annotations object
|
|
89
|
+
"""
|
|
90
|
+
# platform
|
|
91
|
+
id = attr.ib()
|
|
92
|
+
url = attr.ib(repr=False)
|
|
93
|
+
item_url = attr.ib(repr=False)
|
|
94
|
+
_item = attr.ib(repr=False)
|
|
95
|
+
item_id = attr.ib()
|
|
96
|
+
creator = attr.ib()
|
|
97
|
+
created_at = attr.ib()
|
|
98
|
+
updated_by = attr.ib(repr=False)
|
|
99
|
+
updated_at = attr.ib(repr=False)
|
|
100
|
+
type = attr.ib()
|
|
101
|
+
source = attr.ib(repr=False)
|
|
102
|
+
dataset_url = attr.ib(repr=False)
|
|
103
|
+
|
|
104
|
+
# api
|
|
105
|
+
_platform_dict = attr.ib(repr=False)
|
|
106
|
+
# meta
|
|
107
|
+
metadata = attr.ib(repr=False)
|
|
108
|
+
fps = attr.ib(repr=False)
|
|
109
|
+
hash = attr.ib(default=None, repr=False)
|
|
110
|
+
dataset_id = attr.ib(default=None, repr=False)
|
|
111
|
+
status = attr.ib(default=None, repr=False)
|
|
112
|
+
object_id = attr.ib(default=None, repr=False)
|
|
113
|
+
automated = attr.ib(default=None, repr=False)
|
|
114
|
+
item_height = attr.ib(default=None)
|
|
115
|
+
item_width = attr.ib(default=None)
|
|
116
|
+
label_suggestions = attr.ib(default=None)
|
|
117
|
+
|
|
118
|
+
# annotation definition
|
|
119
|
+
_annotation_definition = attr.ib(default=None, repr=False, type=entities.BaseAnnotationDefinition)
|
|
120
|
+
|
|
121
|
+
# snapshots
|
|
122
|
+
frames = attr.ib(default=None, repr=False)
|
|
123
|
+
current_frame = attr.ib(default=0, repr=False)
|
|
124
|
+
|
|
125
|
+
# video attributes
|
|
126
|
+
_end_frame = attr.ib(default=0, repr=False)
|
|
127
|
+
_end_time = attr.ib(default=0, repr=False)
|
|
128
|
+
_start_frame = attr.ib(default=0)
|
|
129
|
+
_start_time = attr.ib(default=0)
|
|
130
|
+
|
|
131
|
+
# sdk
|
|
132
|
+
_dataset = attr.ib(repr=False, default=None)
|
|
133
|
+
_datasets = attr.ib(repr=False, default=None)
|
|
134
|
+
_annotations = attr.ib(repr=False, default=None)
|
|
135
|
+
__client_api = attr.ib(default=None, repr=False)
|
|
136
|
+
_items = attr.ib(repr=False, default=None)
|
|
137
|
+
_recipe_1_attributes = attr.ib(repr=False, default=None)
|
|
138
|
+
|
|
139
|
+
############
|
|
140
|
+
# Platform #
|
|
141
|
+
############
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def annotation_definition(self):
|
|
145
|
+
return self._annotation_definition
|
|
146
|
+
|
|
147
|
+
@annotation_definition.setter
|
|
148
|
+
def annotation_definition(self, ann_def):
|
|
149
|
+
if ann_def is not None:
|
|
150
|
+
ann_def._annotation = self
|
|
151
|
+
self._annotation_definition = ann_def
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def createdAt(self):
|
|
155
|
+
return self.created_at
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def updatedAt(self):
|
|
159
|
+
return self.updated_at
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def updatedBy(self):
|
|
163
|
+
return self.updated_by
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def _client_api(self) -> ApiClient:
|
|
167
|
+
if self.__client_api is None:
|
|
168
|
+
if self._item is None:
|
|
169
|
+
raise PlatformException('400',
|
|
170
|
+
'This action cannot be performed without an item entity. Please set item')
|
|
171
|
+
else:
|
|
172
|
+
self.__client_api = self._item._client_api
|
|
173
|
+
assert isinstance(self.__client_api, ApiClient)
|
|
174
|
+
return self.__client_api
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def dataset(self):
|
|
178
|
+
if self._dataset is None:
|
|
179
|
+
if self._item is not None:
|
|
180
|
+
# get from item
|
|
181
|
+
self._dataset = self._item.dataset
|
|
182
|
+
else:
|
|
183
|
+
# get directly
|
|
184
|
+
self._dataset = self.datasets.get(dataset_id=self.dataset_id)
|
|
185
|
+
assert isinstance(self._dataset, entities.Dataset)
|
|
186
|
+
return self._dataset
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def item(self):
|
|
190
|
+
if self._item is None:
|
|
191
|
+
self._item = self.items.get(item_id=self.item_id)
|
|
192
|
+
assert isinstance(self._item, entities.Item)
|
|
193
|
+
return self._item
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def annotations(self):
|
|
197
|
+
if self._annotations is None:
|
|
198
|
+
self._annotations = repositories.Annotations(client_api=self._client_api, item=self._item)
|
|
199
|
+
assert isinstance(self._annotations, repositories.Annotations)
|
|
200
|
+
return self._annotations
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def datasets(self):
|
|
204
|
+
if self._datasets is None:
|
|
205
|
+
self._datasets = repositories.Datasets(client_api=self._client_api)
|
|
206
|
+
assert isinstance(self._datasets, repositories.Datasets)
|
|
207
|
+
return self._datasets
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def items(self):
|
|
211
|
+
if self._items is None:
|
|
212
|
+
if self._datasets is not None:
|
|
213
|
+
self._items = self._dataset.items
|
|
214
|
+
elif self._item is not None:
|
|
215
|
+
self._items = self._item.items
|
|
216
|
+
else:
|
|
217
|
+
self._items = repositories.Items(client_api=self._client_api, dataset=self._dataset)
|
|
218
|
+
assert isinstance(self._items, repositories.Items)
|
|
219
|
+
return self._items
|
|
220
|
+
|
|
221
|
+
#########################
|
|
222
|
+
# Annotation Properties #
|
|
223
|
+
#########################
|
|
224
|
+
@property
|
|
225
|
+
def parent_id(self):
|
|
226
|
+
try:
|
|
227
|
+
parent_id = self.metadata['system']['parentId']
|
|
228
|
+
except KeyError:
|
|
229
|
+
parent_id = None
|
|
230
|
+
return parent_id
|
|
231
|
+
|
|
232
|
+
@parent_id.setter
|
|
233
|
+
def parent_id(self, parent_id):
|
|
234
|
+
if 'system' not in self.metadata:
|
|
235
|
+
self.metadata['system'] = dict()
|
|
236
|
+
self.metadata['system']['parentId'] = parent_id
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def coordinates(self):
|
|
240
|
+
color = None
|
|
241
|
+
if self.type in ['binary']:
|
|
242
|
+
color = self.color
|
|
243
|
+
coordinates = self.annotation_definition.to_coordinates(color=color)
|
|
244
|
+
return coordinates
|
|
245
|
+
|
|
246
|
+
@property
|
|
247
|
+
def start_frame(self):
|
|
248
|
+
return self._start_frame
|
|
249
|
+
|
|
250
|
+
@start_frame.setter
|
|
251
|
+
def start_frame(self, val):
|
|
252
|
+
if not isinstance(val, float) and not isinstance(val, int):
|
|
253
|
+
raise ValueError('Must input a valid number')
|
|
254
|
+
self._start_frame = round(val)
|
|
255
|
+
self._start_time = val / self.fps if self.fps else 0
|
|
256
|
+
self.frames.start = self._start_frame
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def end_frame(self):
|
|
260
|
+
return self._end_frame
|
|
261
|
+
|
|
262
|
+
@end_frame.setter
|
|
263
|
+
def end_frame(self, val):
|
|
264
|
+
if not isinstance(val, float) and not isinstance(val, int):
|
|
265
|
+
raise ValueError('Must input a valid number')
|
|
266
|
+
self._end_frame = round(val)
|
|
267
|
+
self._end_time = val / self.fps if self.fps else 0
|
|
268
|
+
self.frames.end = self._end_frame
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def start_time(self):
|
|
272
|
+
return self._start_time
|
|
273
|
+
|
|
274
|
+
@start_time.setter
|
|
275
|
+
def start_time(self, val):
|
|
276
|
+
if not isinstance(val, float) and not isinstance(val, int):
|
|
277
|
+
raise ValueError('Must input a valid number')
|
|
278
|
+
self._start_frame = round(val * self.fps if self.fps else 0)
|
|
279
|
+
self._start_time = val
|
|
280
|
+
self.frames.start = self._start_frame
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def end_time(self):
|
|
284
|
+
return self._end_time
|
|
285
|
+
|
|
286
|
+
@end_time.setter
|
|
287
|
+
def end_time(self, val):
|
|
288
|
+
if not isinstance(val, float) and not isinstance(val, int):
|
|
289
|
+
raise ValueError('Must input a valid number')
|
|
290
|
+
self._end_frame = round(val * self.fps if self.fps else 0)
|
|
291
|
+
self._end_time = val
|
|
292
|
+
self.frames.end = self._end_frame
|
|
293
|
+
|
|
294
|
+
@property
|
|
295
|
+
def x(self):
|
|
296
|
+
return self.annotation_definition.x
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def y(self):
|
|
300
|
+
return self.annotation_definition.y
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def rx(self):
|
|
304
|
+
if self.annotation_definition.type == 'ellipse':
|
|
305
|
+
return self.annotation_definition.rx
|
|
306
|
+
else:
|
|
307
|
+
return None
|
|
308
|
+
|
|
309
|
+
@property
|
|
310
|
+
def ry(self):
|
|
311
|
+
if self.annotation_definition.type == 'ellipse':
|
|
312
|
+
return self.annotation_definition.ry
|
|
313
|
+
else:
|
|
314
|
+
return None
|
|
315
|
+
|
|
316
|
+
@property
|
|
317
|
+
def angle(self):
|
|
318
|
+
if self.annotation_definition.type in ['ellipse', 'cube', 'box']:
|
|
319
|
+
return self.annotation_definition.angle
|
|
320
|
+
else:
|
|
321
|
+
return None
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def messages(self):
|
|
325
|
+
if hasattr(self.annotation_definition, 'messages'):
|
|
326
|
+
return self.annotation_definition.messages
|
|
327
|
+
else:
|
|
328
|
+
return None
|
|
329
|
+
|
|
330
|
+
@messages.setter
|
|
331
|
+
def messages(self, messages):
|
|
332
|
+
if self.type == 'note':
|
|
333
|
+
self.annotation_definition.messages = messages
|
|
334
|
+
else:
|
|
335
|
+
raise PlatformException('400', 'Annotation of type {} does not have attribute messages'.format(self.type))
|
|
336
|
+
|
|
337
|
+
def add_message(self, body: str = None):
|
|
338
|
+
if self.type == 'note':
|
|
339
|
+
return self.annotation_definition.add_message(body=body)
|
|
340
|
+
else:
|
|
341
|
+
raise PlatformException('400', 'Annotation of type {} does not have method add_message'.format(self.type))
|
|
342
|
+
|
|
343
|
+
@property
|
|
344
|
+
def geo(self):
|
|
345
|
+
return self.annotation_definition.geo
|
|
346
|
+
|
|
347
|
+
@geo.setter
|
|
348
|
+
def geo(self, geo):
|
|
349
|
+
self.annotation_definition.geo = geo
|
|
350
|
+
|
|
351
|
+
@property
|
|
352
|
+
def top(self):
|
|
353
|
+
return self.annotation_definition.top
|
|
354
|
+
|
|
355
|
+
@top.setter
|
|
356
|
+
def top(self, top):
|
|
357
|
+
self.annotation_definition.top = top
|
|
358
|
+
|
|
359
|
+
@property
|
|
360
|
+
def bottom(self):
|
|
361
|
+
return self.annotation_definition.bottom
|
|
362
|
+
|
|
363
|
+
@bottom.setter
|
|
364
|
+
def bottom(self, bottom):
|
|
365
|
+
self.annotation_definition.bottom = bottom
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def left(self):
|
|
369
|
+
return self.annotation_definition.left
|
|
370
|
+
|
|
371
|
+
@left.setter
|
|
372
|
+
def left(self, left):
|
|
373
|
+
self.annotation_definition.left = left
|
|
374
|
+
|
|
375
|
+
@property
|
|
376
|
+
def right(self):
|
|
377
|
+
return self.annotation_definition.right
|
|
378
|
+
|
|
379
|
+
@right.setter
|
|
380
|
+
def right(self, right):
|
|
381
|
+
self.annotation_definition.right = right
|
|
382
|
+
|
|
383
|
+
@property
|
|
384
|
+
def height(self):
|
|
385
|
+
return self.annotation_definition.height
|
|
386
|
+
|
|
387
|
+
@height.setter
|
|
388
|
+
def height(self, height):
|
|
389
|
+
self.annotation_definition.height = height
|
|
390
|
+
|
|
391
|
+
@property
|
|
392
|
+
def width(self):
|
|
393
|
+
return self.annotation_definition.width
|
|
394
|
+
|
|
395
|
+
@width.setter
|
|
396
|
+
def width(self, width):
|
|
397
|
+
self.annotation_definition.width = width
|
|
398
|
+
|
|
399
|
+
@property
|
|
400
|
+
def description(self):
|
|
401
|
+
return self.annotation_definition.description
|
|
402
|
+
|
|
403
|
+
@description.setter
|
|
404
|
+
def description(self, description):
|
|
405
|
+
if description is None:
|
|
406
|
+
description = ""
|
|
407
|
+
if not isinstance(description, str):
|
|
408
|
+
raise ValueError("Description must get string")
|
|
409
|
+
self.annotation_definition.description = description
|
|
410
|
+
|
|
411
|
+
@property
|
|
412
|
+
def last_frame(self):
|
|
413
|
+
if len(self.frames.actual_keys()) == 0:
|
|
414
|
+
return 0
|
|
415
|
+
return max(self.frames.actual_keys())
|
|
416
|
+
|
|
417
|
+
@property
|
|
418
|
+
def label(self):
|
|
419
|
+
return self.annotation_definition.label
|
|
420
|
+
|
|
421
|
+
@label.setter
|
|
422
|
+
def label(self, label):
|
|
423
|
+
self.annotation_definition.label = label
|
|
424
|
+
|
|
425
|
+
@property
|
|
426
|
+
def attributes(self):
|
|
427
|
+
return self.annotation_definition.attributes
|
|
428
|
+
|
|
429
|
+
@attributes.setter
|
|
430
|
+
def attributes(self, attributes):
|
|
431
|
+
self.annotation_definition.attributes = attributes
|
|
432
|
+
|
|
433
|
+
@property
|
|
434
|
+
def color(self):
|
|
435
|
+
# if "dataset" is not in self - this will always get the dataset
|
|
436
|
+
try:
|
|
437
|
+
colors = self.dataset._get_ontology().color_map
|
|
438
|
+
except (exceptions.BadRequest, exceptions.NotFound):
|
|
439
|
+
colors = None
|
|
440
|
+
logger.warning('Cant get dataset for annotation color. using default.')
|
|
441
|
+
if colors is not None and self.label in colors:
|
|
442
|
+
color = colors[self.label]
|
|
443
|
+
else:
|
|
444
|
+
if self.type == 'binary' and self.annotation_definition._color is not None:
|
|
445
|
+
color = self.annotation_definition._color
|
|
446
|
+
else:
|
|
447
|
+
color = (255, 255, 255)
|
|
448
|
+
return color
|
|
449
|
+
|
|
450
|
+
@color.setter
|
|
451
|
+
def color(self, color):
|
|
452
|
+
if self.type == 'binary':
|
|
453
|
+
if not isinstance(color, tuple) or len(color) != 3:
|
|
454
|
+
raise ValueError("Color must get tuple of length 3")
|
|
455
|
+
self.annotation_definition._color = color
|
|
456
|
+
else:
|
|
457
|
+
raise exceptions.BadRequest(
|
|
458
|
+
status_code='400',
|
|
459
|
+
message='Invalid annotation type - Updating color is only available to binary annotation'
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
####################
|
|
463
|
+
# frame attributes #
|
|
464
|
+
####################
|
|
465
|
+
@property
|
|
466
|
+
def frame_num(self):
|
|
467
|
+
if len(self.frames.actual_keys()) > 0:
|
|
468
|
+
return self.current_frame
|
|
469
|
+
else:
|
|
470
|
+
return self.start_frame
|
|
471
|
+
|
|
472
|
+
@frame_num.setter
|
|
473
|
+
def frame_num(self, frame_num):
|
|
474
|
+
if frame_num != self.current_frame:
|
|
475
|
+
self.frames[self.current_frame].frame_num = frame_num
|
|
476
|
+
self.frames[frame_num] = self.frames[self.current_frame]
|
|
477
|
+
self.frames.pop(self.current_frame)
|
|
478
|
+
|
|
479
|
+
@property
|
|
480
|
+
def fixed(self):
|
|
481
|
+
if len(self.frames.actual_keys()) > 0:
|
|
482
|
+
return self.frames[self.current_frame].fixed
|
|
483
|
+
else:
|
|
484
|
+
return False
|
|
485
|
+
|
|
486
|
+
@fixed.setter
|
|
487
|
+
def fixed(self, fixed):
|
|
488
|
+
if len(self.frames.actual_keys()) > 0:
|
|
489
|
+
self.frames[self.current_frame].fixed = fixed
|
|
490
|
+
|
|
491
|
+
@property
|
|
492
|
+
def object_visible(self):
|
|
493
|
+
if len(self.frames.actual_keys()) > 0:
|
|
494
|
+
return self.frames[self.current_frame].object_visible
|
|
495
|
+
else:
|
|
496
|
+
return False
|
|
497
|
+
|
|
498
|
+
@object_visible.setter
|
|
499
|
+
def object_visible(self, object_visible):
|
|
500
|
+
if len(self.frames.actual_keys()) > 0:
|
|
501
|
+
self.frames[self.current_frame].object_visible = object_visible
|
|
502
|
+
|
|
503
|
+
@property
|
|
504
|
+
def is_video(self):
|
|
505
|
+
if len(self.frames.actual_keys()) == 0:
|
|
506
|
+
return False
|
|
507
|
+
else:
|
|
508
|
+
return True
|
|
509
|
+
|
|
510
|
+
##################
|
|
511
|
+
# entity methods #
|
|
512
|
+
##################
|
|
513
|
+
def update_status(self, status: AnnotationStatus = AnnotationStatus.ISSUE):
|
|
514
|
+
"""
|
|
515
|
+
Set status on annotation
|
|
516
|
+
|
|
517
|
+
**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*.
|
|
518
|
+
|
|
519
|
+
:param str status: can be AnnotationStatus.ISSUE, AnnotationStatus.APPROVED, AnnotationStatus.REVIEW, AnnotationStatus.CLEAR
|
|
520
|
+
:return: Annotation object
|
|
521
|
+
:rtype: dtlpy.entities.annotation.Annotation
|
|
522
|
+
|
|
523
|
+
**Example**:
|
|
524
|
+
|
|
525
|
+
.. code-block:: python
|
|
526
|
+
|
|
527
|
+
annotation = annotation.update_status(status=dl.AnnotationStatus.ISSUE)
|
|
528
|
+
"""
|
|
529
|
+
return self.annotations.update_status(annotation=self, status=status)
|
|
530
|
+
|
|
531
|
+
def delete(self):
|
|
532
|
+
"""
|
|
533
|
+
Remove an annotation from item
|
|
534
|
+
|
|
535
|
+
**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*.
|
|
536
|
+
|
|
537
|
+
:return: True if success
|
|
538
|
+
:rtype: bool
|
|
539
|
+
|
|
540
|
+
**Example**:
|
|
541
|
+
|
|
542
|
+
.. code-block:: python
|
|
543
|
+
|
|
544
|
+
is_deleted = annotation.delete()
|
|
545
|
+
"""
|
|
546
|
+
if self.id is None:
|
|
547
|
+
raise PlatformException(
|
|
548
|
+
'400',
|
|
549
|
+
'Cannot delete annotation because it was not fetched from platform and therefore does not have an id'
|
|
550
|
+
)
|
|
551
|
+
return self.annotations.delete(annotation_id=self.id)
|
|
552
|
+
|
|
553
|
+
def update(self, system_metadata=False):
|
|
554
|
+
"""
|
|
555
|
+
Update an existing annotation in host.
|
|
556
|
+
|
|
557
|
+
**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*.
|
|
558
|
+
|
|
559
|
+
:param system_metadata: True, if you want to change metadata system
|
|
560
|
+
:return: Annotation object
|
|
561
|
+
:rtype: dtlpy.entities.annotation.Annotation
|
|
562
|
+
|
|
563
|
+
**Example**:
|
|
564
|
+
|
|
565
|
+
.. code-block:: python
|
|
566
|
+
|
|
567
|
+
annotation = annotation.update()
|
|
568
|
+
"""
|
|
569
|
+
return self.annotations.update(annotations=self,
|
|
570
|
+
system_metadata=system_metadata)[0]
|
|
571
|
+
|
|
572
|
+
def upload(self):
|
|
573
|
+
"""
|
|
574
|
+
Create a new annotation in host
|
|
575
|
+
|
|
576
|
+
**Prerequisites**: Any user can upload annotations.
|
|
577
|
+
|
|
578
|
+
:return: Annotation entity
|
|
579
|
+
:rtype: dtlpy.entities.annotation.Annotation
|
|
580
|
+
"""
|
|
581
|
+
return self.annotations.upload(annotations=self)[0]
|
|
582
|
+
|
|
583
|
+
def download(self,
|
|
584
|
+
filepath: str,
|
|
585
|
+
annotation_format: ViewAnnotationOptions = ViewAnnotationOptions.JSON,
|
|
586
|
+
height: float = None,
|
|
587
|
+
width: float = None,
|
|
588
|
+
thickness: int = 1,
|
|
589
|
+
with_text: bool = False,
|
|
590
|
+
alpha: float = 1):
|
|
591
|
+
"""
|
|
592
|
+
Save annotation to file
|
|
593
|
+
|
|
594
|
+
**Prerequisites**: Any user can upload annotations.
|
|
595
|
+
|
|
596
|
+
:param str filepath: local path to where annotation will be downloaded to
|
|
597
|
+
:param list annotation_format: options: list(dl.ViewAnnotationOptions)
|
|
598
|
+
:param float height: image height
|
|
599
|
+
:param float width: image width
|
|
600
|
+
:param int thickness: line thickness
|
|
601
|
+
:param bool with_text: get mask with text
|
|
602
|
+
:param float alpha: opacity value [0 1], default 1
|
|
603
|
+
:return: filepath
|
|
604
|
+
:rtype: str
|
|
605
|
+
|
|
606
|
+
**Example**:
|
|
607
|
+
|
|
608
|
+
.. code-block:: python
|
|
609
|
+
|
|
610
|
+
filepath = annotation.download(filepath='filepath', annotation_format=dl.ViewAnnotationOptions.MASK)
|
|
611
|
+
"""
|
|
612
|
+
_, ext = os.path.splitext(filepath)
|
|
613
|
+
if ext == '':
|
|
614
|
+
if annotation_format == ViewAnnotationOptions.JSON:
|
|
615
|
+
ext = '.json'
|
|
616
|
+
else:
|
|
617
|
+
if self.is_video:
|
|
618
|
+
ext = '.mp4'
|
|
619
|
+
else:
|
|
620
|
+
ext = '.png'
|
|
621
|
+
filepath = os.path.join(filepath, self.id + ext)
|
|
622
|
+
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
623
|
+
if annotation_format == ViewAnnotationOptions.JSON:
|
|
624
|
+
with open(filepath, 'w') as f:
|
|
625
|
+
json.dump(self.to_json(), f, indent=2)
|
|
626
|
+
elif annotation_format == entities.ViewAnnotationOptions.VTT:
|
|
627
|
+
_, ext = os.path.splitext(filepath)
|
|
628
|
+
if ext != '.vtt':
|
|
629
|
+
raise PlatformException(error='400', message='file extension must be vtt')
|
|
630
|
+
vtt = webvtt.WebVTT()
|
|
631
|
+
if self.type in ['subtitle']:
|
|
632
|
+
s = str(datetime.timedelta(seconds=self.start_time))
|
|
633
|
+
if len(s.split('.')) == 1:
|
|
634
|
+
s += '.000'
|
|
635
|
+
e = str(datetime.timedelta(seconds=self.end_time))
|
|
636
|
+
if len(e.split('.')) == 1:
|
|
637
|
+
e += '.000'
|
|
638
|
+
caption = webvtt.Caption(
|
|
639
|
+
'{}'.format(s),
|
|
640
|
+
'{}'.format(e),
|
|
641
|
+
'{}'.format(self.coordinates['text'])
|
|
642
|
+
)
|
|
643
|
+
vtt.captions.append(caption)
|
|
644
|
+
vtt.save(filepath)
|
|
645
|
+
else:
|
|
646
|
+
if self.is_video:
|
|
647
|
+
entities.AnnotationCollection(item=self.item, annotations=[self])._video_maker(
|
|
648
|
+
input_filepath=None,
|
|
649
|
+
output_filepath=filepath,
|
|
650
|
+
annotation_format=annotation_format,
|
|
651
|
+
thickness=thickness,
|
|
652
|
+
alpha=alpha,
|
|
653
|
+
with_text=with_text
|
|
654
|
+
)
|
|
655
|
+
else:
|
|
656
|
+
mask = self.show(thickness=thickness,
|
|
657
|
+
alpha=alpha,
|
|
658
|
+
with_text=with_text,
|
|
659
|
+
height=height,
|
|
660
|
+
width=width,
|
|
661
|
+
annotation_format=annotation_format)
|
|
662
|
+
img = Image.fromarray(mask.astype(np.uint8))
|
|
663
|
+
img.save(filepath)
|
|
664
|
+
return filepath
|
|
665
|
+
|
|
666
|
+
def set_frame(self, frame):
|
|
667
|
+
"""
|
|
668
|
+
Set annotation to frame state
|
|
669
|
+
|
|
670
|
+
**Prerequisites**: Any user can upload annotations.
|
|
671
|
+
|
|
672
|
+
:param int frame: frame number
|
|
673
|
+
:return: True if success
|
|
674
|
+
:rtype: bool
|
|
675
|
+
|
|
676
|
+
**Example**:
|
|
677
|
+
|
|
678
|
+
.. code-block:: python
|
|
679
|
+
|
|
680
|
+
success = annotation.set_frame(frame=10)
|
|
681
|
+
"""
|
|
682
|
+
if frame in self.frames:
|
|
683
|
+
self.current_frame = frame
|
|
684
|
+
self.annotation_definition = self.frames[frame].annotation_definition
|
|
685
|
+
return True
|
|
686
|
+
else:
|
|
687
|
+
return False
|
|
688
|
+
|
|
689
|
+
############
|
|
690
|
+
# Plotting #
|
|
691
|
+
############
|
|
692
|
+
def show(self,
|
|
693
|
+
image=None,
|
|
694
|
+
thickness=None,
|
|
695
|
+
with_text=False,
|
|
696
|
+
height=None,
|
|
697
|
+
width=None,
|
|
698
|
+
annotation_format: ViewAnnotationOptions = ViewAnnotationOptions.MASK,
|
|
699
|
+
color=None,
|
|
700
|
+
label_instance_dict=None,
|
|
701
|
+
alpha=1,
|
|
702
|
+
frame_num=None
|
|
703
|
+
):
|
|
704
|
+
"""
|
|
705
|
+
Show annotations
|
|
706
|
+
mark the annotation of the image array and return it
|
|
707
|
+
|
|
708
|
+
**Prerequisites**: Any user can upload annotations.
|
|
709
|
+
|
|
710
|
+
:param image: empty or image to draw on
|
|
711
|
+
:param int thickness: line thickness
|
|
712
|
+
:param bool with_text: add label to annotation
|
|
713
|
+
:param float height: height
|
|
714
|
+
:param float width: width
|
|
715
|
+
:param dl.ViewAnnotationOptions annotation_format: list(dl.ViewAnnotationOptions)
|
|
716
|
+
:param tuple color: optional - color tuple
|
|
717
|
+
:param label_instance_dict: the instance labels
|
|
718
|
+
:param float alpha: opacity value [0 1], default 1
|
|
719
|
+
:param int frame_num: for video annotation, show specific fame
|
|
720
|
+
:return: list or single ndarray of the annotations
|
|
721
|
+
|
|
722
|
+
**Exampls**:
|
|
723
|
+
|
|
724
|
+
.. code-block:: python
|
|
725
|
+
|
|
726
|
+
image = annotation.show(image='ndarray',
|
|
727
|
+
thickness=1,
|
|
728
|
+
annotation_format=dl.VIEW_ANNOTATION_OPTIONS_MASK,
|
|
729
|
+
)
|
|
730
|
+
"""
|
|
731
|
+
try:
|
|
732
|
+
import cv2
|
|
733
|
+
except (ImportError, ModuleNotFoundError):
|
|
734
|
+
logger.error(
|
|
735
|
+
'Import Error! Cant import cv2. Annotations operations will be limited. import manually and fix errors')
|
|
736
|
+
raise
|
|
737
|
+
# sanity check for alpha
|
|
738
|
+
if alpha > 1 or alpha < 0:
|
|
739
|
+
raise ValueError(
|
|
740
|
+
'input variable alpha in annotation.show() should be between 0 and 1. got: {!r}'.format(alpha))
|
|
741
|
+
if height is None:
|
|
742
|
+
if self._item is None or self._item.height is None:
|
|
743
|
+
if image is None:
|
|
744
|
+
raise PlatformException(error='400', message='must provide item width and height')
|
|
745
|
+
else:
|
|
746
|
+
height = image.shape[0]
|
|
747
|
+
else:
|
|
748
|
+
height = self._item.height
|
|
749
|
+
if width is None:
|
|
750
|
+
if self._item is None or self._item.width is None:
|
|
751
|
+
if image is None:
|
|
752
|
+
raise PlatformException(error='400', message='must provide item width and height')
|
|
753
|
+
else:
|
|
754
|
+
width = image.shape[1]
|
|
755
|
+
else:
|
|
756
|
+
width = self._item.width
|
|
757
|
+
|
|
758
|
+
# try getting instance map from dataset, otherwise set to default
|
|
759
|
+
if annotation_format in [entities.ViewAnnotationOptions.INSTANCE, entities.ViewAnnotationOptions.OBJECT_ID] and \
|
|
760
|
+
label_instance_dict is None:
|
|
761
|
+
if self._dataset is not None:
|
|
762
|
+
label_instance_dict = self._dataset.instance_map
|
|
763
|
+
else:
|
|
764
|
+
if self._item is not None and self._item._dataset is not None:
|
|
765
|
+
label_instance_dict = self._item._dataset.instance_map
|
|
766
|
+
if label_instance_dict is None:
|
|
767
|
+
logger.warning("Couldn't set label_instance_dict in annotation.show(). All labels will be mapped to 1")
|
|
768
|
+
label_instance_dict = dict()
|
|
769
|
+
|
|
770
|
+
in_video_range = True
|
|
771
|
+
if frame_num is not None:
|
|
772
|
+
in_video_range = self.set_frame(frame_num)
|
|
773
|
+
|
|
774
|
+
if self.is_video and not in_video_range:
|
|
775
|
+
if image is None:
|
|
776
|
+
image = self._get_default_mask(annotation_format=annotation_format,
|
|
777
|
+
width=width,
|
|
778
|
+
height=height,
|
|
779
|
+
label_instance_dict=label_instance_dict)
|
|
780
|
+
return image
|
|
781
|
+
else:
|
|
782
|
+
image = self._show_single_frame(image=image,
|
|
783
|
+
thickness=thickness,
|
|
784
|
+
alpha=alpha,
|
|
785
|
+
with_text=with_text,
|
|
786
|
+
height=height,
|
|
787
|
+
width=width,
|
|
788
|
+
annotation_format=annotation_format,
|
|
789
|
+
color=color,
|
|
790
|
+
label_instance_dict=label_instance_dict)
|
|
791
|
+
return image
|
|
792
|
+
|
|
793
|
+
def _show_single_frame(self,
|
|
794
|
+
image=None,
|
|
795
|
+
thickness=None,
|
|
796
|
+
with_text=False,
|
|
797
|
+
height=None,
|
|
798
|
+
width=None,
|
|
799
|
+
annotation_format: ViewAnnotationOptions = ViewAnnotationOptions.MASK,
|
|
800
|
+
color=None,
|
|
801
|
+
label_instance_dict=None,
|
|
802
|
+
alpha=None):
|
|
803
|
+
"""
|
|
804
|
+
Show annotations
|
|
805
|
+
mark the annotation of the single frame array and return it
|
|
806
|
+
:param image: empty or image to draw on
|
|
807
|
+
:param thickness: line thickness
|
|
808
|
+
:param with_text: add label to annotation
|
|
809
|
+
:param height: height
|
|
810
|
+
:param width: width
|
|
811
|
+
:param annotation_format: list(dl.ViewAnnotationOptions)
|
|
812
|
+
:param color: optional - color tuple
|
|
813
|
+
:param label_instance_dict: the instance labels
|
|
814
|
+
:param alpha: opacity value [0 1], default 1
|
|
815
|
+
:return: ndarray of the annotations
|
|
816
|
+
"""
|
|
817
|
+
try:
|
|
818
|
+
import cv2
|
|
819
|
+
except (ImportError, ModuleNotFoundError):
|
|
820
|
+
logger.error(
|
|
821
|
+
'Import Error! Cant import cv2. Annotations operations will be limited. import manually and fix errors')
|
|
822
|
+
raise
|
|
823
|
+
# height/width
|
|
824
|
+
if self.annotation_definition.type == 'cube_3d':
|
|
825
|
+
logger.warning('the show for 3d_cube is not supported.')
|
|
826
|
+
return image
|
|
827
|
+
if annotation_format == entities.ViewAnnotationOptions.MASK:
|
|
828
|
+
# create an empty mask
|
|
829
|
+
if image is None:
|
|
830
|
+
image = self._get_default_mask(annotation_format=annotation_format,
|
|
831
|
+
width=width,
|
|
832
|
+
height=height,
|
|
833
|
+
label_instance_dict=label_instance_dict)
|
|
834
|
+
else:
|
|
835
|
+
if len(image.shape) == 2:
|
|
836
|
+
# image is gray
|
|
837
|
+
image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGBA)
|
|
838
|
+
elif image.shape[2] == 3:
|
|
839
|
+
image = cv2.cvtColor(image, cv2.COLOR_RGB2RGBA)
|
|
840
|
+
elif image.shape[2] == 4:
|
|
841
|
+
pass
|
|
842
|
+
else:
|
|
843
|
+
raise PlatformException(
|
|
844
|
+
error='1001',
|
|
845
|
+
message='Unknown image shape. expected depth: gray or RGB or RGBA. got: {}'.format(image.shape))
|
|
846
|
+
elif annotation_format == entities.ViewAnnotationOptions.ANNOTATION_ON_IMAGE:
|
|
847
|
+
if image is None:
|
|
848
|
+
raise PlatformException(error='1001',
|
|
849
|
+
message='Must input image with ANNOTATION_ON_IMAGE view option.')
|
|
850
|
+
elif annotation_format in [entities.ViewAnnotationOptions.INSTANCE, entities.ViewAnnotationOptions.OBJECT_ID]:
|
|
851
|
+
if image is None:
|
|
852
|
+
# create an empty mask
|
|
853
|
+
image = self._get_default_mask(annotation_format=annotation_format,
|
|
854
|
+
width=width,
|
|
855
|
+
height=height,
|
|
856
|
+
label_instance_dict=label_instance_dict)
|
|
857
|
+
else:
|
|
858
|
+
if len(image.shape) != 2:
|
|
859
|
+
raise PlatformException(
|
|
860
|
+
error='1001',
|
|
861
|
+
message='Image shape must be 2d array when trying to draw instance on image')
|
|
862
|
+
# create a dictionary of labels and ids
|
|
863
|
+
else:
|
|
864
|
+
raise PlatformException(error='1001',
|
|
865
|
+
message='unknown annotations format: "{}". known formats: "{}"'.format(
|
|
866
|
+
annotation_format, '", "'.join(list(entities.ViewAnnotationOptions))))
|
|
867
|
+
# color
|
|
868
|
+
if color is None:
|
|
869
|
+
if annotation_format in [entities.ViewAnnotationOptions.MASK,
|
|
870
|
+
entities.ViewAnnotationOptions.INSTANCE,
|
|
871
|
+
entities.ViewAnnotationOptions.OBJECT_ID]:
|
|
872
|
+
color = self._get_color_for_show(annotation_format=annotation_format,
|
|
873
|
+
alpha=alpha,
|
|
874
|
+
label_instance_dict=label_instance_dict)
|
|
875
|
+
else:
|
|
876
|
+
raise PlatformException(error='1001',
|
|
877
|
+
message='unknown annotations format: {}. known formats: "{}"'.format(
|
|
878
|
+
annotation_format, '", "'.join(list(entities.ViewAnnotationOptions))))
|
|
879
|
+
|
|
880
|
+
return self.annotation_definition.show(image=image,
|
|
881
|
+
thickness=thickness,
|
|
882
|
+
with_text=with_text,
|
|
883
|
+
height=height,
|
|
884
|
+
width=width,
|
|
885
|
+
annotation_format=annotation_format,
|
|
886
|
+
color=color,
|
|
887
|
+
alpha=alpha)
|
|
888
|
+
|
|
889
|
+
def _get_color_for_show(self, annotation_format, alpha=1, label_instance_dict=None):
|
|
890
|
+
if annotation_format == entities.ViewAnnotationOptions.MASK:
|
|
891
|
+
color = self.color
|
|
892
|
+
if len(color) == 3:
|
|
893
|
+
color = color + (int(alpha * 255),)
|
|
894
|
+
elif annotation_format == entities.ViewAnnotationOptions.INSTANCE:
|
|
895
|
+
color = label_instance_dict.get(self.label, 1)
|
|
896
|
+
elif annotation_format == entities.ViewAnnotationOptions.OBJECT_ID:
|
|
897
|
+
if self.object_id is None:
|
|
898
|
+
raise ValueError('Try to show object_id but annotation has no value. annotation id: {}'.format(self.id))
|
|
899
|
+
color = int(self.object_id)
|
|
900
|
+
else:
|
|
901
|
+
raise ValueError('cant find color for the annotation format: {}'.format(annotation_format))
|
|
902
|
+
return color
|
|
903
|
+
|
|
904
|
+
def _get_default_mask(self, annotation_format, height, width, label_instance_dict):
|
|
905
|
+
"""
|
|
906
|
+
Create a default mask take from the colors or instance map. tag should '$default'.
|
|
907
|
+
Default value is zeros
|
|
908
|
+
|
|
909
|
+
:param annotation_format: dl.AnnotationOptions to show
|
|
910
|
+
:param height: item height
|
|
911
|
+
:param width: item width
|
|
912
|
+
:return:
|
|
913
|
+
"""
|
|
914
|
+
if annotation_format == entities.ViewAnnotationOptions.MASK:
|
|
915
|
+
try:
|
|
916
|
+
colors = self.dataset._get_ontology().color_map
|
|
917
|
+
except exceptions.BadRequest:
|
|
918
|
+
colors = None
|
|
919
|
+
logger.warning('Cant get dataset for annotation color. using default.')
|
|
920
|
+
if colors is not None and '$default' in colors:
|
|
921
|
+
default_color = colors['$default']
|
|
922
|
+
else:
|
|
923
|
+
default_color = None
|
|
924
|
+
if default_color is None:
|
|
925
|
+
default_color = (0, 0, 0, 0)
|
|
926
|
+
if len(default_color) == 3:
|
|
927
|
+
default_color += (255,)
|
|
928
|
+
if len(default_color) != 4:
|
|
929
|
+
raise ValueError('default color for show() mask should be with len 3 or 4 (RGB/RGBA)')
|
|
930
|
+
image = np.full(shape=(height, width, 4), fill_value=default_color, dtype=np.uint8)
|
|
931
|
+
elif annotation_format in [entities.ViewAnnotationOptions.INSTANCE,
|
|
932
|
+
entities.ViewAnnotationOptions.OBJECT_ID]:
|
|
933
|
+
default_color = 0
|
|
934
|
+
if '$default' in label_instance_dict:
|
|
935
|
+
default_color = label_instance_dict['$default']
|
|
936
|
+
image = np.full(shape=(height, width), fill_value=default_color, dtype=np.uint8)
|
|
937
|
+
else:
|
|
938
|
+
raise ValueError('cant create default mask for annotation_format: {}'.format(annotation_format))
|
|
939
|
+
return image
|
|
940
|
+
|
|
941
|
+
#######
|
|
942
|
+
# I/O #
|
|
943
|
+
#######
|
|
944
|
+
@classmethod
|
|
945
|
+
def new(cls,
|
|
946
|
+
item=None,
|
|
947
|
+
annotation_definition=None,
|
|
948
|
+
object_id=None,
|
|
949
|
+
automated=True,
|
|
950
|
+
metadata=None,
|
|
951
|
+
frame_num=None,
|
|
952
|
+
parent_id=None,
|
|
953
|
+
start_time=None,
|
|
954
|
+
item_height=None,
|
|
955
|
+
item_width=None,
|
|
956
|
+
end_time=None):
|
|
957
|
+
"""
|
|
958
|
+
Create a new annotation object annotations
|
|
959
|
+
|
|
960
|
+
**Prerequisites**: Any user can upload annotations.
|
|
961
|
+
|
|
962
|
+
:param dtlpy.entities.item.Items item: item to annotate
|
|
963
|
+
:param annotation_definition: annotation type object
|
|
964
|
+
:param str object_id: object_id
|
|
965
|
+
:param bool automated: is automated
|
|
966
|
+
:param dict metadata: metadata
|
|
967
|
+
:param int frame_num: optional - first frame number if video annotation
|
|
968
|
+
:param str parent_id: add parent annotation ID
|
|
969
|
+
:param start_time: optional - start time if video annotation
|
|
970
|
+
:param float item_height: annotation item's height
|
|
971
|
+
:param float item_width: annotation item's width
|
|
972
|
+
:param end_time: optional - end time if video annotation
|
|
973
|
+
:return: annotation object
|
|
974
|
+
:rtype: dtlpy.entities.annotation.Annotation
|
|
975
|
+
|
|
976
|
+
**Example**:
|
|
977
|
+
|
|
978
|
+
.. code-block:: python
|
|
979
|
+
|
|
980
|
+
annotation = annotation.new(item='item_entity,
|
|
981
|
+
annotation_definition=dl.Box(top=10,left=10,bottom=100, right=100,label='labelName'))
|
|
982
|
+
)
|
|
983
|
+
"""
|
|
984
|
+
if frame_num is None:
|
|
985
|
+
frame_num = 0
|
|
986
|
+
|
|
987
|
+
if object_id is not None:
|
|
988
|
+
if isinstance(object_id, int):
|
|
989
|
+
object_id = '{}'.format(object_id)
|
|
990
|
+
elif not isinstance(object_id, str) or not object_id.isnumeric():
|
|
991
|
+
raise ValueError('Object id must be an int or a string containing only numbers.')
|
|
992
|
+
|
|
993
|
+
# init annotations
|
|
994
|
+
if metadata is None:
|
|
995
|
+
metadata = dict()
|
|
996
|
+
|
|
997
|
+
# add parent
|
|
998
|
+
if parent_id is not None:
|
|
999
|
+
if 'system' not in metadata:
|
|
1000
|
+
metadata['system'] = dict()
|
|
1001
|
+
metadata['system']['parentId'] = parent_id
|
|
1002
|
+
|
|
1003
|
+
# add note status to metadata
|
|
1004
|
+
if annotation_definition is not None and annotation_definition.type == 'note':
|
|
1005
|
+
if 'system' not in metadata:
|
|
1006
|
+
metadata['system'] = dict()
|
|
1007
|
+
metadata['system']['status'] = annotation_definition.status
|
|
1008
|
+
|
|
1009
|
+
# handle fps
|
|
1010
|
+
fps = None
|
|
1011
|
+
if item is not None:
|
|
1012
|
+
if item.fps is not None:
|
|
1013
|
+
fps = item.fps
|
|
1014
|
+
elif item.mimetype is not None and 'audio' in item.mimetype:
|
|
1015
|
+
fps = 1000
|
|
1016
|
+
|
|
1017
|
+
# get type
|
|
1018
|
+
ann_type = None
|
|
1019
|
+
if annotation_definition is not None:
|
|
1020
|
+
ann_type = annotation_definition.type
|
|
1021
|
+
|
|
1022
|
+
# dataset
|
|
1023
|
+
dataset_url = None
|
|
1024
|
+
dataset_id = None
|
|
1025
|
+
if item is not None:
|
|
1026
|
+
dataset_url = item.dataset_url
|
|
1027
|
+
dataset_id = item.dataset_id
|
|
1028
|
+
|
|
1029
|
+
if start_time is None:
|
|
1030
|
+
if fps is not None and frame_num is not None:
|
|
1031
|
+
start_time = frame_num / fps if fps != 0 else 0
|
|
1032
|
+
else:
|
|
1033
|
+
start_time = 0
|
|
1034
|
+
if end_time is None:
|
|
1035
|
+
end_time = start_time
|
|
1036
|
+
|
|
1037
|
+
if frame_num is None:
|
|
1038
|
+
frame_num = 0
|
|
1039
|
+
|
|
1040
|
+
# frames
|
|
1041
|
+
frames = entities.ReflectDict(
|
|
1042
|
+
value_type=FrameAnnotation,
|
|
1043
|
+
on_access=Annotation.on_access,
|
|
1044
|
+
start=frame_num,
|
|
1045
|
+
end=frame_num
|
|
1046
|
+
)
|
|
1047
|
+
|
|
1048
|
+
res = cls(
|
|
1049
|
+
# platform
|
|
1050
|
+
id=None,
|
|
1051
|
+
url=None,
|
|
1052
|
+
item_url=None,
|
|
1053
|
+
item=item,
|
|
1054
|
+
item_id=None,
|
|
1055
|
+
creator=None,
|
|
1056
|
+
created_at=None,
|
|
1057
|
+
updated_by=None,
|
|
1058
|
+
updated_at=None,
|
|
1059
|
+
object_id=object_id,
|
|
1060
|
+
type=ann_type,
|
|
1061
|
+
dataset_url=dataset_url,
|
|
1062
|
+
dataset_id=dataset_id,
|
|
1063
|
+
item_height=item_height,
|
|
1064
|
+
item_width=item_width,
|
|
1065
|
+
|
|
1066
|
+
# meta
|
|
1067
|
+
metadata=metadata,
|
|
1068
|
+
fps=fps,
|
|
1069
|
+
status=None,
|
|
1070
|
+
automated=automated,
|
|
1071
|
+
|
|
1072
|
+
# snapshots
|
|
1073
|
+
frames=frames,
|
|
1074
|
+
|
|
1075
|
+
# video only attributes
|
|
1076
|
+
end_frame=frame_num,
|
|
1077
|
+
end_time=end_time,
|
|
1078
|
+
start_frame=frame_num,
|
|
1079
|
+
start_time=start_time,
|
|
1080
|
+
|
|
1081
|
+
# temp
|
|
1082
|
+
platform_dict=dict(),
|
|
1083
|
+
source='sdk'
|
|
1084
|
+
)
|
|
1085
|
+
|
|
1086
|
+
if annotation_definition:
|
|
1087
|
+
res.annotation_definition = annotation_definition
|
|
1088
|
+
|
|
1089
|
+
if annotation_definition and annotation_definition.attributes is not None:
|
|
1090
|
+
res.attributes = annotation_definition.attributes
|
|
1091
|
+
|
|
1092
|
+
return res
|
|
1093
|
+
|
|
1094
|
+
def add_frames(self,
|
|
1095
|
+
annotation_definition,
|
|
1096
|
+
frame_num=None,
|
|
1097
|
+
end_frame_num=None,
|
|
1098
|
+
start_time=None,
|
|
1099
|
+
end_time=None,
|
|
1100
|
+
fixed=True,
|
|
1101
|
+
object_visible=True):
|
|
1102
|
+
"""
|
|
1103
|
+
Add a frames state to annotation
|
|
1104
|
+
|
|
1105
|
+
**Prerequisites**: Any user can upload annotations.
|
|
1106
|
+
|
|
1107
|
+
:param annotation_definition: annotation type object - must be same type as annotation
|
|
1108
|
+
:param int frame_num: first frame number
|
|
1109
|
+
:param int end_frame_num: last frame number
|
|
1110
|
+
:param start_time: starting time for video
|
|
1111
|
+
:param end_time: ending time for video
|
|
1112
|
+
:param bool fixed: is fixed
|
|
1113
|
+
:param bool object_visible: does the annotated object is visible
|
|
1114
|
+
:return:
|
|
1115
|
+
|
|
1116
|
+
**Example**:
|
|
1117
|
+
|
|
1118
|
+
.. code-block:: python
|
|
1119
|
+
|
|
1120
|
+
annotation.add_frames(frame_num=10,
|
|
1121
|
+
annotation_definition=dl.Box(top=10,left=10,bottom=100, right=100,label='labelName'))
|
|
1122
|
+
)
|
|
1123
|
+
"""
|
|
1124
|
+
# handle fps
|
|
1125
|
+
if self.fps is None:
|
|
1126
|
+
if self._item is not None:
|
|
1127
|
+
if self._item.fps is not None:
|
|
1128
|
+
self.fps = self._item.fps
|
|
1129
|
+
if self.fps is None:
|
|
1130
|
+
raise PlatformException('400', 'Annotation must have fps in order to perform this action')
|
|
1131
|
+
|
|
1132
|
+
if frame_num is None:
|
|
1133
|
+
frame_num = int(np.round(start_time * self.fps))
|
|
1134
|
+
|
|
1135
|
+
if end_frame_num is None:
|
|
1136
|
+
if end_time is not None:
|
|
1137
|
+
end_frame_num = int(np.round(end_time * self.fps))
|
|
1138
|
+
else:
|
|
1139
|
+
end_frame_num = frame_num
|
|
1140
|
+
|
|
1141
|
+
for frame in range(frame_num, end_frame_num + 1):
|
|
1142
|
+
self.add_frame(annotation_definition=annotation_definition,
|
|
1143
|
+
frame_num=frame,
|
|
1144
|
+
fixed=fixed,
|
|
1145
|
+
object_visible=object_visible)
|
|
1146
|
+
|
|
1147
|
+
def add_frame(self,
|
|
1148
|
+
annotation_definition,
|
|
1149
|
+
frame_num=None,
|
|
1150
|
+
fixed=True,
|
|
1151
|
+
object_visible=True):
|
|
1152
|
+
"""
|
|
1153
|
+
Add a frame state to annotation
|
|
1154
|
+
|
|
1155
|
+
:param annotation_definition: annotation type object - must be same type as annotation
|
|
1156
|
+
:param int frame_num: frame number
|
|
1157
|
+
:param bool fixed: is fixed
|
|
1158
|
+
:param bool object_visible: does the annotated object is visible
|
|
1159
|
+
:return: True if success
|
|
1160
|
+
:rtype: bool
|
|
1161
|
+
|
|
1162
|
+
**Example**:
|
|
1163
|
+
|
|
1164
|
+
.. code-block:: python
|
|
1165
|
+
|
|
1166
|
+
success = annotation.add_frame(frame_num=10,
|
|
1167
|
+
annotation_definition=dl.Box(top=10,left=10,bottom=100, right=100,label='labelName'))
|
|
1168
|
+
)
|
|
1169
|
+
"""
|
|
1170
|
+
# handle fps
|
|
1171
|
+
if self.fps is None:
|
|
1172
|
+
if self._item is not None:
|
|
1173
|
+
if self._item.fps is not None:
|
|
1174
|
+
self.fps = self._item.fps
|
|
1175
|
+
if self.fps is None:
|
|
1176
|
+
raise PlatformException('400', 'Annotation must have fps in order to perform this action')
|
|
1177
|
+
|
|
1178
|
+
# if this is first frame
|
|
1179
|
+
if self.annotation_definition is None:
|
|
1180
|
+
|
|
1181
|
+
if frame_num is None:
|
|
1182
|
+
frame_num = 0
|
|
1183
|
+
|
|
1184
|
+
frame = FrameAnnotation.new(annotation_definition=annotation_definition,
|
|
1185
|
+
frame_num=frame_num,
|
|
1186
|
+
fixed=fixed,
|
|
1187
|
+
object_visible=object_visible,
|
|
1188
|
+
annotation=self)
|
|
1189
|
+
|
|
1190
|
+
self.frames[frame_num] = frame
|
|
1191
|
+
self.set_frame(frame_num)
|
|
1192
|
+
self.current_frame = frame_num
|
|
1193
|
+
self.end_frame = frame_num
|
|
1194
|
+
self.type = annotation_definition.type
|
|
1195
|
+
|
|
1196
|
+
return True
|
|
1197
|
+
|
|
1198
|
+
# check if type matches annotation
|
|
1199
|
+
if not isinstance(annotation_definition, type(self.annotation_definition)):
|
|
1200
|
+
raise PlatformException('400', 'All frames in annotation must have same type')
|
|
1201
|
+
|
|
1202
|
+
# find frame number
|
|
1203
|
+
if frame_num is None:
|
|
1204
|
+
frame_num = self.last_frame + 1
|
|
1205
|
+
elif frame_num < self.start_frame:
|
|
1206
|
+
self.start_frame = frame_num
|
|
1207
|
+
|
|
1208
|
+
# add frame to annotation
|
|
1209
|
+
if not self.is_video:
|
|
1210
|
+
# create first frame from annotation definition
|
|
1211
|
+
frame = FrameAnnotation.new(annotation_definition=self.annotation_definition,
|
|
1212
|
+
frame_num=self.last_frame,
|
|
1213
|
+
fixed=fixed,
|
|
1214
|
+
object_visible=object_visible,
|
|
1215
|
+
annotation=self)
|
|
1216
|
+
|
|
1217
|
+
self.frames[self.start_frame] = frame
|
|
1218
|
+
|
|
1219
|
+
# create new time annotations
|
|
1220
|
+
frame = FrameAnnotation.new(annotation_definition=annotation_definition,
|
|
1221
|
+
frame_num=frame_num,
|
|
1222
|
+
fixed=fixed,
|
|
1223
|
+
object_visible=object_visible,
|
|
1224
|
+
annotation=self)
|
|
1225
|
+
|
|
1226
|
+
self.frames[frame_num] = frame
|
|
1227
|
+
if not self.frames[frame_num].fixed and self.start_frame < frame_num:
|
|
1228
|
+
frame_last = self.frames[frame_num - 1].to_snapshot()
|
|
1229
|
+
frame_current = self.frames[frame_num].to_snapshot()
|
|
1230
|
+
frame_last.pop('frame')
|
|
1231
|
+
frame_current.pop('frame')
|
|
1232
|
+
if frame_last == frame_current:
|
|
1233
|
+
self.frames[frame_num]._interpolation = True
|
|
1234
|
+
self.end_frame = max(self.last_frame, frame_num)
|
|
1235
|
+
|
|
1236
|
+
return True
|
|
1237
|
+
|
|
1238
|
+
@staticmethod
|
|
1239
|
+
def _protected_from_json(_json,
|
|
1240
|
+
item=None,
|
|
1241
|
+
client_api=None,
|
|
1242
|
+
annotations=None,
|
|
1243
|
+
is_video=None,
|
|
1244
|
+
fps=None,
|
|
1245
|
+
item_metadata=None,
|
|
1246
|
+
dataset=None):
|
|
1247
|
+
"""
|
|
1248
|
+
Same as from_json but with try-except to catch if error
|
|
1249
|
+
|
|
1250
|
+
:param _json: platform json
|
|
1251
|
+
:param item: item
|
|
1252
|
+
:param client_api: ApiClient entity
|
|
1253
|
+
:param annotations:
|
|
1254
|
+
:param is_video:
|
|
1255
|
+
:param fps:
|
|
1256
|
+
:param item_metadata:
|
|
1257
|
+
:param dataset
|
|
1258
|
+
:return: annotation object
|
|
1259
|
+
"""
|
|
1260
|
+
try:
|
|
1261
|
+
annotation = Annotation.from_json(_json=_json,
|
|
1262
|
+
item=item,
|
|
1263
|
+
client_api=client_api,
|
|
1264
|
+
annotations=annotations,
|
|
1265
|
+
is_video=is_video,
|
|
1266
|
+
fps=fps,
|
|
1267
|
+
item_metadata=item_metadata,
|
|
1268
|
+
dataset=dataset)
|
|
1269
|
+
status = True
|
|
1270
|
+
except Exception:
|
|
1271
|
+
annotation = traceback.format_exc()
|
|
1272
|
+
status = False
|
|
1273
|
+
return status, annotation
|
|
1274
|
+
|
|
1275
|
+
@classmethod
|
|
1276
|
+
def from_json(cls,
|
|
1277
|
+
_json,
|
|
1278
|
+
item=None,
|
|
1279
|
+
client_api=None,
|
|
1280
|
+
annotations=None,
|
|
1281
|
+
is_video=None,
|
|
1282
|
+
fps=None,
|
|
1283
|
+
item_metadata=None,
|
|
1284
|
+
dataset=None,
|
|
1285
|
+
is_audio=None):
|
|
1286
|
+
"""
|
|
1287
|
+
Create an annotation object from platform json
|
|
1288
|
+
|
|
1289
|
+
:param dict _json: platform json
|
|
1290
|
+
:param dtlpy.entities.item.Item item: item
|
|
1291
|
+
:param client_api: ApiClient entity
|
|
1292
|
+
:param annotations:
|
|
1293
|
+
:param bool is_video: is video
|
|
1294
|
+
:param fps: video fps
|
|
1295
|
+
:param item_metadata: item metadata
|
|
1296
|
+
:param dataset: dataset entity
|
|
1297
|
+
:param bool is_audio: is audio
|
|
1298
|
+
:return: annotation object
|
|
1299
|
+
:rtype: dtlpy.entities.annotation.Annotation
|
|
1300
|
+
"""
|
|
1301
|
+
if item_metadata is None:
|
|
1302
|
+
item_metadata = dict()
|
|
1303
|
+
|
|
1304
|
+
if is_video is None:
|
|
1305
|
+
if item is None:
|
|
1306
|
+
if _json['metadata'].get('system', {}).get('endFrame', 0) > 0:
|
|
1307
|
+
is_video = True
|
|
1308
|
+
else:
|
|
1309
|
+
is_video = False
|
|
1310
|
+
else:
|
|
1311
|
+
# get item type
|
|
1312
|
+
if item.mimetype is not None and 'video' in item.mimetype:
|
|
1313
|
+
is_video = True
|
|
1314
|
+
|
|
1315
|
+
if is_audio is None:
|
|
1316
|
+
if item is None:
|
|
1317
|
+
is_audio = False
|
|
1318
|
+
else:
|
|
1319
|
+
# get item type
|
|
1320
|
+
if item.mimetype is not None and 'audio' in item.mimetype:
|
|
1321
|
+
is_audio = True
|
|
1322
|
+
|
|
1323
|
+
item_url = _json.get('item', item.url if item is not None else None)
|
|
1324
|
+
item_id = _json.get('itemId', item.id if item is not None else None)
|
|
1325
|
+
dataset_url = _json.get('dataset', item.dataset_url if item is not None else None)
|
|
1326
|
+
dataset_id = _json.get('datasetId', item.dataset_id if item is not None else None)
|
|
1327
|
+
|
|
1328
|
+
if item is not None:
|
|
1329
|
+
if item.id != item_id:
|
|
1330
|
+
logger.warning('Annotation has been fetched from a item that is not belong to it')
|
|
1331
|
+
item = None
|
|
1332
|
+
|
|
1333
|
+
if dataset is not None:
|
|
1334
|
+
if dataset.id != dataset_id:
|
|
1335
|
+
logger.warning('Annotation has been fetched from a dataset that is not belong to it')
|
|
1336
|
+
dataset = None
|
|
1337
|
+
|
|
1338
|
+
# get id
|
|
1339
|
+
annotation_id = None
|
|
1340
|
+
if 'id' in _json:
|
|
1341
|
+
annotation_id = _json['id']
|
|
1342
|
+
elif '_id' in _json:
|
|
1343
|
+
annotation_id = _json['_id']
|
|
1344
|
+
|
|
1345
|
+
metadata = _json.get('metadata', dict())
|
|
1346
|
+
|
|
1347
|
+
# get metadata, status, attributes and object id
|
|
1348
|
+
object_id = None
|
|
1349
|
+
status = None
|
|
1350
|
+
if 'system' in metadata and metadata['system'] is not None:
|
|
1351
|
+
object_id = _json['metadata']['system'].get('objectId', object_id)
|
|
1352
|
+
status = _json['metadata']['system'].get('status', status)
|
|
1353
|
+
|
|
1354
|
+
named_attributes = metadata.get('system', dict()).get('attributes', None)
|
|
1355
|
+
recipe_1_attributes = _json.get('attributes', None)
|
|
1356
|
+
|
|
1357
|
+
first_frame_attributes = recipe_1_attributes
|
|
1358
|
+
first_frame_coordinates = list()
|
|
1359
|
+
first_frame_number = 0
|
|
1360
|
+
first_frame_start_time = 0
|
|
1361
|
+
automated = None
|
|
1362
|
+
end_frame = None
|
|
1363
|
+
start_time = 0
|
|
1364
|
+
start_frame = 0
|
|
1365
|
+
end_time = None
|
|
1366
|
+
annotation_definition = None
|
|
1367
|
+
|
|
1368
|
+
############
|
|
1369
|
+
# if video #
|
|
1370
|
+
############
|
|
1371
|
+
if is_video or is_audio:
|
|
1372
|
+
# get fps
|
|
1373
|
+
if item is not None and item.fps is not None:
|
|
1374
|
+
fps = item.fps
|
|
1375
|
+
if fps is None:
|
|
1376
|
+
if is_video:
|
|
1377
|
+
fps = item_metadata.get('fps', 25)
|
|
1378
|
+
else:
|
|
1379
|
+
item_metadata.get('fps', 1000)
|
|
1380
|
+
|
|
1381
|
+
# get video-only attributes
|
|
1382
|
+
end_time = 1.5
|
|
1383
|
+
# get first frame attribute
|
|
1384
|
+
first_frame_attributes = first_frame_attributes
|
|
1385
|
+
# get first frame coordinates
|
|
1386
|
+
first_frame_coordinates = _json.get('coordinates', first_frame_coordinates)
|
|
1387
|
+
if 'system' in metadata:
|
|
1388
|
+
# get first frame number
|
|
1389
|
+
first_frame_number = _json['metadata']['system'].get('frame', first_frame_number)
|
|
1390
|
+
# get first frame start time
|
|
1391
|
+
start_time = _json['metadata']['system'].get('startTime', first_frame_start_time)
|
|
1392
|
+
# get first frame number
|
|
1393
|
+
start_frame = _json['metadata']['system'].get('frame', start_frame)
|
|
1394
|
+
automated = _json['metadata']['system'].get('automated', automated)
|
|
1395
|
+
end_frame = _json['metadata']['system'].get('endFrame', end_frame)
|
|
1396
|
+
end_time = _json['metadata']['system'].get('endTime', end_time)
|
|
1397
|
+
################
|
|
1398
|
+
# if not video #
|
|
1399
|
+
################
|
|
1400
|
+
if not is_video or is_audio:
|
|
1401
|
+
# get coordinates
|
|
1402
|
+
coordinates = _json.get('coordinates', list())
|
|
1403
|
+
# set video only attributes
|
|
1404
|
+
if not is_audio:
|
|
1405
|
+
end_time = 0
|
|
1406
|
+
# get automated
|
|
1407
|
+
if 'system' in metadata and metadata['system'] is not None:
|
|
1408
|
+
automated = metadata['system'].get('automated', automated)
|
|
1409
|
+
# set annotation definition
|
|
1410
|
+
def_dict = {'type': _json['type'],
|
|
1411
|
+
'coordinates': coordinates,
|
|
1412
|
+
'label': _json['label'],
|
|
1413
|
+
'attributes': named_attributes}
|
|
1414
|
+
annotation_definition = FrameAnnotation.json_to_annotation_definition(def_dict)
|
|
1415
|
+
|
|
1416
|
+
frames = entities.ReflectDict(
|
|
1417
|
+
value_type=FrameAnnotation,
|
|
1418
|
+
on_access=Annotation.on_access,
|
|
1419
|
+
start=start_frame,
|
|
1420
|
+
end=end_frame
|
|
1421
|
+
)
|
|
1422
|
+
|
|
1423
|
+
# init annotation
|
|
1424
|
+
annotation = cls(
|
|
1425
|
+
# temp
|
|
1426
|
+
platform_dict=copy.deepcopy(_json),
|
|
1427
|
+
# platform
|
|
1428
|
+
id=annotation_id,
|
|
1429
|
+
url=_json.get('url', None),
|
|
1430
|
+
item_url=item_url,
|
|
1431
|
+
item=item,
|
|
1432
|
+
item_id=item_id,
|
|
1433
|
+
dataset=dataset,
|
|
1434
|
+
dataset_url=dataset_url,
|
|
1435
|
+
dataset_id=dataset_id,
|
|
1436
|
+
creator=_json['creator'],
|
|
1437
|
+
created_at=_json['createdAt'],
|
|
1438
|
+
updated_by=_json['updatedBy'],
|
|
1439
|
+
updated_at=_json['updatedAt'],
|
|
1440
|
+
hash=_json.get('hash', None),
|
|
1441
|
+
object_id=object_id,
|
|
1442
|
+
type=_json['type'],
|
|
1443
|
+
item_width=item_metadata.get('width', None),
|
|
1444
|
+
item_height=item_metadata.get('height', None),
|
|
1445
|
+
# meta
|
|
1446
|
+
metadata=metadata,
|
|
1447
|
+
fps=fps,
|
|
1448
|
+
status=status,
|
|
1449
|
+
# snapshots
|
|
1450
|
+
frames=frames,
|
|
1451
|
+
# video attributes
|
|
1452
|
+
automated=automated,
|
|
1453
|
+
end_frame=end_frame,
|
|
1454
|
+
end_time=end_time,
|
|
1455
|
+
start_frame=start_frame,
|
|
1456
|
+
annotations=annotations,
|
|
1457
|
+
start_time=start_time,
|
|
1458
|
+
label_suggestions=_json.get('labelSuggestions', None),
|
|
1459
|
+
source=_json.get('source', None),
|
|
1460
|
+
recipe_1_attributes=recipe_1_attributes,
|
|
1461
|
+
)
|
|
1462
|
+
annotation.annotation_definition = annotation_definition
|
|
1463
|
+
annotation.__client_api = client_api
|
|
1464
|
+
if annotation_definition:
|
|
1465
|
+
description = _json.get('description', None)
|
|
1466
|
+
if description is not None:
|
|
1467
|
+
annotation.description = description
|
|
1468
|
+
|
|
1469
|
+
#################
|
|
1470
|
+
# if has frames #
|
|
1471
|
+
#################
|
|
1472
|
+
if is_video:
|
|
1473
|
+
# set first frame
|
|
1474
|
+
snapshot = {
|
|
1475
|
+
'attributes': first_frame_attributes,
|
|
1476
|
+
'coordinates': first_frame_coordinates,
|
|
1477
|
+
'fixed': True,
|
|
1478
|
+
'objectVisible': True,
|
|
1479
|
+
'frame': first_frame_number,
|
|
1480
|
+
'label': _json['label'],
|
|
1481
|
+
'type': annotation.type,
|
|
1482
|
+
'namedAttributes': named_attributes
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
# add first frame
|
|
1486
|
+
frame = FrameAnnotation.from_snapshot(
|
|
1487
|
+
_json=snapshot,
|
|
1488
|
+
annotation=annotation,
|
|
1489
|
+
fps=fps
|
|
1490
|
+
)
|
|
1491
|
+
annotation.frames[frame.frame_num] = frame
|
|
1492
|
+
annotation.annotation_definition = frame.annotation_definition
|
|
1493
|
+
|
|
1494
|
+
# put snapshots if there are any
|
|
1495
|
+
for snapshot in _json['metadata']['system'].get('snapshots_', []):
|
|
1496
|
+
frame = FrameAnnotation.from_snapshot(
|
|
1497
|
+
_json=snapshot,
|
|
1498
|
+
annotation=annotation,
|
|
1499
|
+
fps=fps
|
|
1500
|
+
)
|
|
1501
|
+
|
|
1502
|
+
annotation.frames[frame.frame_num] = frame
|
|
1503
|
+
|
|
1504
|
+
annotation.annotation_definition = annotation.frames[min(frames.actual_keys())].annotation_definition
|
|
1505
|
+
if annotation.annotation_definition:
|
|
1506
|
+
description = _json.get('description', None)
|
|
1507
|
+
if description is not None:
|
|
1508
|
+
annotation.description = description
|
|
1509
|
+
|
|
1510
|
+
return annotation
|
|
1511
|
+
|
|
1512
|
+
@staticmethod
|
|
1513
|
+
def on_access(reflect_dict, actual_key: int, requested_key: int, val):
|
|
1514
|
+
val = copy.copy(val)
|
|
1515
|
+
val._interpolation = True
|
|
1516
|
+
val.fixed = False
|
|
1517
|
+
val.frame_num = requested_key
|
|
1518
|
+
reflect_dict[requested_key] = val
|
|
1519
|
+
return val
|
|
1520
|
+
|
|
1521
|
+
def to_json(self):
|
|
1522
|
+
"""
|
|
1523
|
+
Convert annotation object to a platform json representatio
|
|
1524
|
+
|
|
1525
|
+
:return: platform json
|
|
1526
|
+
:rtype: dict
|
|
1527
|
+
"""
|
|
1528
|
+
if len(self.frames.actual_keys()) > 0:
|
|
1529
|
+
self.set_frame(min(self.frames.actual_keys()))
|
|
1530
|
+
_json = attr.asdict(self,
|
|
1531
|
+
filter=attr.filters.include(attr.fields(Annotation).id,
|
|
1532
|
+
attr.fields(Annotation).url,
|
|
1533
|
+
attr.fields(Annotation).metadata,
|
|
1534
|
+
attr.fields(Annotation).creator,
|
|
1535
|
+
attr.fields(Annotation).hash,
|
|
1536
|
+
attr.fields(Annotation).metadata))
|
|
1537
|
+
|
|
1538
|
+
# property attributes
|
|
1539
|
+
item_id = self.item_id
|
|
1540
|
+
if item_id is None and self._item is not None:
|
|
1541
|
+
item_id = self._item.id
|
|
1542
|
+
|
|
1543
|
+
_json['itemId'] = item_id
|
|
1544
|
+
_json['item'] = self.item_url
|
|
1545
|
+
_json['label'] = self.label
|
|
1546
|
+
|
|
1547
|
+
# Need to put back after transition to attributes 2.0
|
|
1548
|
+
# _json['attributes'] = self.attributes
|
|
1549
|
+
|
|
1550
|
+
_json['dataset'] = self.dataset_url
|
|
1551
|
+
|
|
1552
|
+
_json['createdAt'] = self.created_at
|
|
1553
|
+
_json['updatedBy'] = self.updated_by
|
|
1554
|
+
_json['updatedAt'] = self.updated_at
|
|
1555
|
+
_json['source'] = self.source
|
|
1556
|
+
|
|
1557
|
+
if self.description is not None:
|
|
1558
|
+
_json['description'] = self.description
|
|
1559
|
+
|
|
1560
|
+
if self.label_suggestions:
|
|
1561
|
+
_json['labelSuggestions'] = self.label_suggestions
|
|
1562
|
+
|
|
1563
|
+
if self._item is not None and self.dataset_id is None:
|
|
1564
|
+
_json['datasetId'] = self._item.dataset_id
|
|
1565
|
+
else:
|
|
1566
|
+
_json['datasetId'] = self.dataset_id
|
|
1567
|
+
|
|
1568
|
+
_json['type'] = self.type
|
|
1569
|
+
if self.type != 'class':
|
|
1570
|
+
_json['coordinates'] = self.coordinates
|
|
1571
|
+
|
|
1572
|
+
# add system metadata
|
|
1573
|
+
if _json['metadata'].get('system', None) is None:
|
|
1574
|
+
_json['metadata']['system'] = dict()
|
|
1575
|
+
if self.automated is not None:
|
|
1576
|
+
_json['metadata']['system']['automated'] = self.automated
|
|
1577
|
+
if self.object_id is not None:
|
|
1578
|
+
_json['metadata']['system']['objectId'] = self.object_id
|
|
1579
|
+
if self.status is not None:
|
|
1580
|
+
# if status is CLEAR need to set status to None so it will be deleted in backend
|
|
1581
|
+
_json['metadata']['system']['status'] = self.status if self.status != AnnotationStatus.CLEAR else None
|
|
1582
|
+
|
|
1583
|
+
if isinstance(self.annotation_definition, entities.Description):
|
|
1584
|
+
_json['metadata']['system']['system'] = True
|
|
1585
|
+
|
|
1586
|
+
_json['metadata']['system']['attributes'] = self.attributes if self.attributes is not None else dict()
|
|
1587
|
+
_json['attributes'] = self._recipe_1_attributes
|
|
1588
|
+
|
|
1589
|
+
|
|
1590
|
+
# add frame info
|
|
1591
|
+
if self.is_video or (self.end_time and self.end_time > 0) or (self.end_frame and self.end_frame > 0):
|
|
1592
|
+
# get all snapshots but the first one
|
|
1593
|
+
snapshots = list()
|
|
1594
|
+
frame_numbers = self.frames.actual_keys() if len(self.frames.actual_keys()) else []
|
|
1595
|
+
for frame_num in sorted(frame_numbers):
|
|
1596
|
+
if frame_num <= self.frames.start:
|
|
1597
|
+
continue
|
|
1598
|
+
if frame_num > self.frames.end:
|
|
1599
|
+
break
|
|
1600
|
+
if not self.frames[frame_num]._interpolation or self.frames[frame_num].fixed:
|
|
1601
|
+
snapshots.append(self.frames[frame_num].to_snapshot())
|
|
1602
|
+
self.frames[frame_num]._interpolation = False
|
|
1603
|
+
# add metadata to json
|
|
1604
|
+
_json['metadata']['system']['frame'] = self.start_frame
|
|
1605
|
+
_json['metadata']['system']['startTime'] = self.start_time
|
|
1606
|
+
_json['metadata']['system']['endTime'] = self.end_time
|
|
1607
|
+
if self.end_frame is not None:
|
|
1608
|
+
_json['metadata']['system']['endFrame'] = self.end_frame
|
|
1609
|
+
|
|
1610
|
+
# add snapshots only if classification
|
|
1611
|
+
if self.type not in ['subtitle']:
|
|
1612
|
+
_json['metadata']['system']['snapshots_'] = snapshots
|
|
1613
|
+
|
|
1614
|
+
return _json
|
|
1615
|
+
|
|
1616
|
+
def task_scores(self, task_id: str, page_offset: int = None, page_size: int = None):
|
|
1617
|
+
"""
|
|
1618
|
+
Get the scores of the annotation in a specific task.
|
|
1619
|
+
:param task_id: The ID of the task.
|
|
1620
|
+
:param page_offset: The page offset.
|
|
1621
|
+
:param page_size: The page size.
|
|
1622
|
+
:return: page of scores
|
|
1623
|
+
"""
|
|
1624
|
+
return self.annotations.task_scores(annotation_id=self.id ,task_id=task_id, page_offset=page_offset, page_size=page_size)
|
|
1625
|
+
|
|
1626
|
+
|
|
1627
|
+
|
|
1628
|
+
@attr.s
|
|
1629
|
+
class FrameAnnotation(entities.BaseEntity):
|
|
1630
|
+
"""
|
|
1631
|
+
FrameAnnotation object
|
|
1632
|
+
"""
|
|
1633
|
+
# parent annotation
|
|
1634
|
+
annotation = attr.ib()
|
|
1635
|
+
|
|
1636
|
+
# annotations
|
|
1637
|
+
annotation_definition = attr.ib()
|
|
1638
|
+
|
|
1639
|
+
# multi
|
|
1640
|
+
frame_num = attr.ib()
|
|
1641
|
+
fixed = attr.ib()
|
|
1642
|
+
object_visible = attr.ib()
|
|
1643
|
+
|
|
1644
|
+
# temp
|
|
1645
|
+
_interpolation = attr.ib(repr=False, default=False)
|
|
1646
|
+
_recipe_1_attributes = attr.ib(repr=False, default=None)
|
|
1647
|
+
|
|
1648
|
+
################################
|
|
1649
|
+
# parent annotation attributes #
|
|
1650
|
+
################################
|
|
1651
|
+
|
|
1652
|
+
@property
|
|
1653
|
+
def status(self):
|
|
1654
|
+
return self.annotation.status
|
|
1655
|
+
|
|
1656
|
+
@property
|
|
1657
|
+
def timestamp(self):
|
|
1658
|
+
if self.annotation.fps is not None and self.frame_num is not None:
|
|
1659
|
+
return self.frame_num / self.annotation.fps if self.annotation.fps != 0 else None
|
|
1660
|
+
|
|
1661
|
+
####################################
|
|
1662
|
+
# annotation definition attributes #
|
|
1663
|
+
####################################
|
|
1664
|
+
@property
|
|
1665
|
+
def type(self):
|
|
1666
|
+
return self.annotation.type
|
|
1667
|
+
|
|
1668
|
+
@property
|
|
1669
|
+
def label(self):
|
|
1670
|
+
return self.annotation_definition.label
|
|
1671
|
+
|
|
1672
|
+
@label.setter
|
|
1673
|
+
def label(self, label):
|
|
1674
|
+
self.annotation_definition.label = label
|
|
1675
|
+
|
|
1676
|
+
@property
|
|
1677
|
+
def attributes(self):
|
|
1678
|
+
return self.annotation_definition.attributes
|
|
1679
|
+
|
|
1680
|
+
@attributes.setter
|
|
1681
|
+
def attributes(self, attributes):
|
|
1682
|
+
self.annotation_definition.attributes = attributes
|
|
1683
|
+
|
|
1684
|
+
@property
|
|
1685
|
+
def geo(self):
|
|
1686
|
+
return self.annotation_definition.geo
|
|
1687
|
+
|
|
1688
|
+
@property
|
|
1689
|
+
def top(self):
|
|
1690
|
+
return self.annotation_definition.top
|
|
1691
|
+
|
|
1692
|
+
@property
|
|
1693
|
+
def bottom(self):
|
|
1694
|
+
return self.annotation_definition.bottom
|
|
1695
|
+
|
|
1696
|
+
@property
|
|
1697
|
+
def left(self):
|
|
1698
|
+
return self.annotation_definition.left
|
|
1699
|
+
|
|
1700
|
+
@property
|
|
1701
|
+
def right(self):
|
|
1702
|
+
return self.annotation_definition.right
|
|
1703
|
+
|
|
1704
|
+
@property
|
|
1705
|
+
def color(self):
|
|
1706
|
+
if self.annotation.item is None:
|
|
1707
|
+
return 255, 255, 255
|
|
1708
|
+
else:
|
|
1709
|
+
label = None
|
|
1710
|
+
for label in self.annotation.item.dataset.labels:
|
|
1711
|
+
if label.tag.lower() == self.label.lower():
|
|
1712
|
+
return label.rgb
|
|
1713
|
+
if label is None:
|
|
1714
|
+
return 255, 255, 255
|
|
1715
|
+
|
|
1716
|
+
@property
|
|
1717
|
+
def coordinates(self):
|
|
1718
|
+
coordinates = self.annotation_definition.to_coordinates(color=self.color)
|
|
1719
|
+
return coordinates
|
|
1720
|
+
|
|
1721
|
+
@property
|
|
1722
|
+
def x(self):
|
|
1723
|
+
return self.annotation_definition.x
|
|
1724
|
+
|
|
1725
|
+
@property
|
|
1726
|
+
def y(self):
|
|
1727
|
+
return self.annotation_definition.y
|
|
1728
|
+
|
|
1729
|
+
@property
|
|
1730
|
+
def rx(self):
|
|
1731
|
+
if self.annotation_definition.type == 'ellipse':
|
|
1732
|
+
return self.annotation_definition.rx
|
|
1733
|
+
else:
|
|
1734
|
+
return None
|
|
1735
|
+
|
|
1736
|
+
@property
|
|
1737
|
+
def ry(self):
|
|
1738
|
+
if self.annotation_definition.type == 'ellipse':
|
|
1739
|
+
return self.annotation_definition.ry
|
|
1740
|
+
else:
|
|
1741
|
+
return None
|
|
1742
|
+
|
|
1743
|
+
@property
|
|
1744
|
+
def angle(self):
|
|
1745
|
+
if self.annotation_definition.type in ['ellipse', 'box']:
|
|
1746
|
+
return self.annotation_definition.angle
|
|
1747
|
+
else:
|
|
1748
|
+
return None
|
|
1749
|
+
|
|
1750
|
+
######################
|
|
1751
|
+
# annotation methods #
|
|
1752
|
+
######################
|
|
1753
|
+
def show(self, **kwargs):
|
|
1754
|
+
"""
|
|
1755
|
+
Show annotation as ndarray
|
|
1756
|
+
:param kwargs: see annotation definition
|
|
1757
|
+
:return: ndarray of the annotation
|
|
1758
|
+
"""
|
|
1759
|
+
return self.annotation_definition.show(**kwargs)
|
|
1760
|
+
|
|
1761
|
+
@staticmethod
|
|
1762
|
+
def json_to_annotation_definition(_json):
|
|
1763
|
+
if 'namedAttributes' in _json:
|
|
1764
|
+
_json['attributes'] = _json['namedAttributes']
|
|
1765
|
+
else:
|
|
1766
|
+
if not isinstance(_json.get('attributes'), dict):
|
|
1767
|
+
_json['attributes'] = None
|
|
1768
|
+
if _json['type'] == 'segment':
|
|
1769
|
+
annotation = entities.Polygon.from_json(_json)
|
|
1770
|
+
elif _json['type'] == 'polyline':
|
|
1771
|
+
annotation = entities.Polyline.from_json(_json)
|
|
1772
|
+
elif _json['type'] == 'box':
|
|
1773
|
+
annotation = entities.Box.from_json(_json)
|
|
1774
|
+
elif _json['type'] == 'cube':
|
|
1775
|
+
annotation = entities.Cube.from_json(_json)
|
|
1776
|
+
elif _json['type'] == 'cube_3d':
|
|
1777
|
+
annotation = entities.Cube3d.from_json(_json)
|
|
1778
|
+
elif _json['type'] == 'point':
|
|
1779
|
+
annotation = entities.Point.from_json(_json)
|
|
1780
|
+
elif _json['type'] == 'binary':
|
|
1781
|
+
annotation = entities.Segmentation.from_json(_json)
|
|
1782
|
+
elif _json['type'] == 'class':
|
|
1783
|
+
annotation = entities.Classification.from_json(_json)
|
|
1784
|
+
elif _json['type'] == 'subtitle':
|
|
1785
|
+
annotation = entities.Subtitle.from_json(_json)
|
|
1786
|
+
elif _json['type'] == 'ellipse':
|
|
1787
|
+
annotation = entities.Ellipse.from_json(_json)
|
|
1788
|
+
elif _json['type'] == 'comparison':
|
|
1789
|
+
annotation = entities.Comparison.from_json(_json)
|
|
1790
|
+
elif _json['type'] == 'note':
|
|
1791
|
+
annotation = entities.Note.from_json(_json)
|
|
1792
|
+
elif _json['type'] == 'pose':
|
|
1793
|
+
annotation = entities.Pose.from_json(_json)
|
|
1794
|
+
elif _json['type'] == 'gis':
|
|
1795
|
+
annotation = entities.Gis.from_json(_json)
|
|
1796
|
+
else:
|
|
1797
|
+
annotation = entities.UndefinedAnnotationType.from_json(_json)
|
|
1798
|
+
return annotation
|
|
1799
|
+
|
|
1800
|
+
#######
|
|
1801
|
+
# I/O #
|
|
1802
|
+
#######
|
|
1803
|
+
@classmethod
|
|
1804
|
+
def new(cls, annotation, annotation_definition, frame_num, fixed, object_visible=True):
|
|
1805
|
+
"""
|
|
1806
|
+
new frame state to annotation
|
|
1807
|
+
|
|
1808
|
+
:param annotation: annotation
|
|
1809
|
+
:param annotation_definition: annotation type object - must be same type as annotation
|
|
1810
|
+
:param frame_num: frame number
|
|
1811
|
+
:param fixed: is fixed
|
|
1812
|
+
:param object_visible: does the annotated object is visible
|
|
1813
|
+
:return: FrameAnnotation object
|
|
1814
|
+
"""
|
|
1815
|
+
frame = cls(
|
|
1816
|
+
# annotations
|
|
1817
|
+
annotation=annotation,
|
|
1818
|
+
annotation_definition=annotation_definition,
|
|
1819
|
+
|
|
1820
|
+
# multi
|
|
1821
|
+
frame_num=frame_num,
|
|
1822
|
+
fixed=fixed,
|
|
1823
|
+
object_visible=object_visible,
|
|
1824
|
+
)
|
|
1825
|
+
if annotation_definition.attributes:
|
|
1826
|
+
frame.attributes = annotation_definition.attributes
|
|
1827
|
+
return frame
|
|
1828
|
+
|
|
1829
|
+
@classmethod
|
|
1830
|
+
def from_snapshot(cls, annotation, _json, fps):
|
|
1831
|
+
"""
|
|
1832
|
+
new frame state to annotation
|
|
1833
|
+
|
|
1834
|
+
:param annotation: annotation
|
|
1835
|
+
:param _json: annotation type object - must be same type as annotation
|
|
1836
|
+
:param fps: frame number
|
|
1837
|
+
:return: FrameAnnotation object
|
|
1838
|
+
"""
|
|
1839
|
+
# get annotation class
|
|
1840
|
+
_json['type'] = annotation.type
|
|
1841
|
+
attrs = _json.get('attributes', None)
|
|
1842
|
+
annotation_definition = cls.json_to_annotation_definition(_json=_json)
|
|
1843
|
+
|
|
1844
|
+
frame_num = _json.get('frame', annotation.last_frame + 1)
|
|
1845
|
+
|
|
1846
|
+
return cls(
|
|
1847
|
+
# annotations
|
|
1848
|
+
annotation=annotation,
|
|
1849
|
+
annotation_definition=annotation_definition,
|
|
1850
|
+
|
|
1851
|
+
# multi
|
|
1852
|
+
frame_num=frame_num,
|
|
1853
|
+
fixed=_json.get('fixed', False),
|
|
1854
|
+
object_visible=_json.get('objectVisible', True),
|
|
1855
|
+
recipe_1_attributes=attrs,
|
|
1856
|
+
)
|
|
1857
|
+
|
|
1858
|
+
def to_snapshot(self):
|
|
1859
|
+
|
|
1860
|
+
snapshot_dict = {
|
|
1861
|
+
'frame': self.frame_num,
|
|
1862
|
+
'fixed': self.fixed,
|
|
1863
|
+
'label': self.label,
|
|
1864
|
+
'type': self.type,
|
|
1865
|
+
'objectVisible': self.object_visible,
|
|
1866
|
+
'data': self.coordinates
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
if self.annotation_definition.description is not None:
|
|
1870
|
+
snapshot_dict['description'] = self.annotation_definition.description
|
|
1871
|
+
|
|
1872
|
+
if self.attributes is not None:
|
|
1873
|
+
snapshot_dict['namedAttributes'] = self.attributes
|
|
1874
|
+
|
|
1875
|
+
if self._recipe_1_attributes is not None:
|
|
1876
|
+
snapshot_dict['attributes'] = self._recipe_1_attributes
|
|
1877
|
+
|
|
1878
|
+
|
|
1879
|
+
return snapshot_dict
|