dtlpy 1.115.44__py3-none-any.whl → 1.117.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. dtlpy/__init__.py +491 -491
  2. dtlpy/__version__.py +1 -1
  3. dtlpy/assets/__init__.py +26 -26
  4. dtlpy/assets/code_server/config.yaml +2 -2
  5. dtlpy/assets/code_server/installation.sh +24 -24
  6. dtlpy/assets/code_server/launch.json +13 -13
  7. dtlpy/assets/code_server/settings.json +2 -2
  8. dtlpy/assets/main.py +53 -53
  9. dtlpy/assets/main_partial.py +18 -18
  10. dtlpy/assets/mock.json +11 -11
  11. dtlpy/assets/model_adapter.py +83 -83
  12. dtlpy/assets/package.json +61 -61
  13. dtlpy/assets/package_catalog.json +29 -29
  14. dtlpy/assets/package_gitignore +307 -307
  15. dtlpy/assets/service_runners/__init__.py +33 -33
  16. dtlpy/assets/service_runners/converter.py +96 -96
  17. dtlpy/assets/service_runners/multi_method.py +49 -49
  18. dtlpy/assets/service_runners/multi_method_annotation.py +54 -54
  19. dtlpy/assets/service_runners/multi_method_dataset.py +55 -55
  20. dtlpy/assets/service_runners/multi_method_item.py +52 -52
  21. dtlpy/assets/service_runners/multi_method_json.py +52 -52
  22. dtlpy/assets/service_runners/single_method.py +37 -37
  23. dtlpy/assets/service_runners/single_method_annotation.py +43 -43
  24. dtlpy/assets/service_runners/single_method_dataset.py +43 -43
  25. dtlpy/assets/service_runners/single_method_item.py +41 -41
  26. dtlpy/assets/service_runners/single_method_json.py +42 -42
  27. dtlpy/assets/service_runners/single_method_multi_input.py +45 -45
  28. dtlpy/assets/voc_annotation_template.xml +23 -23
  29. dtlpy/caches/base_cache.py +32 -32
  30. dtlpy/caches/cache.py +473 -473
  31. dtlpy/caches/dl_cache.py +201 -201
  32. dtlpy/caches/filesystem_cache.py +89 -89
  33. dtlpy/caches/redis_cache.py +84 -84
  34. dtlpy/dlp/__init__.py +20 -20
  35. dtlpy/dlp/cli_utilities.py +367 -367
  36. dtlpy/dlp/command_executor.py +764 -764
  37. dtlpy/dlp/dlp +1 -1
  38. dtlpy/dlp/dlp.bat +1 -1
  39. dtlpy/dlp/dlp.py +128 -128
  40. dtlpy/dlp/parser.py +651 -651
  41. dtlpy/entities/__init__.py +83 -83
  42. dtlpy/entities/analytic.py +347 -347
  43. dtlpy/entities/annotation.py +1879 -1879
  44. dtlpy/entities/annotation_collection.py +699 -699
  45. dtlpy/entities/annotation_definitions/__init__.py +20 -20
  46. dtlpy/entities/annotation_definitions/base_annotation_definition.py +100 -100
  47. dtlpy/entities/annotation_definitions/box.py +195 -195
  48. dtlpy/entities/annotation_definitions/classification.py +67 -67
  49. dtlpy/entities/annotation_definitions/comparison.py +72 -72
  50. dtlpy/entities/annotation_definitions/cube.py +204 -204
  51. dtlpy/entities/annotation_definitions/cube_3d.py +149 -149
  52. dtlpy/entities/annotation_definitions/description.py +32 -32
  53. dtlpy/entities/annotation_definitions/ellipse.py +124 -124
  54. dtlpy/entities/annotation_definitions/free_text.py +62 -62
  55. dtlpy/entities/annotation_definitions/gis.py +69 -69
  56. dtlpy/entities/annotation_definitions/note.py +139 -139
  57. dtlpy/entities/annotation_definitions/point.py +117 -117
  58. dtlpy/entities/annotation_definitions/polygon.py +182 -182
  59. dtlpy/entities/annotation_definitions/polyline.py +111 -111
  60. dtlpy/entities/annotation_definitions/pose.py +92 -92
  61. dtlpy/entities/annotation_definitions/ref_image.py +86 -86
  62. dtlpy/entities/annotation_definitions/segmentation.py +240 -240
  63. dtlpy/entities/annotation_definitions/subtitle.py +34 -34
  64. dtlpy/entities/annotation_definitions/text.py +85 -85
  65. dtlpy/entities/annotation_definitions/undefined_annotation.py +74 -74
  66. dtlpy/entities/app.py +220 -220
  67. dtlpy/entities/app_module.py +107 -107
  68. dtlpy/entities/artifact.py +174 -174
  69. dtlpy/entities/assignment.py +399 -399
  70. dtlpy/entities/base_entity.py +214 -214
  71. dtlpy/entities/bot.py +113 -113
  72. dtlpy/entities/codebase.py +292 -292
  73. dtlpy/entities/collection.py +38 -38
  74. dtlpy/entities/command.py +169 -169
  75. dtlpy/entities/compute.py +449 -449
  76. dtlpy/entities/dataset.py +1299 -1299
  77. dtlpy/entities/directory_tree.py +44 -44
  78. dtlpy/entities/dpk.py +470 -470
  79. dtlpy/entities/driver.py +235 -235
  80. dtlpy/entities/execution.py +397 -397
  81. dtlpy/entities/feature.py +124 -124
  82. dtlpy/entities/feature_set.py +152 -145
  83. dtlpy/entities/filters.py +798 -798
  84. dtlpy/entities/gis_item.py +107 -107
  85. dtlpy/entities/integration.py +184 -184
  86. dtlpy/entities/item.py +975 -959
  87. dtlpy/entities/label.py +123 -123
  88. dtlpy/entities/links.py +85 -85
  89. dtlpy/entities/message.py +175 -175
  90. dtlpy/entities/model.py +684 -684
  91. dtlpy/entities/node.py +1005 -1005
  92. dtlpy/entities/ontology.py +810 -803
  93. dtlpy/entities/organization.py +287 -287
  94. dtlpy/entities/package.py +657 -657
  95. dtlpy/entities/package_defaults.py +5 -5
  96. dtlpy/entities/package_function.py +185 -185
  97. dtlpy/entities/package_module.py +113 -113
  98. dtlpy/entities/package_slot.py +118 -118
  99. dtlpy/entities/paged_entities.py +299 -299
  100. dtlpy/entities/pipeline.py +624 -624
  101. dtlpy/entities/pipeline_execution.py +279 -279
  102. dtlpy/entities/project.py +394 -394
  103. dtlpy/entities/prompt_item.py +505 -505
  104. dtlpy/entities/recipe.py +301 -301
  105. dtlpy/entities/reflect_dict.py +102 -102
  106. dtlpy/entities/resource_execution.py +138 -138
  107. dtlpy/entities/service.py +974 -963
  108. dtlpy/entities/service_driver.py +117 -117
  109. dtlpy/entities/setting.py +294 -294
  110. dtlpy/entities/task.py +495 -495
  111. dtlpy/entities/time_series.py +143 -143
  112. dtlpy/entities/trigger.py +426 -426
  113. dtlpy/entities/user.py +118 -118
  114. dtlpy/entities/webhook.py +124 -124
  115. dtlpy/examples/__init__.py +19 -19
  116. dtlpy/examples/add_labels.py +135 -135
  117. dtlpy/examples/add_metadata_to_item.py +21 -21
  118. dtlpy/examples/annotate_items_using_model.py +65 -65
  119. dtlpy/examples/annotate_video_using_model_and_tracker.py +75 -75
  120. dtlpy/examples/annotations_convert_to_voc.py +9 -9
  121. dtlpy/examples/annotations_convert_to_yolo.py +9 -9
  122. dtlpy/examples/convert_annotation_types.py +51 -51
  123. dtlpy/examples/converter.py +143 -143
  124. dtlpy/examples/copy_annotations.py +22 -22
  125. dtlpy/examples/copy_folder.py +31 -31
  126. dtlpy/examples/create_annotations.py +51 -51
  127. dtlpy/examples/create_video_annotations.py +83 -83
  128. dtlpy/examples/delete_annotations.py +26 -26
  129. dtlpy/examples/filters.py +113 -113
  130. dtlpy/examples/move_item.py +23 -23
  131. dtlpy/examples/play_video_annotation.py +13 -13
  132. dtlpy/examples/show_item_and_mask.py +53 -53
  133. dtlpy/examples/triggers.py +49 -49
  134. dtlpy/examples/upload_batch_of_items.py +20 -20
  135. dtlpy/examples/upload_items_and_custom_format_annotations.py +55 -55
  136. dtlpy/examples/upload_items_with_modalities.py +43 -43
  137. dtlpy/examples/upload_segmentation_annotations_from_mask_image.py +44 -44
  138. dtlpy/examples/upload_yolo_format_annotations.py +70 -70
  139. dtlpy/exceptions.py +125 -125
  140. dtlpy/miscellaneous/__init__.py +20 -20
  141. dtlpy/miscellaneous/dict_differ.py +95 -95
  142. dtlpy/miscellaneous/git_utils.py +217 -217
  143. dtlpy/miscellaneous/json_utils.py +14 -14
  144. dtlpy/miscellaneous/list_print.py +105 -105
  145. dtlpy/miscellaneous/zipping.py +130 -130
  146. dtlpy/ml/__init__.py +20 -20
  147. dtlpy/ml/base_feature_extractor_adapter.py +27 -27
  148. dtlpy/ml/base_model_adapter.py +1287 -1230
  149. dtlpy/ml/metrics.py +461 -461
  150. dtlpy/ml/predictions_utils.py +274 -274
  151. dtlpy/ml/summary_writer.py +57 -57
  152. dtlpy/ml/train_utils.py +60 -60
  153. dtlpy/new_instance.py +252 -252
  154. dtlpy/repositories/__init__.py +56 -56
  155. dtlpy/repositories/analytics.py +85 -85
  156. dtlpy/repositories/annotations.py +916 -916
  157. dtlpy/repositories/apps.py +383 -383
  158. dtlpy/repositories/artifacts.py +452 -452
  159. dtlpy/repositories/assignments.py +599 -599
  160. dtlpy/repositories/bots.py +213 -213
  161. dtlpy/repositories/codebases.py +559 -559
  162. dtlpy/repositories/collections.py +332 -332
  163. dtlpy/repositories/commands.py +152 -152
  164. dtlpy/repositories/compositions.py +61 -61
  165. dtlpy/repositories/computes.py +439 -439
  166. dtlpy/repositories/datasets.py +1585 -1504
  167. dtlpy/repositories/downloader.py +1157 -923
  168. dtlpy/repositories/dpks.py +433 -433
  169. dtlpy/repositories/drivers.py +482 -482
  170. dtlpy/repositories/executions.py +815 -815
  171. dtlpy/repositories/feature_sets.py +256 -226
  172. dtlpy/repositories/features.py +255 -255
  173. dtlpy/repositories/integrations.py +484 -484
  174. dtlpy/repositories/items.py +912 -912
  175. dtlpy/repositories/messages.py +94 -94
  176. dtlpy/repositories/models.py +1000 -1000
  177. dtlpy/repositories/nodes.py +80 -80
  178. dtlpy/repositories/ontologies.py +511 -511
  179. dtlpy/repositories/organizations.py +525 -525
  180. dtlpy/repositories/packages.py +1941 -1941
  181. dtlpy/repositories/pipeline_executions.py +451 -451
  182. dtlpy/repositories/pipelines.py +640 -640
  183. dtlpy/repositories/projects.py +539 -539
  184. dtlpy/repositories/recipes.py +429 -399
  185. dtlpy/repositories/resource_executions.py +137 -137
  186. dtlpy/repositories/schema.py +120 -120
  187. dtlpy/repositories/service_drivers.py +213 -213
  188. dtlpy/repositories/services.py +1704 -1704
  189. dtlpy/repositories/settings.py +339 -339
  190. dtlpy/repositories/tasks.py +1477 -1477
  191. dtlpy/repositories/times_series.py +278 -278
  192. dtlpy/repositories/triggers.py +536 -536
  193. dtlpy/repositories/upload_element.py +257 -257
  194. dtlpy/repositories/uploader.py +661 -661
  195. dtlpy/repositories/webhooks.py +249 -249
  196. dtlpy/services/__init__.py +22 -22
  197. dtlpy/services/aihttp_retry.py +131 -131
  198. dtlpy/services/api_client.py +1786 -1785
  199. dtlpy/services/api_reference.py +40 -40
  200. dtlpy/services/async_utils.py +133 -133
  201. dtlpy/services/calls_counter.py +44 -44
  202. dtlpy/services/check_sdk.py +68 -68
  203. dtlpy/services/cookie.py +115 -115
  204. dtlpy/services/create_logger.py +156 -156
  205. dtlpy/services/events.py +84 -84
  206. dtlpy/services/logins.py +235 -235
  207. dtlpy/services/reporter.py +256 -256
  208. dtlpy/services/service_defaults.py +91 -91
  209. dtlpy/utilities/__init__.py +20 -20
  210. dtlpy/utilities/annotations/__init__.py +16 -16
  211. dtlpy/utilities/annotations/annotation_converters.py +269 -269
  212. dtlpy/utilities/base_package_runner.py +285 -264
  213. dtlpy/utilities/converter.py +1650 -1650
  214. dtlpy/utilities/dataset_generators/__init__.py +1 -1
  215. dtlpy/utilities/dataset_generators/dataset_generator.py +670 -670
  216. dtlpy/utilities/dataset_generators/dataset_generator_tensorflow.py +23 -23
  217. dtlpy/utilities/dataset_generators/dataset_generator_torch.py +21 -21
  218. dtlpy/utilities/local_development/__init__.py +1 -1
  219. dtlpy/utilities/local_development/local_session.py +179 -179
  220. dtlpy/utilities/reports/__init__.py +2 -2
  221. dtlpy/utilities/reports/figures.py +343 -343
  222. dtlpy/utilities/reports/report.py +71 -71
  223. dtlpy/utilities/videos/__init__.py +17 -17
  224. dtlpy/utilities/videos/video_player.py +598 -598
  225. dtlpy/utilities/videos/videos.py +470 -470
  226. {dtlpy-1.115.44.data → dtlpy-1.117.6.data}/scripts/dlp +1 -1
  227. dtlpy-1.117.6.data/scripts/dlp.bat +2 -0
  228. {dtlpy-1.115.44.data → dtlpy-1.117.6.data}/scripts/dlp.py +128 -128
  229. {dtlpy-1.115.44.dist-info → dtlpy-1.117.6.dist-info}/METADATA +186 -186
  230. dtlpy-1.117.6.dist-info/RECORD +239 -0
  231. {dtlpy-1.115.44.dist-info → dtlpy-1.117.6.dist-info}/WHEEL +1 -1
  232. {dtlpy-1.115.44.dist-info → dtlpy-1.117.6.dist-info}/licenses/LICENSE +200 -200
  233. tests/features/environment.py +551 -551
  234. dtlpy/assets/__pycache__/__init__.cpython-310.pyc +0 -0
  235. dtlpy-1.115.44.data/scripts/dlp.bat +0 -2
  236. dtlpy-1.115.44.dist-info/RECORD +0 -240
  237. {dtlpy-1.115.44.dist-info → dtlpy-1.117.6.dist-info}/entry_points.txt +0 -0
  238. {dtlpy-1.115.44.dist-info → dtlpy-1.117.6.dist-info}/top_level.txt +0 -0
@@ -1,598 +1,598 @@
1
- import logging
2
- import json
3
- import time
4
- import os
5
- import numpy as np
6
- import dtlpy as dl
7
-
8
- logger = logging.getLogger(name='dtlpy')
9
-
10
- class VideoPlayer:
11
- """
12
- Video Player GUI.
13
- """
14
-
15
- def __init__(self, project_name=None, project_id=None,
16
- dataset_name=None, dataset_id=None,
17
- item_filepath=None, item_id=None):
18
- try:
19
- import tkinter
20
- except ImportError:
21
- logger.error(
22
- 'Import Error! Cant import tkinter. Annotations operations will be limited. import manually and fix errors')
23
- raise
24
-
25
- # init tkinter window
26
- self.window = tkinter.Tk()
27
- self.window.title('Dataloop Player')
28
-
29
- self.video_source = None
30
- self.video_annotations = None
31
- self.local_annotations_filename = None
32
- self.labels = None
33
- self.project = None
34
- self.dataset = None
35
- self.item = None
36
- #############################
37
- # load video and annotation #
38
- #############################
39
- self.platform_params = {'project_name': project_name, 'project_id': project_id,
40
- 'dataset_name': dataset_name, 'dataset_id': dataset_id,
41
- 'item_filepath': item_filepath, 'item_id': item_id}
42
- self.from_platform()
43
-
44
- # load video
45
- self.photo = None
46
- self.vid = None
47
- self.load_video()
48
-
49
- # canvas to put frames on
50
- self.canvas = tkinter.Canvas(self.window, width=self.vid.width, height=self.vid.height)
51
- self.canvas.pack()
52
- self.canvas.bind('<Configure>', self._resize_image)
53
-
54
- ########################
55
- # create buttons panel #
56
- ########################
57
- self.window_width = self.vid.width
58
- self.window_height = self.vid.height
59
- self.buttons_window = tkinter.Toplevel(master=self.window)
60
- self.buttons_window.title('Controls')
61
- self.buttons_window.geometry()
62
-
63
- button_frame = tkinter.Frame(self.buttons_window)
64
- button_frame.grid(sticky="W", row=0, column=0)
65
-
66
- ###############
67
- # information #
68
- ###############
69
- txt_dataset_name = tkinter.Label(button_frame)
70
- txt_dataset_name.grid(sticky="W", row=2, column=0, columnspan=10)
71
- txt_dataset_name.configure(text='Dataset name: %s' % self.dataset.name)
72
- txt_item_name = tkinter.Label(button_frame)
73
- txt_item_name.grid(sticky="W", row=3, column=0, columnspan=10)
74
- txt_item_name.configure(text='Item name: %s' % self.item.name)
75
- ###############
76
- # Exit Button #
77
- ###############
78
- btn_exit = tkinter.Button(button_frame, text="Close", command=self.close).grid(sticky="W", row=17, column=0)
79
- ################
80
- # Play / Pause #
81
- ################
82
- self.btn_toggle_play = tkinter.Button(button_frame, text='Play ', command=self.toggle_play)
83
- self.btn_toggle_play.grid(sticky="W", row=4, column=0)
84
- ###################
85
- # Next prev frame #
86
- ###################
87
- btn_next_frame = tkinter.Button(button_frame, text="Next frame", command=self.next_frame).grid(sticky="W",
88
- row=4,
89
- column=2)
90
- btn_prev_frame = tkinter.Button(button_frame, text="Prev frame", command=self.prev_frame).grid(sticky="W",
91
- row=4,
92
- column=3)
93
- self.btn_toggle_frame_num = tkinter.Button(button_frame,
94
- text="Hide frame number",
95
- command=self.toggle_show_frame_number)
96
- self.btn_toggle_frame_num.grid(sticky="W", row=5, column=0, columnspan=10)
97
- self.btn_toggle_annotations = tkinter.Button(button_frame,
98
- text="Hide annotations",
99
- command=self.toggle_show_annotations)
100
- self.btn_toggle_annotations.grid(sticky="W", row=6, column=0, columnspan=10)
101
- self.btn_toggle_label = tkinter.Button(button_frame,
102
- text="Hide label",
103
- command=self.toggle_show_label)
104
- self.btn_toggle_label.grid(sticky="W", row=7, column=0, columnspan=10)
105
- #################
106
- # Export button #
107
- #################
108
- tkinter.Button(button_frame, text="Export video", command=self.export) \
109
- .grid(sticky="W", row=16, column=2, columnspan=2)
110
- # ################
111
- # # Apply button #
112
- # ################
113
- # btn_apply_offset_fix = tkinter.Button(button_frame, text="Apply offset fix", command=self.apply_offset_fix) \
114
- # .grid(sticky="W", row=16, column=0, columnspan=2)
115
- btn_reset = tkinter.Button(button_frame, text="Reset", command=self.reset).grid(sticky="W", row=4, column=4)
116
- # #####################
117
- # # Annotation Offset #
118
- # #####################
119
- # tkinter.Label(button_frame, text="Annotation offset (in frames):") \
120
- # .grid(sticky="W", row=13, column=0,
121
- # columnspan=10)
122
- # self.annotations_offset_entry = tkinter.Entry(button_frame)
123
- # self.annotations_offset_entry.bind("<Return>", self.set_annotation_offset)
124
- # self.annotations_offset_entry.grid(sticky="W", row=14, column=0, columnspan=10)
125
- # self.annotation_offset_text = tkinter.Label(button_frame)
126
- # self.annotation_offset_text.grid(sticky="W", row=15, column=0, columnspan=10)
127
- ##############
128
- # Set Frames #
129
- ##############
130
- tkinter.Label(button_frame, text="Set frame (in frames):") \
131
- .grid(sticky="W", row=10, column=0, columnspan=10)
132
- self.current_frame_entry = tkinter.Entry(button_frame)
133
- self.current_frame_entry.bind("<Return>", self.set_current_frame)
134
- self.current_frame_entry.grid(sticky="W", row=11, column=0, columnspan=10)
135
- self.current_frame_text = tkinter.Label(button_frame)
136
- self.current_frame_text.grid(sticky="W", row=12, column=0, columnspan=10)
137
- #########################
138
- # Timestamp information #
139
- #########################
140
- self.frame_timestamp_text = tkinter.Label(button_frame)
141
- self.frame_timestamp_text.grid(sticky="W", row=8, column=0, columnspan=10)
142
- self.annotations_timestamp_text = tkinter.Label(button_frame)
143
- self.annotations_timestamp_text.grid(sticky="W", row=9, column=0, columnspan=10)
144
-
145
- ##############
146
- # Parameters #
147
- ##############
148
- self.delay = None
149
- self.annotations_timestamp = None
150
- # self.annotations_offset = None
151
- self.annotations_timestamp = None
152
- self.show_frame_num = True
153
- self.show_annotations = True
154
- self.show_label = True
155
- self.playing = False
156
- self.init_all_params()
157
- self.show_frame(0)
158
-
159
- ###############
160
- # Start video #
161
- ###############
162
- self.delay = int(1000 * 1 / self.vid.fps)
163
- self.update()
164
- button_frame.lift()
165
- self.window.mainloop()
166
-
167
- def _resize_image(self, event):
168
- """
169
- Resize frame to match window size
170
- :param event:
171
- :return:
172
- """
173
- self.window_width = event.width
174
- self.window_height = event.height
175
-
176
- def reset(self):
177
- """
178
- Reset video and annotation
179
- :return:
180
- """
181
- self.from_platform()
182
- self.load_video()
183
- self.init_all_params()
184
- self.show_frame(0)
185
-
186
- def export(self):
187
- """
188
- Create an annotated video saved to file
189
- :return:
190
- """
191
-
192
- try:
193
- import cv2
194
- except (ImportError, ModuleNotFoundError):
195
- logger.error(
196
- 'Import Error! Cant import cv2. Annotations operations will be limited. import manually and fix errors')
197
- raise
198
-
199
- try:
200
- from tkinter import ttk
201
- except ImportError:
202
- logger.error(
203
- 'Import Error! Cant import ttk from tkinter. Annotations operations will be limited. import manually and fix errors')
204
- raise
205
-
206
- # start progress bar
207
- p, ext = os.path.splitext(self.video_source)
208
- output_filename = p + '_out.mp4'
209
-
210
- # read input video
211
- reader = cv2.VideoCapture(self.video_source)
212
- width = int(reader.get(cv2.CAP_PROP_FRAME_WIDTH))
213
- height = int(reader.get(cv2.CAP_PROP_FRAME_HEIGHT))
214
- fps = reader.get(cv2.CAP_PROP_FPS)
215
- encoding = int(reader.get(cv2.CAP_PROP_FOURCC))
216
- n_frames = int(reader.get(cv2.CAP_PROP_FRAME_COUNT))
217
- writer = cv2.VideoWriter(output_filename, cv2.VideoWriter_fourcc(*'MP4V'), fps, (width, height))
218
-
219
- # create popup
220
- popup = tkinter.Toplevel()
221
- tkinter.Label(popup, text='Exporting to\n%s' % output_filename).grid(row=0, column=0)
222
- progress_var = tkinter.DoubleVar()
223
- progress_bar = ttk.Progressbar(popup, variable=progress_var, maximum=n_frames)
224
- progress_bar.grid(row=1, column=0) # .pack(fill=tk.X, expand=1, side=tk.BOTTOM)
225
- popup.pack_slaves()
226
-
227
- i_frame = 0
228
- while reader.isOpened():
229
- popup.update()
230
- ret, frame = reader.read()
231
- if not ret:
232
- break
233
- # mark on frame
234
- annotations = [frame_annotation.get_annotation_by_frame(frame=i_frame)
235
- for frame_annotation in self.video_annotations]
236
-
237
- for annotation in annotations:
238
- if annotation is None:
239
- continue
240
- frame = annotation.show(image=frame,
241
- color=self.get_class_color(annotation.label))
242
- if self.show_label:
243
- text = '%s-%s' % (annotation.label, ','.join(annotation.attributes))
244
- frame = cv2.putText(frame,
245
- text=text,
246
- org=tuple([int(np.round(annotation.left)), int(np.round(annotation.top))]),
247
- color=(255, 0, 0),
248
- fontFace=cv2.FONT_HERSHEY_DUPLEX,
249
- fontScale=1,
250
- thickness=2)
251
- # write
252
- writer.write(frame)
253
- i_frame += 1
254
- progress_var.set(i_frame)
255
- reader.release()
256
- writer.release()
257
- popup.destroy()
258
-
259
- def load_video(self):
260
- """
261
- Load VideoCapture instance
262
- :return:
263
- """
264
- self.vid = VideoCapture(self.video_source)
265
-
266
- def init_all_params(self):
267
- """
268
- Reset all initial variables
269
- :return:
270
- """
271
- self.annotations_timestamp = 0
272
- # self.annotations_offset = 0
273
- # self.annotation_offset_text.configure(text='Current: %d' % self.annotations_offset)
274
- self.annotations_timestamp_text.configure(text='Annotation timestamp:\n %d' % self.annotations_timestamp)
275
- self.annotations_timestamp_text.grid(sticky="W", row=9, column=0, columnspan=10)
276
- # set text frames
277
- # self.annotations_offset_entry.delete(0, 'end')
278
- # self.annotations_offset_entry.insert(0, str(self.annotations_offset))
279
- self.current_frame_entry.delete(0, 'end')
280
- self.current_frame_entry.insert(0, str(self.vid.frame_number))
281
-
282
- def from_local(self):
283
- """
284
- Load local video and annotation
285
- :return:
286
- """
287
- if self.local_annotations_filename is not None:
288
- with open(self.local_annotations_filename, 'r') as f:
289
- data = json.load(f)
290
- self.video_annotations = dl.AnnotationCollection.from_json(data['annotations'])
291
-
292
- def from_platform(self):
293
- """
294
- Load video and annotations from platform
295
- :return:
296
- """
297
- project_name = self.platform_params['project_name']
298
- project_id = self.platform_params['project_id']
299
- dataset_name = self.platform_params['dataset_name']
300
- dataset_id = self.platform_params['dataset_id']
301
- item_filepath = self.platform_params['item_filepath']
302
- item_id = self.platform_params['item_id']
303
-
304
- # load remote item
305
- if dataset_id is None:
306
- self.project = dl.projects.get(project_name=project_name, project_id=project_id)
307
- if self.project is None:
308
- raise ValueError('Project doesnt exists. name: %s, id: %s' % (project_name, project_id))
309
- self.dataset = self.project.datasets.get(dataset_name=dataset_name, dataset_id=dataset_id)
310
- else:
311
- self.dataset = dl.datasets.get(dataset_id=dataset_id)
312
- if self.dataset is None:
313
- raise ValueError('Dataset doesnt exists. name: %s, id: %s' % (dataset_name, dataset_id))
314
- self.item = self.dataset.items.get(filepath=item_filepath, item_id=item_id)
315
- if self.item is None:
316
- raise ValueError('Item doesnt exists. name: %s, id: %s' % (item_filepath, item_id))
317
- self.labels = {label.tag: label.rgb for label in self.dataset.labels}
318
- _, ext = os.path.splitext(self.item.filename[1:])
319
- video_filename = os.path.join(self.dataset.__get_local_path__(), self.item.filename[1:])
320
- if not os.path.isdir(os.path.dirname(video_filename)):
321
- os.makedirs(os.path.dirname(video_filename))
322
- if not os.path.isfile(video_filename):
323
- self.item.download(local_path=os.path.dirname(video_filename), to_items_folder=False)
324
- self.video_source = video_filename
325
- self.video_annotations = self.item.annotations.list()
326
-
327
- def close(self):
328
- """
329
- Terminate window and application
330
- :return:
331
- """
332
- self.window.destroy()
333
- self.buttons_window.destroy()
334
-
335
- #
336
- # def set_annotation_offset(self, entry):
337
- # """
338
- # Set annotations offset to video start time
339
- # :param entry:
340
- # :return:
341
- # """
342
- # self.annotations_offset = int(self.annotations_offset_entry.get())
343
- # self.annotation_offset_text.configure(text='Current: %d' % self.annotations_offset)
344
-
345
- def set_current_frame(self, entry):
346
- """
347
- Go to specific frame
348
- :param entry:
349
- :return:
350
- """
351
- input_frame = int(self.current_frame_entry.get())
352
- self.show_frame(frame_number=input_frame)
353
-
354
- def toggle_show_frame_number(self):
355
- """
356
- Show/hide frame number on frames
357
- :return:
358
- """
359
- if self.show_frame_num:
360
- self.show_frame_num = False
361
- self.btn_toggle_frame_num.config(text='Show frame num')
362
- else:
363
- self.show_frame_num = True
364
- self.btn_toggle_frame_num.config(text='Hide frame num')
365
-
366
- def toggle_show_annotations(self):
367
- """
368
- Show/hide annotations from frame
369
- :return:
370
- """
371
- if self.show_annotations:
372
- self.show_annotations = False
373
- self.btn_toggle_annotations.config(text='Show annotations')
374
- else:
375
- self.show_annotations = True
376
- self.btn_toggle_annotations.config(text='Hide annotations')
377
-
378
- def toggle_show_label(self):
379
- """
380
- Show/hide label per annotations
381
- :return:
382
- """
383
- if self.show_label:
384
- self.show_label = False
385
- self.btn_toggle_label.config(text='Show label')
386
- else:
387
- self.show_label = True
388
- self.btn_toggle_label.config(text='Hide label')
389
-
390
- def get_class_color(self, label):
391
- """
392
- Color of label
393
- :param label:
394
- :return:
395
- """
396
- if label not in self.labels:
397
- print('[WARNING] label not in dataset labels: %s' % label)
398
- return (255, 0, 0)
399
- color = self.labels[label]
400
- if isinstance(color, str):
401
- if color.startswith('rgb'):
402
- color = tuple(eval(color.lstrip('rgb')))
403
- elif color.startswith('#'):
404
- color = tuple(int(color.lstrip('#')[i:i + 2], 16) for i in (0, 2, 4))
405
- else:
406
- print('[WARNING] Unknown color scheme: %s' % color)
407
- color = (255, 0, 0)
408
- return color
409
-
410
- def get_annotations(self, frame):
411
- """
412
- Get all annotations of frame
413
- :param frame:
414
- :return:
415
- """
416
- # self.annotations_timestamp = (self.vid.frame_number + self.annotations_offset) / self.vid.fps
417
- self.annotations_timestamp = self.vid.frame_number / self.vid.fps
418
- frame = self.video_annotations.get_frame(frame_num=self.vid.frame_number).show(image=frame,
419
- height=frame.shape[0],
420
- width=frame.shape[1],
421
- with_text=self.show_label)
422
- return frame
423
-
424
- def toggle_play(self):
425
- """
426
- Toggle play pause
427
- :return:
428
- """
429
- if self.playing:
430
- # need to pause
431
- self.playing = False
432
- self.btn_toggle_play.config(text='Play ')
433
- else:
434
- # need to play
435
- self.playing = True
436
- self.btn_toggle_play.config(text='Pause')
437
-
438
- def next_frame(self):
439
- """
440
- Get next frame
441
- :return:
442
- """
443
- self.show_frame()
444
-
445
- def prev_frame(self):
446
- """
447
- Get previous frame
448
- :return:
449
- """
450
- self.show_frame(self.vid.frame_number - 1)
451
-
452
- # def apply_offset_fix(self):
453
- # """
454
- # Apply offset to platform
455
- # :return:
456
- # """
457
- # # delete annotations from platform
458
- # for annotation in self.video_annotations.annotations:
459
- # self.item.annotations.delete(annotations_id=annotation.id)
460
- #
461
- # # apply annotations fix
462
- # new_annotations = list()
463
- # for annotation in self.video_annotations.annotations:
464
- # new_annotation = annotation.copy()
465
- # for i in range(len(new_annotation['metadata']['snapshots_'])):
466
- # new_annotation['metadata']['snapshots_'][i]['startTime'] -= (self.annotations_offset / self.vid.fps)
467
- # new_annotation['metadata']['snapshots_'][i]['frameNum'] = int(
468
- # new_annotation['metadata']['snapshots_'][i]['startTime'] * self.vid.fps)
469
- # new_annotations.append(json.dumps(new_annotation))
470
- #
471
- # # upload annotations
472
- # self.item.annotation.upload(annotation=[new_annotations])
473
-
474
- def show_frame(self, frame_number=None):
475
- """
476
- Get a frame from the video source
477
- :param frame_number:
478
- :return:
479
- """
480
- try:
481
- import cv2
482
- except (ImportError, ModuleNotFoundError):
483
- logger.error(
484
- 'Import Error! Cant import cv2. Annotations operations will be limited. import manually and fix errors')
485
- raise
486
-
487
- try:
488
- import tkinter
489
- except ImportError:
490
- logger.error(
491
- 'Import Error! Cant import tkinter. Annotations operations will be limited. import manually and fix errors')
492
- raise
493
-
494
- try:
495
- import PIL.ImageTk
496
- import PIL.Image
497
- except ImportError:
498
- logger.error(
499
- 'Import Error! Cant import PIL.ImageTk/PIL.ImageTk. Annotations operations will be limited. '
500
- 'import manually and fix errors')
501
- raise
502
- ret, frame = self.vid.get_frame(frame_number)
503
- if ret:
504
- if self.show_annotations:
505
- frame = self.get_annotations(frame)
506
- if self.show_frame_num:
507
- text = '%d - %d' % (self.vid.frame_number, np.round(self.annotations_timestamp * self.vid.fps))
508
- frame = cv2.putText(frame,
509
- text=text,
510
- org=(100, 100),
511
- color=(0, 0, 255),
512
- fontFace=cv2.FONT_HERSHEY_DUPLEX,
513
- fontScale=2,
514
- thickness=3)
515
-
516
- self.photo = PIL.ImageTk.PhotoImage(
517
- image=PIL.Image.fromarray(frame).resize((self.window_width, self.window_height)),
518
- master=self.canvas)
519
- self.canvas.create_image(0, 0, image=self.photo, anchor=tkinter.NW)
520
- # set timestamp
521
- self.current_frame_text.configure(text='Frame number:\n%d' % self.vid.frame_number)
522
- self.current_frame_text.grid(sticky="W", row=12, column=0, columnspan=10)
523
- millis = int(1000 * self.vid.frame_number / self.vid.fps)
524
- seconds = (millis / 1000) % 60
525
- minutes = int((millis / (1000 * 60)) % 60)
526
- hours = int((millis / (1000 * 60 * 60)) % 24)
527
- self.frame_timestamp_text.configure(
528
- text='Frame timestamp:\n{:02d}:{:02d}:{:.3f}'.format(hours, minutes, seconds))
529
- self.frame_timestamp_text.grid(sticky="W", row=8, column=0, columnspan=10)
530
-
531
- def update(self):
532
- """
533
- Playing the movie. Get frames at the FPS and show
534
- :return:
535
- """
536
- tic = time.time()
537
- if self.playing:
538
- self.show_frame()
539
- add_delay = np.maximum(1, self.delay - int(1000 * (time.time() - tic)))
540
- self.window.after(ms=add_delay, func=self.update)
541
-
542
-
543
- class VideoCapture:
544
- """
545
- Video class using cv2 to play video
546
- """
547
- def __init__(self, source=0):
548
- try:
549
- import cv2
550
- except (ImportError, ModuleNotFoundError):
551
- logger.error(
552
- 'Import Error! Cant import cv2. Annotations operations will be limited. import manually and fix errors')
553
- raise
554
- # Open the video source
555
- self.vid = cv2.VideoCapture(source)
556
- if not self.vid.isOpened():
557
- raise ValueError("Unable to open video source", source)
558
-
559
- # Get video source width and height
560
- self.width = self.vid.get(cv2.CAP_PROP_FRAME_WIDTH)
561
- self.height = self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT)
562
- self.fps = self.vid.get(cv2.CAP_PROP_FPS)
563
- self.frame_number = self.vid.get(cv2.CAP_PROP_POS_FRAMES)
564
-
565
- def get_frame(self, frame_number=None):
566
- """
567
- get the frame from video
568
- :param frame_number:
569
- :return:
570
- """
571
- try:
572
- import cv2
573
- except (ImportError, ModuleNotFoundError):
574
- logger.error(
575
- 'Import Error! Cant import cv2. Annotations operations will be limited. import manually and fix errors')
576
- raise
577
- if self.vid.isOpened():
578
- if self.frame_number is None:
579
- self.frame_number = self.vid.get(cv2.CAP_PROP_POS_FRAMES)
580
- else:
581
- self.frame_number += 1
582
- if frame_number is not None:
583
- self.frame_number = frame_number
584
- self.vid.set(cv2.CAP_PROP_POS_FRAMES, frame_number)
585
- ret, frame = self.vid.read()
586
-
587
- if ret:
588
- # Return a boolean success flag and the current frame converted to BGR
589
- return ret, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
590
- else:
591
- return ret, None
592
- else:
593
- return False, None
594
-
595
- # Release the video source when the object is destroyed
596
- def __del__(self):
597
- if self.vid.isOpened():
598
- self.vid.release()
1
+ import logging
2
+ import json
3
+ import time
4
+ import os
5
+ import numpy as np
6
+ import dtlpy as dl
7
+
8
+ logger = logging.getLogger(name='dtlpy')
9
+
10
+ class VideoPlayer:
11
+ """
12
+ Video Player GUI.
13
+ """
14
+
15
+ def __init__(self, project_name=None, project_id=None,
16
+ dataset_name=None, dataset_id=None,
17
+ item_filepath=None, item_id=None):
18
+ try:
19
+ import tkinter
20
+ except ImportError:
21
+ logger.error(
22
+ 'Import Error! Cant import tkinter. Annotations operations will be limited. import manually and fix errors')
23
+ raise
24
+
25
+ # init tkinter window
26
+ self.window = tkinter.Tk()
27
+ self.window.title('Dataloop Player')
28
+
29
+ self.video_source = None
30
+ self.video_annotations = None
31
+ self.local_annotations_filename = None
32
+ self.labels = None
33
+ self.project = None
34
+ self.dataset = None
35
+ self.item = None
36
+ #############################
37
+ # load video and annotation #
38
+ #############################
39
+ self.platform_params = {'project_name': project_name, 'project_id': project_id,
40
+ 'dataset_name': dataset_name, 'dataset_id': dataset_id,
41
+ 'item_filepath': item_filepath, 'item_id': item_id}
42
+ self.from_platform()
43
+
44
+ # load video
45
+ self.photo = None
46
+ self.vid = None
47
+ self.load_video()
48
+
49
+ # canvas to put frames on
50
+ self.canvas = tkinter.Canvas(self.window, width=self.vid.width, height=self.vid.height)
51
+ self.canvas.pack()
52
+ self.canvas.bind('<Configure>', self._resize_image)
53
+
54
+ ########################
55
+ # create buttons panel #
56
+ ########################
57
+ self.window_width = self.vid.width
58
+ self.window_height = self.vid.height
59
+ self.buttons_window = tkinter.Toplevel(master=self.window)
60
+ self.buttons_window.title('Controls')
61
+ self.buttons_window.geometry()
62
+
63
+ button_frame = tkinter.Frame(self.buttons_window)
64
+ button_frame.grid(sticky="W", row=0, column=0)
65
+
66
+ ###############
67
+ # information #
68
+ ###############
69
+ txt_dataset_name = tkinter.Label(button_frame)
70
+ txt_dataset_name.grid(sticky="W", row=2, column=0, columnspan=10)
71
+ txt_dataset_name.configure(text='Dataset name: %s' % self.dataset.name)
72
+ txt_item_name = tkinter.Label(button_frame)
73
+ txt_item_name.grid(sticky="W", row=3, column=0, columnspan=10)
74
+ txt_item_name.configure(text='Item name: %s' % self.item.name)
75
+ ###############
76
+ # Exit Button #
77
+ ###############
78
+ btn_exit = tkinter.Button(button_frame, text="Close", command=self.close).grid(sticky="W", row=17, column=0)
79
+ ################
80
+ # Play / Pause #
81
+ ################
82
+ self.btn_toggle_play = tkinter.Button(button_frame, text='Play ', command=self.toggle_play)
83
+ self.btn_toggle_play.grid(sticky="W", row=4, column=0)
84
+ ###################
85
+ # Next prev frame #
86
+ ###################
87
+ btn_next_frame = tkinter.Button(button_frame, text="Next frame", command=self.next_frame).grid(sticky="W",
88
+ row=4,
89
+ column=2)
90
+ btn_prev_frame = tkinter.Button(button_frame, text="Prev frame", command=self.prev_frame).grid(sticky="W",
91
+ row=4,
92
+ column=3)
93
+ self.btn_toggle_frame_num = tkinter.Button(button_frame,
94
+ text="Hide frame number",
95
+ command=self.toggle_show_frame_number)
96
+ self.btn_toggle_frame_num.grid(sticky="W", row=5, column=0, columnspan=10)
97
+ self.btn_toggle_annotations = tkinter.Button(button_frame,
98
+ text="Hide annotations",
99
+ command=self.toggle_show_annotations)
100
+ self.btn_toggle_annotations.grid(sticky="W", row=6, column=0, columnspan=10)
101
+ self.btn_toggle_label = tkinter.Button(button_frame,
102
+ text="Hide label",
103
+ command=self.toggle_show_label)
104
+ self.btn_toggle_label.grid(sticky="W", row=7, column=0, columnspan=10)
105
+ #################
106
+ # Export button #
107
+ #################
108
+ tkinter.Button(button_frame, text="Export video", command=self.export) \
109
+ .grid(sticky="W", row=16, column=2, columnspan=2)
110
+ # ################
111
+ # # Apply button #
112
+ # ################
113
+ # btn_apply_offset_fix = tkinter.Button(button_frame, text="Apply offset fix", command=self.apply_offset_fix) \
114
+ # .grid(sticky="W", row=16, column=0, columnspan=2)
115
+ btn_reset = tkinter.Button(button_frame, text="Reset", command=self.reset).grid(sticky="W", row=4, column=4)
116
+ # #####################
117
+ # # Annotation Offset #
118
+ # #####################
119
+ # tkinter.Label(button_frame, text="Annotation offset (in frames):") \
120
+ # .grid(sticky="W", row=13, column=0,
121
+ # columnspan=10)
122
+ # self.annotations_offset_entry = tkinter.Entry(button_frame)
123
+ # self.annotations_offset_entry.bind("<Return>", self.set_annotation_offset)
124
+ # self.annotations_offset_entry.grid(sticky="W", row=14, column=0, columnspan=10)
125
+ # self.annotation_offset_text = tkinter.Label(button_frame)
126
+ # self.annotation_offset_text.grid(sticky="W", row=15, column=0, columnspan=10)
127
+ ##############
128
+ # Set Frames #
129
+ ##############
130
+ tkinter.Label(button_frame, text="Set frame (in frames):") \
131
+ .grid(sticky="W", row=10, column=0, columnspan=10)
132
+ self.current_frame_entry = tkinter.Entry(button_frame)
133
+ self.current_frame_entry.bind("<Return>", self.set_current_frame)
134
+ self.current_frame_entry.grid(sticky="W", row=11, column=0, columnspan=10)
135
+ self.current_frame_text = tkinter.Label(button_frame)
136
+ self.current_frame_text.grid(sticky="W", row=12, column=0, columnspan=10)
137
+ #########################
138
+ # Timestamp information #
139
+ #########################
140
+ self.frame_timestamp_text = tkinter.Label(button_frame)
141
+ self.frame_timestamp_text.grid(sticky="W", row=8, column=0, columnspan=10)
142
+ self.annotations_timestamp_text = tkinter.Label(button_frame)
143
+ self.annotations_timestamp_text.grid(sticky="W", row=9, column=0, columnspan=10)
144
+
145
+ ##############
146
+ # Parameters #
147
+ ##############
148
+ self.delay = None
149
+ self.annotations_timestamp = None
150
+ # self.annotations_offset = None
151
+ self.annotations_timestamp = None
152
+ self.show_frame_num = True
153
+ self.show_annotations = True
154
+ self.show_label = True
155
+ self.playing = False
156
+ self.init_all_params()
157
+ self.show_frame(0)
158
+
159
+ ###############
160
+ # Start video #
161
+ ###############
162
+ self.delay = int(1000 * 1 / self.vid.fps)
163
+ self.update()
164
+ button_frame.lift()
165
+ self.window.mainloop()
166
+
167
+ def _resize_image(self, event):
168
+ """
169
+ Resize frame to match window size
170
+ :param event:
171
+ :return:
172
+ """
173
+ self.window_width = event.width
174
+ self.window_height = event.height
175
+
176
+ def reset(self):
177
+ """
178
+ Reset video and annotation
179
+ :return:
180
+ """
181
+ self.from_platform()
182
+ self.load_video()
183
+ self.init_all_params()
184
+ self.show_frame(0)
185
+
186
+ def export(self):
187
+ """
188
+ Create an annotated video saved to file
189
+ :return:
190
+ """
191
+
192
+ try:
193
+ import cv2
194
+ except (ImportError, ModuleNotFoundError):
195
+ logger.error(
196
+ 'Import Error! Cant import cv2. Annotations operations will be limited. import manually and fix errors')
197
+ raise
198
+
199
+ try:
200
+ from tkinter import ttk
201
+ except ImportError:
202
+ logger.error(
203
+ 'Import Error! Cant import ttk from tkinter. Annotations operations will be limited. import manually and fix errors')
204
+ raise
205
+
206
+ # start progress bar
207
+ p, ext = os.path.splitext(self.video_source)
208
+ output_filename = p + '_out.mp4'
209
+
210
+ # read input video
211
+ reader = cv2.VideoCapture(self.video_source)
212
+ width = int(reader.get(cv2.CAP_PROP_FRAME_WIDTH))
213
+ height = int(reader.get(cv2.CAP_PROP_FRAME_HEIGHT))
214
+ fps = reader.get(cv2.CAP_PROP_FPS)
215
+ encoding = int(reader.get(cv2.CAP_PROP_FOURCC))
216
+ n_frames = int(reader.get(cv2.CAP_PROP_FRAME_COUNT))
217
+ writer = cv2.VideoWriter(output_filename, cv2.VideoWriter_fourcc(*'MP4V'), fps, (width, height))
218
+
219
+ # create popup
220
+ popup = tkinter.Toplevel()
221
+ tkinter.Label(popup, text='Exporting to\n%s' % output_filename).grid(row=0, column=0)
222
+ progress_var = tkinter.DoubleVar()
223
+ progress_bar = ttk.Progressbar(popup, variable=progress_var, maximum=n_frames)
224
+ progress_bar.grid(row=1, column=0) # .pack(fill=tk.X, expand=1, side=tk.BOTTOM)
225
+ popup.pack_slaves()
226
+
227
+ i_frame = 0
228
+ while reader.isOpened():
229
+ popup.update()
230
+ ret, frame = reader.read()
231
+ if not ret:
232
+ break
233
+ # mark on frame
234
+ annotations = [frame_annotation.get_annotation_by_frame(frame=i_frame)
235
+ for frame_annotation in self.video_annotations]
236
+
237
+ for annotation in annotations:
238
+ if annotation is None:
239
+ continue
240
+ frame = annotation.show(image=frame,
241
+ color=self.get_class_color(annotation.label))
242
+ if self.show_label:
243
+ text = '%s-%s' % (annotation.label, ','.join(annotation.attributes))
244
+ frame = cv2.putText(frame,
245
+ text=text,
246
+ org=tuple([int(np.round(annotation.left)), int(np.round(annotation.top))]),
247
+ color=(255, 0, 0),
248
+ fontFace=cv2.FONT_HERSHEY_DUPLEX,
249
+ fontScale=1,
250
+ thickness=2)
251
+ # write
252
+ writer.write(frame)
253
+ i_frame += 1
254
+ progress_var.set(i_frame)
255
+ reader.release()
256
+ writer.release()
257
+ popup.destroy()
258
+
259
+ def load_video(self):
260
+ """
261
+ Load VideoCapture instance
262
+ :return:
263
+ """
264
+ self.vid = VideoCapture(self.video_source)
265
+
266
+ def init_all_params(self):
267
+ """
268
+ Reset all initial variables
269
+ :return:
270
+ """
271
+ self.annotations_timestamp = 0
272
+ # self.annotations_offset = 0
273
+ # self.annotation_offset_text.configure(text='Current: %d' % self.annotations_offset)
274
+ self.annotations_timestamp_text.configure(text='Annotation timestamp:\n %d' % self.annotations_timestamp)
275
+ self.annotations_timestamp_text.grid(sticky="W", row=9, column=0, columnspan=10)
276
+ # set text frames
277
+ # self.annotations_offset_entry.delete(0, 'end')
278
+ # self.annotations_offset_entry.insert(0, str(self.annotations_offset))
279
+ self.current_frame_entry.delete(0, 'end')
280
+ self.current_frame_entry.insert(0, str(self.vid.frame_number))
281
+
282
+ def from_local(self):
283
+ """
284
+ Load local video and annotation
285
+ :return:
286
+ """
287
+ if self.local_annotations_filename is not None:
288
+ with open(self.local_annotations_filename, 'r') as f:
289
+ data = json.load(f)
290
+ self.video_annotations = dl.AnnotationCollection.from_json(data['annotations'])
291
+
292
+ def from_platform(self):
293
+ """
294
+ Load video and annotations from platform
295
+ :return:
296
+ """
297
+ project_name = self.platform_params['project_name']
298
+ project_id = self.platform_params['project_id']
299
+ dataset_name = self.platform_params['dataset_name']
300
+ dataset_id = self.platform_params['dataset_id']
301
+ item_filepath = self.platform_params['item_filepath']
302
+ item_id = self.platform_params['item_id']
303
+
304
+ # load remote item
305
+ if dataset_id is None:
306
+ self.project = dl.projects.get(project_name=project_name, project_id=project_id)
307
+ if self.project is None:
308
+ raise ValueError('Project doesnt exists. name: %s, id: %s' % (project_name, project_id))
309
+ self.dataset = self.project.datasets.get(dataset_name=dataset_name, dataset_id=dataset_id)
310
+ else:
311
+ self.dataset = dl.datasets.get(dataset_id=dataset_id)
312
+ if self.dataset is None:
313
+ raise ValueError('Dataset doesnt exists. name: %s, id: %s' % (dataset_name, dataset_id))
314
+ self.item = self.dataset.items.get(filepath=item_filepath, item_id=item_id)
315
+ if self.item is None:
316
+ raise ValueError('Item doesnt exists. name: %s, id: %s' % (item_filepath, item_id))
317
+ self.labels = {label.tag: label.rgb for label in self.dataset.labels}
318
+ _, ext = os.path.splitext(self.item.filename[1:])
319
+ video_filename = os.path.join(self.dataset.__get_local_path__(), self.item.filename[1:])
320
+ if not os.path.isdir(os.path.dirname(video_filename)):
321
+ os.makedirs(os.path.dirname(video_filename))
322
+ if not os.path.isfile(video_filename):
323
+ self.item.download(local_path=os.path.dirname(video_filename), to_items_folder=False)
324
+ self.video_source = video_filename
325
+ self.video_annotations = self.item.annotations.list()
326
+
327
+ def close(self):
328
+ """
329
+ Terminate window and application
330
+ :return:
331
+ """
332
+ self.window.destroy()
333
+ self.buttons_window.destroy()
334
+
335
+ #
336
+ # def set_annotation_offset(self, entry):
337
+ # """
338
+ # Set annotations offset to video start time
339
+ # :param entry:
340
+ # :return:
341
+ # """
342
+ # self.annotations_offset = int(self.annotations_offset_entry.get())
343
+ # self.annotation_offset_text.configure(text='Current: %d' % self.annotations_offset)
344
+
345
+ def set_current_frame(self, entry):
346
+ """
347
+ Go to specific frame
348
+ :param entry:
349
+ :return:
350
+ """
351
+ input_frame = int(self.current_frame_entry.get())
352
+ self.show_frame(frame_number=input_frame)
353
+
354
+ def toggle_show_frame_number(self):
355
+ """
356
+ Show/hide frame number on frames
357
+ :return:
358
+ """
359
+ if self.show_frame_num:
360
+ self.show_frame_num = False
361
+ self.btn_toggle_frame_num.config(text='Show frame num')
362
+ else:
363
+ self.show_frame_num = True
364
+ self.btn_toggle_frame_num.config(text='Hide frame num')
365
+
366
+ def toggle_show_annotations(self):
367
+ """
368
+ Show/hide annotations from frame
369
+ :return:
370
+ """
371
+ if self.show_annotations:
372
+ self.show_annotations = False
373
+ self.btn_toggle_annotations.config(text='Show annotations')
374
+ else:
375
+ self.show_annotations = True
376
+ self.btn_toggle_annotations.config(text='Hide annotations')
377
+
378
+ def toggle_show_label(self):
379
+ """
380
+ Show/hide label per annotations
381
+ :return:
382
+ """
383
+ if self.show_label:
384
+ self.show_label = False
385
+ self.btn_toggle_label.config(text='Show label')
386
+ else:
387
+ self.show_label = True
388
+ self.btn_toggle_label.config(text='Hide label')
389
+
390
+ def get_class_color(self, label):
391
+ """
392
+ Color of label
393
+ :param label:
394
+ :return:
395
+ """
396
+ if label not in self.labels:
397
+ print('[WARNING] label not in dataset labels: %s' % label)
398
+ return (255, 0, 0)
399
+ color = self.labels[label]
400
+ if isinstance(color, str):
401
+ if color.startswith('rgb'):
402
+ color = tuple(eval(color.lstrip('rgb')))
403
+ elif color.startswith('#'):
404
+ color = tuple(int(color.lstrip('#')[i:i + 2], 16) for i in (0, 2, 4))
405
+ else:
406
+ print('[WARNING] Unknown color scheme: %s' % color)
407
+ color = (255, 0, 0)
408
+ return color
409
+
410
+ def get_annotations(self, frame):
411
+ """
412
+ Get all annotations of frame
413
+ :param frame:
414
+ :return:
415
+ """
416
+ # self.annotations_timestamp = (self.vid.frame_number + self.annotations_offset) / self.vid.fps
417
+ self.annotations_timestamp = self.vid.frame_number / self.vid.fps
418
+ frame = self.video_annotations.get_frame(frame_num=self.vid.frame_number).show(image=frame,
419
+ height=frame.shape[0],
420
+ width=frame.shape[1],
421
+ with_text=self.show_label)
422
+ return frame
423
+
424
+ def toggle_play(self):
425
+ """
426
+ Toggle play pause
427
+ :return:
428
+ """
429
+ if self.playing:
430
+ # need to pause
431
+ self.playing = False
432
+ self.btn_toggle_play.config(text='Play ')
433
+ else:
434
+ # need to play
435
+ self.playing = True
436
+ self.btn_toggle_play.config(text='Pause')
437
+
438
+ def next_frame(self):
439
+ """
440
+ Get next frame
441
+ :return:
442
+ """
443
+ self.show_frame()
444
+
445
+ def prev_frame(self):
446
+ """
447
+ Get previous frame
448
+ :return:
449
+ """
450
+ self.show_frame(self.vid.frame_number - 1)
451
+
452
+ # def apply_offset_fix(self):
453
+ # """
454
+ # Apply offset to platform
455
+ # :return:
456
+ # """
457
+ # # delete annotations from platform
458
+ # for annotation in self.video_annotations.annotations:
459
+ # self.item.annotations.delete(annotations_id=annotation.id)
460
+ #
461
+ # # apply annotations fix
462
+ # new_annotations = list()
463
+ # for annotation in self.video_annotations.annotations:
464
+ # new_annotation = annotation.copy()
465
+ # for i in range(len(new_annotation['metadata']['snapshots_'])):
466
+ # new_annotation['metadata']['snapshots_'][i]['startTime'] -= (self.annotations_offset / self.vid.fps)
467
+ # new_annotation['metadata']['snapshots_'][i]['frameNum'] = int(
468
+ # new_annotation['metadata']['snapshots_'][i]['startTime'] * self.vid.fps)
469
+ # new_annotations.append(json.dumps(new_annotation))
470
+ #
471
+ # # upload annotations
472
+ # self.item.annotation.upload(annotation=[new_annotations])
473
+
474
+ def show_frame(self, frame_number=None):
475
+ """
476
+ Get a frame from the video source
477
+ :param frame_number:
478
+ :return:
479
+ """
480
+ try:
481
+ import cv2
482
+ except (ImportError, ModuleNotFoundError):
483
+ logger.error(
484
+ 'Import Error! Cant import cv2. Annotations operations will be limited. import manually and fix errors')
485
+ raise
486
+
487
+ try:
488
+ import tkinter
489
+ except ImportError:
490
+ logger.error(
491
+ 'Import Error! Cant import tkinter. Annotations operations will be limited. import manually and fix errors')
492
+ raise
493
+
494
+ try:
495
+ import PIL.ImageTk
496
+ import PIL.Image
497
+ except ImportError:
498
+ logger.error(
499
+ 'Import Error! Cant import PIL.ImageTk/PIL.ImageTk. Annotations operations will be limited. '
500
+ 'import manually and fix errors')
501
+ raise
502
+ ret, frame = self.vid.get_frame(frame_number)
503
+ if ret:
504
+ if self.show_annotations:
505
+ frame = self.get_annotations(frame)
506
+ if self.show_frame_num:
507
+ text = '%d - %d' % (self.vid.frame_number, np.round(self.annotations_timestamp * self.vid.fps))
508
+ frame = cv2.putText(frame,
509
+ text=text,
510
+ org=(100, 100),
511
+ color=(0, 0, 255),
512
+ fontFace=cv2.FONT_HERSHEY_DUPLEX,
513
+ fontScale=2,
514
+ thickness=3)
515
+
516
+ self.photo = PIL.ImageTk.PhotoImage(
517
+ image=PIL.Image.fromarray(frame).resize((self.window_width, self.window_height)),
518
+ master=self.canvas)
519
+ self.canvas.create_image(0, 0, image=self.photo, anchor=tkinter.NW)
520
+ # set timestamp
521
+ self.current_frame_text.configure(text='Frame number:\n%d' % self.vid.frame_number)
522
+ self.current_frame_text.grid(sticky="W", row=12, column=0, columnspan=10)
523
+ millis = int(1000 * self.vid.frame_number / self.vid.fps)
524
+ seconds = (millis / 1000) % 60
525
+ minutes = int((millis / (1000 * 60)) % 60)
526
+ hours = int((millis / (1000 * 60 * 60)) % 24)
527
+ self.frame_timestamp_text.configure(
528
+ text='Frame timestamp:\n{:02d}:{:02d}:{:.3f}'.format(hours, minutes, seconds))
529
+ self.frame_timestamp_text.grid(sticky="W", row=8, column=0, columnspan=10)
530
+
531
+ def update(self):
532
+ """
533
+ Playing the movie. Get frames at the FPS and show
534
+ :return:
535
+ """
536
+ tic = time.time()
537
+ if self.playing:
538
+ self.show_frame()
539
+ add_delay = np.maximum(1, self.delay - int(1000 * (time.time() - tic)))
540
+ self.window.after(ms=add_delay, func=self.update)
541
+
542
+
543
+ class VideoCapture:
544
+ """
545
+ Video class using cv2 to play video
546
+ """
547
+ def __init__(self, source=0):
548
+ try:
549
+ import cv2
550
+ except (ImportError, ModuleNotFoundError):
551
+ logger.error(
552
+ 'Import Error! Cant import cv2. Annotations operations will be limited. import manually and fix errors')
553
+ raise
554
+ # Open the video source
555
+ self.vid = cv2.VideoCapture(source)
556
+ if not self.vid.isOpened():
557
+ raise ValueError("Unable to open video source", source)
558
+
559
+ # Get video source width and height
560
+ self.width = self.vid.get(cv2.CAP_PROP_FRAME_WIDTH)
561
+ self.height = self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT)
562
+ self.fps = self.vid.get(cv2.CAP_PROP_FPS)
563
+ self.frame_number = self.vid.get(cv2.CAP_PROP_POS_FRAMES)
564
+
565
+ def get_frame(self, frame_number=None):
566
+ """
567
+ get the frame from video
568
+ :param frame_number:
569
+ :return:
570
+ """
571
+ try:
572
+ import cv2
573
+ except (ImportError, ModuleNotFoundError):
574
+ logger.error(
575
+ 'Import Error! Cant import cv2. Annotations operations will be limited. import manually and fix errors')
576
+ raise
577
+ if self.vid.isOpened():
578
+ if self.frame_number is None:
579
+ self.frame_number = self.vid.get(cv2.CAP_PROP_POS_FRAMES)
580
+ else:
581
+ self.frame_number += 1
582
+ if frame_number is not None:
583
+ self.frame_number = frame_number
584
+ self.vid.set(cv2.CAP_PROP_POS_FRAMES, frame_number)
585
+ ret, frame = self.vid.read()
586
+
587
+ if ret:
588
+ # Return a boolean success flag and the current frame converted to BGR
589
+ return ret, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
590
+ else:
591
+ return ret, None
592
+ else:
593
+ return False, None
594
+
595
+ # Release the video source when the object is destroyed
596
+ def __del__(self):
597
+ if self.vid.isOpened():
598
+ self.vid.release()