dtlpy 1.113.10__py3-none-any.whl → 1.114.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. dtlpy/__init__.py +488 -488
  2. dtlpy/__version__.py +1 -1
  3. dtlpy/assets/__init__.py +26 -26
  4. dtlpy/assets/__pycache__/__init__.cpython-38.pyc +0 -0
  5. dtlpy/assets/code_server/config.yaml +2 -2
  6. dtlpy/assets/code_server/installation.sh +24 -24
  7. dtlpy/assets/code_server/launch.json +13 -13
  8. dtlpy/assets/code_server/settings.json +2 -2
  9. dtlpy/assets/main.py +53 -53
  10. dtlpy/assets/main_partial.py +18 -18
  11. dtlpy/assets/mock.json +11 -11
  12. dtlpy/assets/model_adapter.py +83 -83
  13. dtlpy/assets/package.json +61 -61
  14. dtlpy/assets/package_catalog.json +29 -29
  15. dtlpy/assets/package_gitignore +307 -307
  16. dtlpy/assets/service_runners/__init__.py +33 -33
  17. dtlpy/assets/service_runners/converter.py +96 -96
  18. dtlpy/assets/service_runners/multi_method.py +49 -49
  19. dtlpy/assets/service_runners/multi_method_annotation.py +54 -54
  20. dtlpy/assets/service_runners/multi_method_dataset.py +55 -55
  21. dtlpy/assets/service_runners/multi_method_item.py +52 -52
  22. dtlpy/assets/service_runners/multi_method_json.py +52 -52
  23. dtlpy/assets/service_runners/single_method.py +37 -37
  24. dtlpy/assets/service_runners/single_method_annotation.py +43 -43
  25. dtlpy/assets/service_runners/single_method_dataset.py +43 -43
  26. dtlpy/assets/service_runners/single_method_item.py +41 -41
  27. dtlpy/assets/service_runners/single_method_json.py +42 -42
  28. dtlpy/assets/service_runners/single_method_multi_input.py +45 -45
  29. dtlpy/assets/voc_annotation_template.xml +23 -23
  30. dtlpy/caches/base_cache.py +32 -32
  31. dtlpy/caches/cache.py +473 -473
  32. dtlpy/caches/dl_cache.py +201 -201
  33. dtlpy/caches/filesystem_cache.py +89 -89
  34. dtlpy/caches/redis_cache.py +84 -84
  35. dtlpy/dlp/__init__.py +20 -20
  36. dtlpy/dlp/cli_utilities.py +367 -367
  37. dtlpy/dlp/command_executor.py +764 -764
  38. dtlpy/dlp/dlp +1 -1
  39. dtlpy/dlp/dlp.bat +1 -1
  40. dtlpy/dlp/dlp.py +128 -128
  41. dtlpy/dlp/parser.py +651 -651
  42. dtlpy/entities/__init__.py +83 -83
  43. dtlpy/entities/analytic.py +311 -311
  44. dtlpy/entities/annotation.py +1879 -1879
  45. dtlpy/entities/annotation_collection.py +699 -699
  46. dtlpy/entities/annotation_definitions/__init__.py +20 -20
  47. dtlpy/entities/annotation_definitions/base_annotation_definition.py +100 -100
  48. dtlpy/entities/annotation_definitions/box.py +195 -195
  49. dtlpy/entities/annotation_definitions/classification.py +67 -67
  50. dtlpy/entities/annotation_definitions/comparison.py +72 -72
  51. dtlpy/entities/annotation_definitions/cube.py +204 -204
  52. dtlpy/entities/annotation_definitions/cube_3d.py +149 -149
  53. dtlpy/entities/annotation_definitions/description.py +32 -32
  54. dtlpy/entities/annotation_definitions/ellipse.py +124 -124
  55. dtlpy/entities/annotation_definitions/free_text.py +62 -62
  56. dtlpy/entities/annotation_definitions/gis.py +69 -69
  57. dtlpy/entities/annotation_definitions/note.py +139 -139
  58. dtlpy/entities/annotation_definitions/point.py +117 -117
  59. dtlpy/entities/annotation_definitions/polygon.py +182 -182
  60. dtlpy/entities/annotation_definitions/polyline.py +111 -111
  61. dtlpy/entities/annotation_definitions/pose.py +92 -92
  62. dtlpy/entities/annotation_definitions/ref_image.py +86 -86
  63. dtlpy/entities/annotation_definitions/segmentation.py +240 -240
  64. dtlpy/entities/annotation_definitions/subtitle.py +34 -34
  65. dtlpy/entities/annotation_definitions/text.py +85 -85
  66. dtlpy/entities/annotation_definitions/undefined_annotation.py +74 -74
  67. dtlpy/entities/app.py +220 -220
  68. dtlpy/entities/app_module.py +107 -107
  69. dtlpy/entities/artifact.py +174 -174
  70. dtlpy/entities/assignment.py +399 -399
  71. dtlpy/entities/base_entity.py +214 -214
  72. dtlpy/entities/bot.py +113 -113
  73. dtlpy/entities/codebase.py +296 -296
  74. dtlpy/entities/collection.py +38 -38
  75. dtlpy/entities/command.py +169 -169
  76. dtlpy/entities/compute.py +442 -442
  77. dtlpy/entities/dataset.py +1285 -1285
  78. dtlpy/entities/directory_tree.py +44 -44
  79. dtlpy/entities/dpk.py +470 -470
  80. dtlpy/entities/driver.py +222 -222
  81. dtlpy/entities/execution.py +397 -397
  82. dtlpy/entities/feature.py +124 -124
  83. dtlpy/entities/feature_set.py +145 -145
  84. dtlpy/entities/filters.py +641 -641
  85. dtlpy/entities/gis_item.py +107 -107
  86. dtlpy/entities/integration.py +184 -184
  87. dtlpy/entities/item.py +953 -953
  88. dtlpy/entities/label.py +123 -123
  89. dtlpy/entities/links.py +85 -85
  90. dtlpy/entities/message.py +175 -175
  91. dtlpy/entities/model.py +694 -691
  92. dtlpy/entities/node.py +1005 -1005
  93. dtlpy/entities/ontology.py +803 -803
  94. dtlpy/entities/organization.py +287 -287
  95. dtlpy/entities/package.py +657 -657
  96. dtlpy/entities/package_defaults.py +5 -5
  97. dtlpy/entities/package_function.py +185 -185
  98. dtlpy/entities/package_module.py +113 -113
  99. dtlpy/entities/package_slot.py +118 -118
  100. dtlpy/entities/paged_entities.py +290 -267
  101. dtlpy/entities/pipeline.py +593 -593
  102. dtlpy/entities/pipeline_execution.py +279 -279
  103. dtlpy/entities/project.py +394 -394
  104. dtlpy/entities/prompt_item.py +499 -499
  105. dtlpy/entities/recipe.py +301 -301
  106. dtlpy/entities/reflect_dict.py +102 -102
  107. dtlpy/entities/resource_execution.py +138 -138
  108. dtlpy/entities/service.py +958 -958
  109. dtlpy/entities/service_driver.py +117 -117
  110. dtlpy/entities/setting.py +294 -294
  111. dtlpy/entities/task.py +491 -491
  112. dtlpy/entities/time_series.py +143 -143
  113. dtlpy/entities/trigger.py +426 -426
  114. dtlpy/entities/user.py +118 -118
  115. dtlpy/entities/webhook.py +124 -124
  116. dtlpy/examples/__init__.py +19 -19
  117. dtlpy/examples/add_labels.py +135 -135
  118. dtlpy/examples/add_metadata_to_item.py +21 -21
  119. dtlpy/examples/annotate_items_using_model.py +65 -65
  120. dtlpy/examples/annotate_video_using_model_and_tracker.py +75 -75
  121. dtlpy/examples/annotations_convert_to_voc.py +9 -9
  122. dtlpy/examples/annotations_convert_to_yolo.py +9 -9
  123. dtlpy/examples/convert_annotation_types.py +51 -51
  124. dtlpy/examples/converter.py +143 -143
  125. dtlpy/examples/copy_annotations.py +22 -22
  126. dtlpy/examples/copy_folder.py +31 -31
  127. dtlpy/examples/create_annotations.py +51 -51
  128. dtlpy/examples/create_video_annotations.py +83 -83
  129. dtlpy/examples/delete_annotations.py +26 -26
  130. dtlpy/examples/filters.py +113 -113
  131. dtlpy/examples/move_item.py +23 -23
  132. dtlpy/examples/play_video_annotation.py +13 -13
  133. dtlpy/examples/show_item_and_mask.py +53 -53
  134. dtlpy/examples/triggers.py +49 -49
  135. dtlpy/examples/upload_batch_of_items.py +20 -20
  136. dtlpy/examples/upload_items_and_custom_format_annotations.py +55 -55
  137. dtlpy/examples/upload_items_with_modalities.py +43 -43
  138. dtlpy/examples/upload_segmentation_annotations_from_mask_image.py +44 -44
  139. dtlpy/examples/upload_yolo_format_annotations.py +70 -70
  140. dtlpy/exceptions.py +125 -125
  141. dtlpy/miscellaneous/__init__.py +20 -20
  142. dtlpy/miscellaneous/dict_differ.py +95 -95
  143. dtlpy/miscellaneous/git_utils.py +217 -217
  144. dtlpy/miscellaneous/json_utils.py +14 -14
  145. dtlpy/miscellaneous/list_print.py +105 -105
  146. dtlpy/miscellaneous/zipping.py +130 -130
  147. dtlpy/ml/__init__.py +20 -20
  148. dtlpy/ml/base_feature_extractor_adapter.py +27 -27
  149. dtlpy/ml/base_model_adapter.py +945 -940
  150. dtlpy/ml/metrics.py +461 -461
  151. dtlpy/ml/predictions_utils.py +274 -274
  152. dtlpy/ml/summary_writer.py +57 -57
  153. dtlpy/ml/train_utils.py +60 -60
  154. dtlpy/new_instance.py +252 -252
  155. dtlpy/repositories/__init__.py +56 -56
  156. dtlpy/repositories/analytics.py +85 -85
  157. dtlpy/repositories/annotations.py +916 -916
  158. dtlpy/repositories/apps.py +383 -383
  159. dtlpy/repositories/artifacts.py +452 -452
  160. dtlpy/repositories/assignments.py +599 -599
  161. dtlpy/repositories/bots.py +213 -213
  162. dtlpy/repositories/codebases.py +559 -559
  163. dtlpy/repositories/collections.py +332 -348
  164. dtlpy/repositories/commands.py +158 -158
  165. dtlpy/repositories/compositions.py +61 -61
  166. dtlpy/repositories/computes.py +434 -406
  167. dtlpy/repositories/datasets.py +1291 -1291
  168. dtlpy/repositories/downloader.py +895 -895
  169. dtlpy/repositories/dpks.py +433 -433
  170. dtlpy/repositories/drivers.py +266 -266
  171. dtlpy/repositories/executions.py +817 -817
  172. dtlpy/repositories/feature_sets.py +226 -226
  173. dtlpy/repositories/features.py +238 -238
  174. dtlpy/repositories/integrations.py +484 -484
  175. dtlpy/repositories/items.py +909 -915
  176. dtlpy/repositories/messages.py +94 -94
  177. dtlpy/repositories/models.py +877 -867
  178. dtlpy/repositories/nodes.py +80 -80
  179. dtlpy/repositories/ontologies.py +511 -511
  180. dtlpy/repositories/organizations.py +525 -525
  181. dtlpy/repositories/packages.py +1941 -1941
  182. dtlpy/repositories/pipeline_executions.py +448 -448
  183. dtlpy/repositories/pipelines.py +642 -642
  184. dtlpy/repositories/projects.py +539 -539
  185. dtlpy/repositories/recipes.py +399 -399
  186. dtlpy/repositories/resource_executions.py +137 -137
  187. dtlpy/repositories/schema.py +120 -120
  188. dtlpy/repositories/service_drivers.py +213 -213
  189. dtlpy/repositories/services.py +1704 -1704
  190. dtlpy/repositories/settings.py +339 -339
  191. dtlpy/repositories/tasks.py +1124 -1124
  192. dtlpy/repositories/times_series.py +278 -278
  193. dtlpy/repositories/triggers.py +536 -536
  194. dtlpy/repositories/upload_element.py +257 -257
  195. dtlpy/repositories/uploader.py +651 -651
  196. dtlpy/repositories/webhooks.py +249 -249
  197. dtlpy/services/__init__.py +22 -22
  198. dtlpy/services/aihttp_retry.py +131 -131
  199. dtlpy/services/api_client.py +1782 -1782
  200. dtlpy/services/api_reference.py +40 -40
  201. dtlpy/services/async_utils.py +133 -133
  202. dtlpy/services/calls_counter.py +44 -44
  203. dtlpy/services/check_sdk.py +68 -68
  204. dtlpy/services/cookie.py +115 -115
  205. dtlpy/services/create_logger.py +156 -156
  206. dtlpy/services/events.py +84 -84
  207. dtlpy/services/logins.py +235 -235
  208. dtlpy/services/reporter.py +256 -256
  209. dtlpy/services/service_defaults.py +91 -91
  210. dtlpy/utilities/__init__.py +20 -20
  211. dtlpy/utilities/annotations/__init__.py +16 -16
  212. dtlpy/utilities/annotations/annotation_converters.py +269 -269
  213. dtlpy/utilities/base_package_runner.py +264 -264
  214. dtlpy/utilities/converter.py +1650 -1650
  215. dtlpy/utilities/dataset_generators/__init__.py +1 -1
  216. dtlpy/utilities/dataset_generators/dataset_generator.py +670 -670
  217. dtlpy/utilities/dataset_generators/dataset_generator_tensorflow.py +23 -23
  218. dtlpy/utilities/dataset_generators/dataset_generator_torch.py +21 -21
  219. dtlpy/utilities/local_development/__init__.py +1 -1
  220. dtlpy/utilities/local_development/local_session.py +179 -179
  221. dtlpy/utilities/reports/__init__.py +2 -2
  222. dtlpy/utilities/reports/figures.py +343 -343
  223. dtlpy/utilities/reports/report.py +71 -71
  224. dtlpy/utilities/videos/__init__.py +17 -17
  225. dtlpy/utilities/videos/video_player.py +598 -598
  226. dtlpy/utilities/videos/videos.py +470 -470
  227. {dtlpy-1.113.10.data → dtlpy-1.114.13.data}/scripts/dlp +1 -1
  228. dtlpy-1.114.13.data/scripts/dlp.bat +2 -0
  229. {dtlpy-1.113.10.data → dtlpy-1.114.13.data}/scripts/dlp.py +128 -128
  230. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/LICENSE +200 -200
  231. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/METADATA +172 -172
  232. dtlpy-1.114.13.dist-info/RECORD +240 -0
  233. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/WHEEL +1 -1
  234. tests/features/environment.py +551 -550
  235. dtlpy-1.113.10.data/scripts/dlp.bat +0 -2
  236. dtlpy-1.113.10.dist-info/RECORD +0 -244
  237. tests/assets/__init__.py +0 -0
  238. tests/assets/models_flow/__init__.py +0 -0
  239. tests/assets/models_flow/failedmain.py +0 -52
  240. tests/assets/models_flow/main.py +0 -62
  241. tests/assets/models_flow/main_model.py +0 -54
  242. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/entry_points.txt +0 -0
  243. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/top_level.txt +0 -0
@@ -1,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()