vidformer 0.10.1__py3-none-any.whl → 0.12.0__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.
- vidformer/__init__.py +331 -45
- vidformer/cv2/__init__.py +222 -85
- vidformer/supervision/__init__.py +97 -6
- {vidformer-0.10.1.dist-info → vidformer-0.12.0.dist-info}/METADATA +2 -2
- vidformer-0.12.0.dist-info/RECORD +6 -0
- {vidformer-0.10.1.dist-info → vidformer-0.12.0.dist-info}/WHEEL +1 -1
- vidformer-0.10.1.dist-info/RECORD +0 -6
vidformer/__init__.py
CHANGED
@@ -9,27 +9,28 @@ vidformer-py is a Python 🐍 interface for [vidformer](https://github.com/ixlab
|
|
9
9
|
* [🧑💻 Source Code](https://github.com/ixlab/vidformer/tree/main/vidformer-py/)
|
10
10
|
"""
|
11
11
|
|
12
|
-
__version__ = "0.
|
12
|
+
__version__ = "0.12.0"
|
13
13
|
|
14
14
|
|
15
|
-
import
|
16
|
-
|
17
|
-
import random
|
18
|
-
import time
|
15
|
+
import base64
|
16
|
+
import gzip
|
19
17
|
import json
|
20
|
-
import socket
|
21
|
-
import os
|
22
18
|
import multiprocessing
|
23
|
-
import
|
24
|
-
import
|
25
|
-
import gzip
|
26
|
-
import base64
|
19
|
+
import os
|
20
|
+
import random
|
27
21
|
import re
|
22
|
+
import socket
|
23
|
+
import struct
|
24
|
+
import subprocess
|
25
|
+
import threading
|
26
|
+
import time
|
27
|
+
import uuid
|
28
|
+
from fractions import Fraction
|
28
29
|
from urllib.parse import urlparse
|
29
30
|
|
30
|
-
import requests
|
31
31
|
import msgpack
|
32
32
|
import numpy as np
|
33
|
+
import requests
|
33
34
|
|
34
35
|
_in_notebook = False
|
35
36
|
try:
|
@@ -104,14 +105,14 @@ def _play(namespace, hls_video_url, hls_js_url, method="html", status_url=None):
|
|
104
105
|
<script src="{hls_js_url}"></script>
|
105
106
|
</head>
|
106
107
|
<body>
|
107
|
-
<div id="container"></div>
|
108
|
+
<div id="container-{namespace}"></div>
|
108
109
|
<script>
|
109
110
|
var statusUrl = '{status_url}';
|
110
111
|
var videoSrc = '{hls_video_url}';
|
111
112
|
var videoNamespace = '{namespace}';
|
112
113
|
|
113
114
|
function showWaiting() {{
|
114
|
-
document.getElementById('container').textContent = 'Waiting...';
|
115
|
+
document.getElementById('container-{namespace}').textContent = 'Waiting...';
|
115
116
|
pollStatus();
|
116
117
|
}}
|
117
118
|
|
@@ -121,7 +122,7 @@ def _play(namespace, hls_video_url, hls_js_url, method="html", status_url=None):
|
|
121
122
|
.then(r => r.json())
|
122
123
|
.then(res => {{
|
123
124
|
if (res.ready) {{
|
124
|
-
document.getElementById('container').textContent = '';
|
125
|
+
document.getElementById('container-{namespace}').textContent = '';
|
125
126
|
attachHls();
|
126
127
|
}} else {{
|
127
128
|
pollStatus();
|
@@ -135,7 +136,7 @@ def _play(namespace, hls_video_url, hls_js_url, method="html", status_url=None):
|
|
135
136
|
}}
|
136
137
|
|
137
138
|
function attachHls() {{
|
138
|
-
var container = document.getElementById('container');
|
139
|
+
var container = document.getElementById('container-{namespace}');
|
139
140
|
container.textContent = '';
|
140
141
|
var video = document.createElement('video');
|
141
142
|
video.id = 'video-' + videoNamespace;
|
@@ -182,6 +183,214 @@ def _play(namespace, hls_video_url, hls_js_url, method="html", status_url=None):
|
|
182
183
|
raise ValueError("Invalid method")
|
183
184
|
|
184
185
|
|
186
|
+
def _feb_expr_coded_as_scalar(expr) -> bool:
|
187
|
+
if type(expr) is tuple:
|
188
|
+
expr = list(expr)
|
189
|
+
if type(expr) is FilterExpr:
|
190
|
+
return False
|
191
|
+
if type(expr) is list:
|
192
|
+
if len(expr) > 3:
|
193
|
+
return False
|
194
|
+
else:
|
195
|
+
return all([type(x) is int and x >= -(2**15) and x < 2**15 for x in expr])
|
196
|
+
else:
|
197
|
+
assert type(expr) in [int, float, str, bytes, SourceExpr, bool, list]
|
198
|
+
return True
|
199
|
+
|
200
|
+
|
201
|
+
class _FrameExpressionBlock:
|
202
|
+
def __init__(self):
|
203
|
+
self._functions = []
|
204
|
+
self._literals = []
|
205
|
+
self._sources = []
|
206
|
+
self._kwarg_keys = []
|
207
|
+
self._source_fracs = []
|
208
|
+
self._exprs = []
|
209
|
+
self._frame_exprs = []
|
210
|
+
|
211
|
+
def __len__(self):
|
212
|
+
return len(self._frame_exprs)
|
213
|
+
|
214
|
+
def insert_expr(self, expr):
|
215
|
+
if type(expr) is SourceExpr or type(expr) is FilterExpr:
|
216
|
+
return self.insert_frame_expr(expr)
|
217
|
+
else:
|
218
|
+
return self.insert_data_expr(expr)
|
219
|
+
|
220
|
+
def insert_data_expr(self, data):
|
221
|
+
if type(data) is tuple:
|
222
|
+
data = list(data)
|
223
|
+
if type(data) is bool:
|
224
|
+
self._exprs.append(0x01000000_00000000 | int(data))
|
225
|
+
return len(self._exprs) - 1
|
226
|
+
elif type(data) is int:
|
227
|
+
if data >= -(2**31) and data < 2**31:
|
228
|
+
self._exprs.append(data & 0xFFFFFFFF)
|
229
|
+
else:
|
230
|
+
self._literals.append(_json_arg(data, skip_data_anot=True))
|
231
|
+
self._exprs.append(0x40000000_00000000 | len(self._literals) - 1)
|
232
|
+
return len(self._exprs) - 1
|
233
|
+
elif type(data) is float:
|
234
|
+
self._exprs.append(
|
235
|
+
0x02000000_00000000 | int.from_bytes(struct.pack("f", data)[::-1])
|
236
|
+
)
|
237
|
+
elif type(data) is str:
|
238
|
+
self._literals.append(_json_arg(data, skip_data_anot=True))
|
239
|
+
self._exprs.append(0x40000000_00000000 | len(self._literals) - 1)
|
240
|
+
elif type(data) is bytes:
|
241
|
+
self._literals.append(_json_arg(data, skip_data_anot=True))
|
242
|
+
self._exprs.append(0x40000000_00000000 | len(self._literals) - 1)
|
243
|
+
elif type(data) is list:
|
244
|
+
if len(data) == 0:
|
245
|
+
self._exprs.append(0x03000000_00000000)
|
246
|
+
return len(self._exprs) - 1
|
247
|
+
if (
|
248
|
+
len(data) == 1
|
249
|
+
and type(data[0]) is int
|
250
|
+
and data[0] >= -(2**15)
|
251
|
+
and data[0] < 2**15
|
252
|
+
):
|
253
|
+
self._exprs.append(0x04000000_00000000 | (data[0] & 0xFFFF))
|
254
|
+
return len(self._exprs) - 1
|
255
|
+
if (
|
256
|
+
len(data) == 2
|
257
|
+
and type(data[0]) is int
|
258
|
+
and data[0] >= -(2**15)
|
259
|
+
and data[0] < 2**15
|
260
|
+
and type(data[1]) is int
|
261
|
+
and data[1] >= -(2**15)
|
262
|
+
and data[1] < 2**15
|
263
|
+
):
|
264
|
+
self._exprs.append(
|
265
|
+
0x05000000_00000000
|
266
|
+
| ((data[0] & 0xFFFF) << 16)
|
267
|
+
| (data[1] & 0xFFFF)
|
268
|
+
)
|
269
|
+
return len(self._exprs) - 1
|
270
|
+
if (
|
271
|
+
len(data) == 3
|
272
|
+
and type(data[0]) is int
|
273
|
+
and data[0] >= -(2**15)
|
274
|
+
and data[0] < 2**15
|
275
|
+
and type(data[1]) is int
|
276
|
+
and data[1] >= -(2**15)
|
277
|
+
and data[1] < 2**15
|
278
|
+
and type(data[2]) is int
|
279
|
+
and data[2] >= -(2**15)
|
280
|
+
and data[2] < 2**15
|
281
|
+
):
|
282
|
+
self._exprs.append(
|
283
|
+
0x06000000_00000000
|
284
|
+
| ((data[0] & 0xFFFF) << 32)
|
285
|
+
| ((data[1] & 0xFFFF) << 16)
|
286
|
+
| (data[2] & 0xFFFF)
|
287
|
+
)
|
288
|
+
return len(self._exprs) - 1
|
289
|
+
out = len(self._exprs)
|
290
|
+
member_idxs = []
|
291
|
+
for member in data:
|
292
|
+
if _feb_expr_coded_as_scalar(member):
|
293
|
+
member_idxs.append(None)
|
294
|
+
else:
|
295
|
+
member_idxs.append(self.insert_data_expr(member))
|
296
|
+
|
297
|
+
self._exprs.append(0x42000000_00000000 | len(data))
|
298
|
+
|
299
|
+
for i in range(len(data)):
|
300
|
+
if member_idxs[i] is None:
|
301
|
+
self.insert_data_expr(data[i])
|
302
|
+
else:
|
303
|
+
self._exprs.append(0x45000000_00000000 | member_idxs[i])
|
304
|
+
|
305
|
+
return out
|
306
|
+
else:
|
307
|
+
raise Exception("Invalid data type")
|
308
|
+
|
309
|
+
def insert_frame_expr(self, frame):
|
310
|
+
if type(frame) is SourceExpr:
|
311
|
+
source = frame._source._name
|
312
|
+
if source in self._sources:
|
313
|
+
source_idx = self._sources.index(source)
|
314
|
+
else:
|
315
|
+
source_idx = len(self._sources)
|
316
|
+
self._sources.append(source)
|
317
|
+
if frame._is_iloc:
|
318
|
+
self._exprs.append(
|
319
|
+
0x43000000_00000000 | (source_idx << 32) | frame._idx
|
320
|
+
)
|
321
|
+
else:
|
322
|
+
idx = len(self._source_fracs) // 2
|
323
|
+
self._source_fracs.append(frame._idx.numerator)
|
324
|
+
self._source_fracs.append(frame._idx.denominator)
|
325
|
+
self._exprs.append(0x44000000_00000000 | (source_idx << 32) | idx)
|
326
|
+
return len(self._exprs) - 1
|
327
|
+
elif type(frame) is FilterExpr:
|
328
|
+
func = frame._filter._func
|
329
|
+
if func in self._functions:
|
330
|
+
func_idx = self._functions.index(func)
|
331
|
+
else:
|
332
|
+
func_idx = len(self._functions)
|
333
|
+
self._functions.append(func)
|
334
|
+
len_args = len(frame._args)
|
335
|
+
len_kwargs = len(frame._kwargs)
|
336
|
+
|
337
|
+
arg_idxs = []
|
338
|
+
for arg in frame._args:
|
339
|
+
if _feb_expr_coded_as_scalar(arg):
|
340
|
+
arg_idxs.append(None)
|
341
|
+
else:
|
342
|
+
arg_idxs.append(self.insert_expr(arg))
|
343
|
+
kwarg_idxs = {}
|
344
|
+
for k, v in frame._kwargs.items():
|
345
|
+
if _feb_expr_coded_as_scalar(v):
|
346
|
+
kwarg_idxs[k] = None
|
347
|
+
else:
|
348
|
+
kwarg_idxs[k] = self.insert_expr(v)
|
349
|
+
|
350
|
+
out_idx = len(self._exprs)
|
351
|
+
self._exprs.append(
|
352
|
+
0x41000000_00000000 | (len_args << 24) | (len_kwargs << 16) | func_idx
|
353
|
+
)
|
354
|
+
for i in range(len_args):
|
355
|
+
if arg_idxs[i] is None:
|
356
|
+
# It's a scalar
|
357
|
+
self.insert_expr(frame._args[i])
|
358
|
+
else:
|
359
|
+
# It's an expression pointer
|
360
|
+
self._exprs.append(0x45000000_00000000 | arg_idxs[i])
|
361
|
+
for k, v in frame._kwargs.items():
|
362
|
+
if k in self._kwarg_keys:
|
363
|
+
k_idx = self._kwarg_keys.index(k)
|
364
|
+
else:
|
365
|
+
k_idx = len(self._kwarg_keys)
|
366
|
+
self._kwarg_keys.append(k)
|
367
|
+
self._exprs.append(0x46000000_00000000 | k_idx)
|
368
|
+
if kwarg_idxs[k] is None:
|
369
|
+
# It's a scalar
|
370
|
+
self.insert_expr(v)
|
371
|
+
else:
|
372
|
+
# It's an expression pointer
|
373
|
+
self._exprs.append(0x45000000_00000000 | kwarg_idxs[k])
|
374
|
+
return out_idx
|
375
|
+
else:
|
376
|
+
raise Exception("Invalid frame type")
|
377
|
+
|
378
|
+
def insert_frame(self, frame):
|
379
|
+
idx = self.insert_frame_expr(frame)
|
380
|
+
self._frame_exprs.append(idx)
|
381
|
+
|
382
|
+
def as_dict(self):
|
383
|
+
return {
|
384
|
+
"functions": self._functions,
|
385
|
+
"literals": self._literals,
|
386
|
+
"sources": self._sources,
|
387
|
+
"kwarg_keys": self._kwarg_keys,
|
388
|
+
"source_fracs": self._source_fracs,
|
389
|
+
"exprs": self._exprs,
|
390
|
+
"frame_exprs": self._frame_exprs,
|
391
|
+
}
|
392
|
+
|
393
|
+
|
185
394
|
class IgniSource:
|
186
395
|
def __init__(self, id: str, src):
|
187
396
|
self._name = id
|
@@ -245,7 +454,9 @@ class IgniServer:
|
|
245
454
|
self._endpoint = endpoint
|
246
455
|
|
247
456
|
self._api_key = api_key
|
248
|
-
|
457
|
+
self._session = requests.Session()
|
458
|
+
self._session.headers.update({"Authorization": f"Bearer {self._api_key}"})
|
459
|
+
response = self._session.get(
|
249
460
|
f"{self._endpoint}/auth",
|
250
461
|
headers={"Authorization": f"Bearer {self._api_key}"},
|
251
462
|
)
|
@@ -256,7 +467,7 @@ class IgniServer:
|
|
256
467
|
|
257
468
|
def get_source(self, id: str) -> IgniSource:
|
258
469
|
assert type(id) is str
|
259
|
-
response =
|
470
|
+
response = self._session.get(
|
260
471
|
f"{self._endpoint}/source/{id}",
|
261
472
|
headers={"Authorization": f"Bearer {self._api_key}"},
|
262
473
|
)
|
@@ -266,7 +477,7 @@ class IgniServer:
|
|
266
477
|
return IgniSource(response["id"], response)
|
267
478
|
|
268
479
|
def list_sources(self) -> list[str]:
|
269
|
-
response =
|
480
|
+
response = self._session.get(
|
270
481
|
f"{self._endpoint}/source",
|
271
482
|
headers={"Authorization": f"Bearer {self._api_key}"},
|
272
483
|
)
|
@@ -277,7 +488,7 @@ class IgniServer:
|
|
277
488
|
|
278
489
|
def delete_source(self, id: str):
|
279
490
|
assert type(id) is str
|
280
|
-
response =
|
491
|
+
response = self._session.delete(
|
281
492
|
f"{self._endpoint}/source/{id}",
|
282
493
|
headers={"Authorization": f"Bearer {self._api_key}"},
|
283
494
|
)
|
@@ -302,7 +513,7 @@ class IgniServer:
|
|
302
513
|
"storage_service": storage_service,
|
303
514
|
"storage_config": storage_config,
|
304
515
|
}
|
305
|
-
response =
|
516
|
+
response = self._session.post(
|
306
517
|
f"{self._endpoint}/source/search",
|
307
518
|
json=req,
|
308
519
|
headers={"Authorization": f"Bearer {self._api_key}"},
|
@@ -328,7 +539,7 @@ class IgniServer:
|
|
328
539
|
"storage_service": storage_service,
|
329
540
|
"storage_config": storage_config,
|
330
541
|
}
|
331
|
-
response =
|
542
|
+
response = self._session.post(
|
332
543
|
f"{self._endpoint}/source",
|
333
544
|
json=req,
|
334
545
|
headers={"Authorization": f"Bearer {self._api_key}"},
|
@@ -354,7 +565,7 @@ class IgniServer:
|
|
354
565
|
|
355
566
|
def get_spec(self, id: str) -> IgniSpec:
|
356
567
|
assert type(id) is str
|
357
|
-
response =
|
568
|
+
response = self._session.get(
|
358
569
|
f"{self._endpoint}/spec/{id}",
|
359
570
|
headers={"Authorization": f"Bearer {self._api_key}"},
|
360
571
|
)
|
@@ -364,7 +575,7 @@ class IgniServer:
|
|
364
575
|
return IgniSpec(response["id"], response)
|
365
576
|
|
366
577
|
def list_specs(self) -> list[str]:
|
367
|
-
response =
|
578
|
+
response = self._session.get(
|
368
579
|
f"{self._endpoint}/spec",
|
369
580
|
headers={"Authorization": f"Bearer {self._api_key}"},
|
370
581
|
)
|
@@ -382,6 +593,7 @@ class IgniServer:
|
|
382
593
|
frame_rate,
|
383
594
|
ready_hook=None,
|
384
595
|
steer_hook=None,
|
596
|
+
ttl=None,
|
385
597
|
) -> IgniSpec:
|
386
598
|
assert type(width) is int
|
387
599
|
assert type(height) is int
|
@@ -390,6 +602,7 @@ class IgniServer:
|
|
390
602
|
assert type(frame_rate) is Fraction
|
391
603
|
assert type(ready_hook) is str or ready_hook is None
|
392
604
|
assert type(steer_hook) is str or steer_hook is None
|
605
|
+
assert ttl is None or type(ttl) is int
|
393
606
|
|
394
607
|
req = {
|
395
608
|
"width": width,
|
@@ -402,8 +615,9 @@ class IgniServer:
|
|
402
615
|
"frame_rate": [frame_rate.numerator, frame_rate.denominator],
|
403
616
|
"ready_hook": ready_hook,
|
404
617
|
"steer_hook": steer_hook,
|
618
|
+
"ttl": ttl,
|
405
619
|
}
|
406
|
-
response =
|
620
|
+
response = self._session.post(
|
407
621
|
f"{self._endpoint}/spec",
|
408
622
|
json=req,
|
409
623
|
headers={"Authorization": f"Bearer {self._api_key}"},
|
@@ -416,7 +630,7 @@ class IgniServer:
|
|
416
630
|
|
417
631
|
def delete_spec(self, id: str):
|
418
632
|
assert type(id) is str
|
419
|
-
response =
|
633
|
+
response = self._session.delete(
|
420
634
|
f"{self._endpoint}/spec/{id}",
|
421
635
|
headers={"Authorization": f"Bearer {self._api_key}"},
|
422
636
|
)
|
@@ -453,7 +667,7 @@ class IgniServer:
|
|
453
667
|
"frames": req_frames,
|
454
668
|
"terminal": terminal,
|
455
669
|
}
|
456
|
-
response =
|
670
|
+
response = self._session.post(
|
457
671
|
f"{self._endpoint}/spec/{spec_id}/part",
|
458
672
|
json=req,
|
459
673
|
headers={"Authorization": f"Bearer {self._api_key}"},
|
@@ -463,6 +677,84 @@ class IgniServer:
|
|
463
677
|
response = response.json()
|
464
678
|
assert response["status"] == "ok"
|
465
679
|
|
680
|
+
def push_spec_part_block(
|
681
|
+
self, spec_id: str, pos, blocks, terminal, compression="gzip"
|
682
|
+
):
|
683
|
+
if type(spec_id) is IgniSpec:
|
684
|
+
spec_id = spec_id._id
|
685
|
+
assert type(spec_id) is str
|
686
|
+
assert type(pos) is int
|
687
|
+
assert type(blocks) is list
|
688
|
+
assert type(terminal) is bool
|
689
|
+
assert compression is None or compression == "gzip"
|
690
|
+
|
691
|
+
req_blocks = []
|
692
|
+
for block in blocks:
|
693
|
+
assert type(block) is _FrameExpressionBlock
|
694
|
+
block_body = block.as_dict()
|
695
|
+
block_frames = len(block_body["frame_exprs"])
|
696
|
+
block_body = json.dumps(block_body).encode("utf-8")
|
697
|
+
if compression == "gzip":
|
698
|
+
block_body = gzip.compress(block_body, 1)
|
699
|
+
block_body = base64.b64encode(block_body).decode("utf-8")
|
700
|
+
req_blocks.append(
|
701
|
+
{
|
702
|
+
"frames": block_frames,
|
703
|
+
"compression": compression,
|
704
|
+
"body": block_body,
|
705
|
+
}
|
706
|
+
)
|
707
|
+
|
708
|
+
req = {
|
709
|
+
"pos": pos,
|
710
|
+
"terminal": terminal,
|
711
|
+
"blocks": req_blocks,
|
712
|
+
}
|
713
|
+
response = self._session.post(
|
714
|
+
f"{self._endpoint}/spec/{spec_id}/part_block",
|
715
|
+
json=req,
|
716
|
+
headers={"Authorization": f"Bearer {self._api_key}"},
|
717
|
+
)
|
718
|
+
if not response.ok:
|
719
|
+
raise Exception(response.text)
|
720
|
+
response = response.json()
|
721
|
+
assert response["status"] == "ok"
|
722
|
+
|
723
|
+
def frame(self, width, height, pix_fmt, frame_expr, compression="gzip"):
|
724
|
+
assert type(frame_expr) is FilterExpr or type(frame_expr) is SourceExpr
|
725
|
+
assert compression is None or compression in ["gzip"]
|
726
|
+
feb = _FrameExpressionBlock()
|
727
|
+
feb.insert_frame(frame_expr)
|
728
|
+
feb_body = feb.as_dict()
|
729
|
+
|
730
|
+
feb_body = json.dumps(feb_body).encode("utf-8")
|
731
|
+
if compression == "gzip":
|
732
|
+
feb_body = gzip.compress(feb_body, 1)
|
733
|
+
feb_body = base64.b64encode(feb_body).decode("utf-8")
|
734
|
+
req = {
|
735
|
+
"width": width,
|
736
|
+
"height": height,
|
737
|
+
"pix_fmt": pix_fmt,
|
738
|
+
"compression": compression,
|
739
|
+
"block": {
|
740
|
+
"frames": 1,
|
741
|
+
"compression": compression,
|
742
|
+
"body": feb_body,
|
743
|
+
},
|
744
|
+
}
|
745
|
+
response = self._session.post(
|
746
|
+
f"{self._endpoint}/frame",
|
747
|
+
json=req,
|
748
|
+
headers={"Authorization": f"Bearer {self._api_key}"},
|
749
|
+
)
|
750
|
+
if not response.ok:
|
751
|
+
raise Exception(response.text)
|
752
|
+
response_body = response.content
|
753
|
+
assert type(response_body) is bytes
|
754
|
+
if compression == "gzip":
|
755
|
+
response_body = gzip.decompress(response_body)
|
756
|
+
return response_body
|
757
|
+
|
466
758
|
|
467
759
|
class YrdenSpec:
|
468
760
|
"""
|
@@ -543,12 +835,11 @@ class YrdenSpec:
|
|
543
835
|
}
|
544
836
|
for k, v in filters.items()
|
545
837
|
}
|
546
|
-
arrays = []
|
547
838
|
|
548
839
|
if verbose:
|
549
840
|
print(f"Sending to server. Spec is {len(spec_obj_json_gzip_b64)} bytes")
|
550
841
|
|
551
|
-
resp = server._new(spec_obj_json_gzip_b64, sources, filters,
|
842
|
+
resp = server._new(spec_obj_json_gzip_b64, sources, filters, self._fmt)
|
552
843
|
hls_video_url = resp["stream_url"]
|
553
844
|
hls_player_url = resp["player_url"]
|
554
845
|
namespace = resp["namespace"]
|
@@ -616,9 +907,7 @@ class YrdenSpec:
|
|
616
907
|
}
|
617
908
|
for k, v in filters.items()
|
618
909
|
}
|
619
|
-
|
620
|
-
|
621
|
-
resp = server._new(spec_obj_json_gzip_b64, sources, filters, arrays, self._fmt)
|
910
|
+
resp = server._new(spec_obj_json_gzip_b64, sources, filters, self._fmt)
|
622
911
|
namespace = resp["namespace"]
|
623
912
|
return YrdenLoader(server, namespace, self._domain)
|
624
913
|
|
@@ -652,14 +941,12 @@ class YrdenSpec:
|
|
652
941
|
}
|
653
942
|
for k, v in filters.items()
|
654
943
|
}
|
655
|
-
arrays = []
|
656
944
|
|
657
945
|
resp = server._export(
|
658
946
|
pth,
|
659
947
|
spec_obj_json_gzip_b64,
|
660
948
|
sources,
|
661
949
|
filters,
|
662
|
-
arrays,
|
663
950
|
self._fmt,
|
664
951
|
encoder,
|
665
952
|
encoder_opts,
|
@@ -692,12 +979,11 @@ class YrdenSpec:
|
|
692
979
|
}
|
693
980
|
for k, v in filters.items()
|
694
981
|
}
|
695
|
-
arrays = []
|
696
982
|
end_t = time.time()
|
697
983
|
out["vrod_create_spec"] = end_t - start_t
|
698
984
|
|
699
985
|
start = time.time()
|
700
|
-
resp = server._new(pth, sources, filters,
|
986
|
+
resp = server._new(pth, sources, filters, self._fmt)
|
701
987
|
end = time.time()
|
702
988
|
out["vrod_register"] = end - start
|
703
989
|
|
@@ -735,12 +1021,11 @@ class YrdenSpec:
|
|
735
1021
|
}
|
736
1022
|
for k, v in filters.items()
|
737
1023
|
}
|
738
|
-
arrays = []
|
739
1024
|
end_t = time.time()
|
740
1025
|
out["dve2_create_spec"] = end_t - start_t
|
741
1026
|
|
742
1027
|
start = time.time()
|
743
|
-
resp = server._export(pth, sources, filters,
|
1028
|
+
resp = server._export(pth, sources, filters, self._fmt, None, None)
|
744
1029
|
resp.raise_for_status()
|
745
1030
|
end = time.time()
|
746
1031
|
out["dve2_exec"] = end - start
|
@@ -861,12 +1146,11 @@ class YrdenServer:
|
|
861
1146
|
resp["ts"] = [Fraction(x[0], x[1]) for x in resp["ts"]]
|
862
1147
|
return resp
|
863
1148
|
|
864
|
-
def _new(self, spec, sources, filters,
|
1149
|
+
def _new(self, spec, sources, filters, fmt):
|
865
1150
|
req = {
|
866
1151
|
"spec": spec,
|
867
1152
|
"sources": sources,
|
868
1153
|
"filters": filters,
|
869
|
-
"arrays": arrays,
|
870
1154
|
"width": fmt["width"],
|
871
1155
|
"height": fmt["height"],
|
872
1156
|
"pix_fmt": fmt["pix_fmt"],
|
@@ -878,14 +1162,11 @@ class YrdenServer:
|
|
878
1162
|
|
879
1163
|
return r.json()
|
880
1164
|
|
881
|
-
def _export(
|
882
|
-
self, pth, spec, sources, filters, arrays, fmt, encoder, encoder_opts, format
|
883
|
-
):
|
1165
|
+
def _export(self, pth, spec, sources, filters, fmt, encoder, encoder_opts, format):
|
884
1166
|
req = {
|
885
1167
|
"spec": spec,
|
886
1168
|
"sources": sources,
|
887
1169
|
"filters": filters,
|
888
|
-
"arrays": arrays,
|
889
1170
|
"width": fmt["width"],
|
890
1171
|
"height": fmt["height"],
|
891
1172
|
"pix_fmt": fmt["pix_fmt"],
|
@@ -915,7 +1196,12 @@ class YrdenServer:
|
|
915
1196
|
|
916
1197
|
def __del__(self):
|
917
1198
|
if self._proc is not None:
|
918
|
-
self._proc.
|
1199
|
+
self._proc.terminate()
|
1200
|
+
try:
|
1201
|
+
self._proc.wait(timeout=1)
|
1202
|
+
except subprocess.TimeoutExpired:
|
1203
|
+
self._proc.kill()
|
1204
|
+
self._proc.wait()
|
919
1205
|
|
920
1206
|
|
921
1207
|
class YrdenSource:
|
vidformer/cv2/__init__.py
CHANGED
@@ -18,13 +18,13 @@ try:
|
|
18
18
|
except Exception:
|
19
19
|
_opencv2 = None
|
20
20
|
|
21
|
-
import
|
22
|
-
|
21
|
+
import re
|
23
22
|
import uuid
|
24
|
-
from fractions import Fraction
|
25
|
-
from bisect import bisect_right
|
26
23
|
import zlib
|
27
|
-
import
|
24
|
+
from bisect import bisect_right
|
25
|
+
from fractions import Fraction
|
26
|
+
|
27
|
+
import numpy as np
|
28
28
|
|
29
29
|
CAP_PROP_POS_MSEC = 0
|
30
30
|
CAP_PROP_POS_FRAMES = 1
|
@@ -51,6 +51,7 @@ LINE_AA = 16
|
|
51
51
|
_inline_mat = vf.Filter("_inline_mat")
|
52
52
|
_slice_mat = vf.Filter("_slice_mat")
|
53
53
|
_slice_write_mat = vf.Filter("_slice_write_mat")
|
54
|
+
_black = vf.Filter("_black")
|
54
55
|
|
55
56
|
|
56
57
|
_filter_scale = vf.Filter("Scale")
|
@@ -61,6 +62,7 @@ _filter_line = vf.Filter("cv2.line")
|
|
61
62
|
_filter_circle = vf.Filter("cv2.circle")
|
62
63
|
_filter_addWeighted = vf.Filter("cv2.addWeighted")
|
63
64
|
_filter_ellipse = vf.Filter("cv2.ellipse")
|
65
|
+
_set_to = vf.Filter("cv2.setTo")
|
64
66
|
|
65
67
|
|
66
68
|
def _ts_to_fps(timestamps):
|
@@ -89,11 +91,30 @@ def set_server(server):
|
|
89
91
|
_global_cv2_server = server
|
90
92
|
|
91
93
|
|
94
|
+
_PIX_FMT_MAP = {
|
95
|
+
"rgb24": "rgb24",
|
96
|
+
"yuv420p": "rgb24",
|
97
|
+
"yuv422p": "rgb24",
|
98
|
+
"yuv444p": "rgb24",
|
99
|
+
"yuvj420p": "rgb24",
|
100
|
+
"yuvj422p": "rgb24",
|
101
|
+
"yuvj444p": "rgb24",
|
102
|
+
"gray": "gray",
|
103
|
+
}
|
104
|
+
|
105
|
+
|
106
|
+
def _top_level_pix_fmt(pix_fmt):
|
107
|
+
if pix_fmt in _PIX_FMT_MAP:
|
108
|
+
return _PIX_FMT_MAP[pix_fmt]
|
109
|
+
raise Exception(f"Unsupported pix_fmt {pix_fmt}")
|
110
|
+
|
111
|
+
|
92
112
|
class Frame:
|
93
113
|
def __init__(self, f, fmt):
|
94
114
|
self._f = f
|
95
115
|
self._fmt = fmt
|
96
|
-
|
116
|
+
channels = 3 if _top_level_pix_fmt(fmt["pix_fmt"]) == "rgb24" else 1
|
117
|
+
self.shape = (fmt["height"], fmt["width"], channels)
|
97
118
|
|
98
119
|
# denotes that the frame has not yet been modified
|
99
120
|
# when a frame is modified, it is converted to rgb24 first
|
@@ -101,13 +122,22 @@ class Frame:
|
|
101
122
|
|
102
123
|
def _mut(self):
|
103
124
|
if self._modified:
|
104
|
-
assert self._fmt["pix_fmt"]
|
125
|
+
assert self._fmt["pix_fmt"] in ["rgb24", "gray"]
|
105
126
|
return
|
106
127
|
|
107
128
|
self._modified = True
|
108
|
-
if
|
129
|
+
if (
|
130
|
+
self._fmt["pix_fmt"] != "rgb24"
|
131
|
+
and _top_level_pix_fmt(self._fmt["pix_fmt"]) == "rgb24"
|
132
|
+
):
|
109
133
|
self._f = _filter_scale(self._f, pix_fmt="rgb24")
|
110
134
|
self._fmt["pix_fmt"] = "rgb24"
|
135
|
+
elif (
|
136
|
+
self._fmt["pix_fmt"] != "gray"
|
137
|
+
and _top_level_pix_fmt(self._fmt["pix_fmt"]) == "gray"
|
138
|
+
):
|
139
|
+
self._f = _filter_scale(self._f, pix_fmt="gray")
|
140
|
+
self._fmt["pix_fmt"] = "gray"
|
111
141
|
|
112
142
|
def copy(self):
|
113
143
|
return Frame(self._f, self._fmt.copy())
|
@@ -118,16 +148,29 @@ class Frame:
|
|
118
148
|
"""
|
119
149
|
|
120
150
|
self._mut()
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
151
|
+
server = _server()
|
152
|
+
if type(server) is vf.YrdenServer:
|
153
|
+
spec = vf.YrdenSpec([Fraction(0, 1)], lambda t, i: self._f, self._fmt)
|
154
|
+
loader = spec.load(_server())
|
155
|
+
|
156
|
+
frame_raster_rgb24 = loader[0]
|
157
|
+
assert type(frame_raster_rgb24) is bytes
|
158
|
+
assert len(frame_raster_rgb24) == self.shape[0] * self.shape[1] * 3
|
159
|
+
raw_data_array = np.frombuffer(frame_raster_rgb24, dtype=np.uint8)
|
160
|
+
frame = raw_data_array.reshape(self.shape)
|
161
|
+
frame = frame[:, :, ::-1] # convert RGB to BGR
|
162
|
+
return frame
|
163
|
+
else:
|
164
|
+
frame = server.frame(
|
165
|
+
self.shape[1], self.shape[0], self._fmt["pix_fmt"], self._f
|
166
|
+
)
|
167
|
+
assert type(frame) is bytes
|
168
|
+
assert len(frame) == self.shape[0] * self.shape[1] * self.shape[2]
|
169
|
+
raw_data_array = np.frombuffer(frame, dtype=np.uint8)
|
170
|
+
frame = raw_data_array.reshape(self.shape)
|
171
|
+
if self.shape[2] == 3:
|
172
|
+
frame = frame[:, :, ::-1] # convert RGB to BGR
|
173
|
+
return frame
|
131
174
|
|
132
175
|
def __getitem__(self, key):
|
133
176
|
if not isinstance(key, tuple):
|
@@ -171,49 +214,75 @@ class Frame:
|
|
171
214
|
return Frame(f, fmt)
|
172
215
|
|
173
216
|
def __setitem__(self, key, value):
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
217
|
+
if type(key) is tuple:
|
218
|
+
value = frameify(value, "value")
|
219
|
+
|
220
|
+
if len(key) != 2:
|
221
|
+
raise NotImplementedError("Only 2D slicing is supported")
|
222
|
+
|
223
|
+
if not all(isinstance(x, slice) for x in key):
|
224
|
+
raise NotImplementedError("Only 2D slicing is supported")
|
225
|
+
|
226
|
+
miny = key[0].start if key[0].start is not None else 0
|
227
|
+
maxy = key[0].stop if key[0].stop is not None else self.shape[0]
|
228
|
+
minx = key[1].start if key[1].start is not None else 0
|
229
|
+
maxx = key[1].stop if key[1].stop is not None else self.shape[1]
|
230
|
+
|
231
|
+
# handle negative indices
|
232
|
+
if miny < 0:
|
233
|
+
miny = self.shape[0] + miny
|
234
|
+
if maxy < 0:
|
235
|
+
maxy = self.shape[0] + maxy
|
236
|
+
if minx < 0:
|
237
|
+
minx = self.shape[1] + minx
|
238
|
+
if maxx < 0:
|
239
|
+
maxx = self.shape[1] + maxx
|
240
|
+
|
241
|
+
if (
|
242
|
+
maxy <= miny
|
243
|
+
or maxx <= minx
|
244
|
+
or miny < 0
|
245
|
+
or minx < 0
|
246
|
+
or maxy > self.shape[0]
|
247
|
+
or maxx > self.shape[1]
|
248
|
+
):
|
249
|
+
raise NotImplementedError("Invalid slice")
|
250
|
+
|
251
|
+
if value.shape[0] != maxy - miny or value.shape[1] != maxx - minx:
|
252
|
+
raise NotImplementedError("Shape mismatch")
|
253
|
+
|
254
|
+
self._mut()
|
255
|
+
value._mut()
|
256
|
+
|
257
|
+
self._f = _slice_write_mat(self._f, value._f, miny, maxy, minx, maxx)
|
258
|
+
elif type(key) is Frame or type(key) is np.ndarray:
|
259
|
+
key = frameify(key, "key")
|
260
|
+
|
261
|
+
if key.shape[0] != self.shape[0] or key.shape[1] != self.shape[1]:
|
262
|
+
raise NotImplementedError("Shape mismatch")
|
263
|
+
|
264
|
+
if key.shape[2] != 1:
|
265
|
+
raise NotImplementedError("Only 1-channel mask frames are supported")
|
266
|
+
|
267
|
+
# Value should be a bgr or bgra color
|
268
|
+
if (type(value) is not list and type(value) is not tuple) or len(
|
269
|
+
value
|
270
|
+
) not in [3, 4]:
|
271
|
+
raise NotImplementedError(
|
272
|
+
"Value should be a 3 or 4 element list or tuple"
|
273
|
+
)
|
274
|
+
value = [float(x) for x in value]
|
275
|
+
if len(value) == 3:
|
276
|
+
value.append(255.0)
|
212
277
|
|
213
|
-
|
214
|
-
|
278
|
+
self._mut()
|
279
|
+
key._mut()
|
215
280
|
|
216
|
-
|
281
|
+
self._f = _set_to(self._f, value, key._f)
|
282
|
+
else:
|
283
|
+
raise NotImplementedError(
|
284
|
+
"__setitem__ only supports slicing by a 2d tuple or a mask frame"
|
285
|
+
)
|
217
286
|
|
218
287
|
|
219
288
|
def _inline_frame(arr):
|
@@ -283,7 +352,7 @@ class VideoCapture:
|
|
283
352
|
elif prop == CAP_PROP_FRAME_HEIGHT:
|
284
353
|
return self._source.fmt()["height"]
|
285
354
|
elif prop == CAP_PROP_FRAME_COUNT:
|
286
|
-
return len(self._source
|
355
|
+
return len(self._source)
|
287
356
|
elif prop == CAP_PROP_POS_FRAMES:
|
288
357
|
return self._next_frame_idx
|
289
358
|
|
@@ -309,6 +378,20 @@ class VideoCapture:
|
|
309
378
|
frame = Frame(frame, self._source.fmt())
|
310
379
|
return True, frame
|
311
380
|
|
381
|
+
def __getitem__(self, key):
|
382
|
+
if not isinstance(key, int):
|
383
|
+
raise NotImplementedError("Only integer indexing is supported")
|
384
|
+
if key < 0:
|
385
|
+
key = len(self._source) + key
|
386
|
+
if key < 0 or key >= len(self._source):
|
387
|
+
raise IndexError("Index out of bounds")
|
388
|
+
frame = self._source.iloc[key]
|
389
|
+
frame = Frame(frame, self._source.fmt())
|
390
|
+
return frame
|
391
|
+
|
392
|
+
def __len__(self):
|
393
|
+
return len(self._source)
|
394
|
+
|
312
395
|
def release(self):
|
313
396
|
pass
|
314
397
|
|
@@ -341,6 +424,8 @@ class _IgniVideoWriter:
|
|
341
424
|
fps,
|
342
425
|
size,
|
343
426
|
batch_size=1024,
|
427
|
+
compression="gzip",
|
428
|
+
ttl=3600,
|
344
429
|
vod_segment_length=Fraction(2, 1),
|
345
430
|
):
|
346
431
|
server = _server()
|
@@ -359,26 +444,34 @@ class _IgniVideoWriter:
|
|
359
444
|
assert isinstance(size, tuple) or isinstance(size, list)
|
360
445
|
assert len(size) == 2
|
361
446
|
width, height = size
|
447
|
+
assert ttl is None or isinstance(ttl, int)
|
362
448
|
self._spec = server.create_spec(
|
363
|
-
width, height, "yuv420p", vod_segment_length, 1 / self._f_time
|
449
|
+
width, height, "yuv420p", vod_segment_length, 1 / self._f_time, ttl=ttl
|
364
450
|
)
|
365
451
|
self._batch_size = batch_size
|
452
|
+
assert compression is None or compression in ["gzip"]
|
453
|
+
self._compression = compression
|
366
454
|
self._idx = 0
|
367
|
-
self.
|
455
|
+
self._feb = vf._FrameExpressionBlock()
|
368
456
|
|
369
457
|
def _flush(self, terminal=False):
|
370
458
|
server = _server()
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
459
|
+
if len(self._feb) > 0:
|
460
|
+
server.push_spec_part_block(
|
461
|
+
self._spec,
|
462
|
+
self._idx - len(self._feb),
|
463
|
+
[self._feb],
|
464
|
+
terminal=terminal,
|
465
|
+
compression=self._compression,
|
466
|
+
)
|
467
|
+
self._feb = vf._FrameExpressionBlock()
|
468
|
+
else:
|
469
|
+
server.push_spec_part_block(
|
470
|
+
self._spec,
|
471
|
+
self._idx - len(self._feb),
|
472
|
+
[],
|
473
|
+
terminal=terminal,
|
474
|
+
)
|
382
475
|
|
383
476
|
def spec(self):
|
384
477
|
return self._spec
|
@@ -397,18 +490,14 @@ class _IgniVideoWriter:
|
|
397
490
|
if frame._fmt["pix_fmt"] != self._spec._fmt["pix_fmt"]:
|
398
491
|
f_obj = _filter_scale(frame._f, pix_fmt=self._spec._fmt["pix_fmt"])
|
399
492
|
frame = Frame(f_obj, self._spec._fmt)
|
400
|
-
|
401
|
-
self._frame_buffer.append((t, frame._f if frame is not None else None))
|
493
|
+
self._feb.insert_frame(frame._f if frame is not None else None)
|
402
494
|
self._idx += 1
|
403
495
|
|
404
|
-
if len(self.
|
496
|
+
if len(self._feb) >= self._batch_size:
|
405
497
|
self._flush()
|
406
498
|
|
407
499
|
def release(self):
|
408
|
-
|
409
|
-
self._flush(True)
|
410
|
-
else:
|
411
|
-
self._explicit_terminate()
|
500
|
+
self._flush(True)
|
412
501
|
|
413
502
|
|
414
503
|
class _YrdenVideoWriter:
|
@@ -478,18 +567,33 @@ def frameify(obj, field_name=None):
|
|
478
567
|
def imread(path, *args):
|
479
568
|
if len(args) > 0:
|
480
569
|
raise NotImplementedError("imread does not support additional arguments")
|
481
|
-
|
482
570
|
assert path.lower().endswith((".jpg", ".jpeg", ".png"))
|
483
571
|
server = _server()
|
484
|
-
|
485
|
-
|
486
|
-
|
572
|
+
|
573
|
+
if type(server) is vf.YrdenServer:
|
574
|
+
source = vf.YrdenSource(server, str(uuid.uuid4()), path, 0)
|
575
|
+
frame = Frame(source.iloc[0], source.fmt())
|
576
|
+
return frame
|
577
|
+
else:
|
578
|
+
cap = VideoCapture(path)
|
579
|
+
assert cap.isOpened()
|
580
|
+
assert len(cap._source) == 1
|
581
|
+
ret, frame = cap.read()
|
582
|
+
assert ret
|
583
|
+
cap.release()
|
584
|
+
return frame
|
487
585
|
|
488
586
|
|
489
587
|
def imwrite(path, img, *args):
|
490
588
|
if len(args) > 0:
|
491
589
|
raise NotImplementedError("imwrite does not support additional arguments")
|
492
590
|
|
591
|
+
server = _server()
|
592
|
+
if type(server) is vf.IgniServer:
|
593
|
+
raise NotImplementedError(
|
594
|
+
"imwrite is only supported with YrdenServer, not IgniServer"
|
595
|
+
)
|
596
|
+
|
493
597
|
img = frameify(img)
|
494
598
|
|
495
599
|
fmt = img._fmt.copy()
|
@@ -518,7 +622,7 @@ def imwrite(path, img, *args):
|
|
518
622
|
fmt["pix_fmt"] = "yuvj420p"
|
519
623
|
|
520
624
|
spec = vf.YrdenSpec(domain, lambda t, i: f, fmt)
|
521
|
-
spec.save(
|
625
|
+
spec.save(server, path, encoder="mjpeg")
|
522
626
|
else:
|
523
627
|
raise Exception("Unsupported image format")
|
524
628
|
|
@@ -546,6 +650,39 @@ def vidplay(video, *args, **kwargs):
|
|
546
650
|
raise Exception("Unsupported video type to vidplay")
|
547
651
|
|
548
652
|
|
653
|
+
def zeros(shape, dtype=np.uint8):
|
654
|
+
"""
|
655
|
+
Create a black frame. Mimics numpy.zeros.
|
656
|
+
"""
|
657
|
+
assert isinstance(shape, tuple) or isinstance(shape, list)
|
658
|
+
assert len(shape) == 3
|
659
|
+
assert shape[2] in [1, 3]
|
660
|
+
assert dtype == np.uint8
|
661
|
+
|
662
|
+
height, width, channels = shape
|
663
|
+
if channels == 1:
|
664
|
+
pix_fmt = "gray"
|
665
|
+
else:
|
666
|
+
pix_fmt = "rgb24"
|
667
|
+
|
668
|
+
f = _black(width=width, height=height, pix_fmt=pix_fmt)
|
669
|
+
fmt = {"width": width, "height": height, "pix_fmt": pix_fmt}
|
670
|
+
return Frame(f, fmt)
|
671
|
+
|
672
|
+
|
673
|
+
def resize(src, dsize):
|
674
|
+
src = frameify(src)
|
675
|
+
src._mut()
|
676
|
+
|
677
|
+
assert isinstance(dsize, tuple) or isinstance(dsize, list)
|
678
|
+
assert len(dsize) == 2
|
679
|
+
width, height = dsize
|
680
|
+
|
681
|
+
f = _filter_scale(src._f, width=width, height=height)
|
682
|
+
fmt = {"width": width, "height": height, "pix_fmt": src._fmt["pix_fmt"]}
|
683
|
+
return Frame(f, fmt)
|
684
|
+
|
685
|
+
|
549
686
|
def rectangle(img, pt1, pt2, color, thickness=None, lineType=None, shift=None):
|
550
687
|
"""
|
551
688
|
cv.rectangle( img, pt1, pt2, color[, thickness[, lineType[, shift]]] )
|
@@ -2,17 +2,23 @@
|
|
2
2
|
vidformer.supervision is the [supervision](https://supervision.roboflow.com/) frontend for [vidformer](https://github.com/ixlab/vidformer).
|
3
3
|
"""
|
4
4
|
|
5
|
-
|
5
|
+
from math import sqrt
|
6
6
|
|
7
|
-
import supervision as _sv
|
8
7
|
import numpy as np
|
9
|
-
|
8
|
+
import supervision as _sv
|
9
|
+
from supervision import Color, ColorLookup, ColorPalette, Detections
|
10
10
|
from supervision.annotators.utils import resolve_color, resolve_text_background_xyxy
|
11
|
-
from supervision.detection.utils import spread_out_boxes
|
12
11
|
from supervision.config import CLASS_NAME_DATA_FIELD
|
13
|
-
from
|
12
|
+
from supervision.detection.utils import spread_out_boxes
|
14
13
|
from supervision.geometry.core import Position
|
15
14
|
|
15
|
+
import vidformer.cv2 as vf_cv2
|
16
|
+
|
17
|
+
try:
|
18
|
+
import cv2 as ocv_cv2
|
19
|
+
except ImportError:
|
20
|
+
ocv_cv2 = None
|
21
|
+
|
16
22
|
CV2_FONT = vf_cv2.FONT_HERSHEY_SIMPLEX
|
17
23
|
|
18
24
|
|
@@ -271,7 +277,6 @@ class DotAnnotator:
|
|
271
277
|
outline_thickness: int = 0,
|
272
278
|
outline_color=Color.BLACK,
|
273
279
|
):
|
274
|
-
|
275
280
|
self.color = color
|
276
281
|
self.radius: int = radius
|
277
282
|
self.position: Position = position
|
@@ -536,3 +541,89 @@ class LabelAnnotator:
|
|
536
541
|
thickness=-1,
|
537
542
|
)
|
538
543
|
return scene
|
544
|
+
|
545
|
+
|
546
|
+
class MaskAnnotator:
|
547
|
+
def __init__(
|
548
|
+
self,
|
549
|
+
color=ColorPalette.DEFAULT,
|
550
|
+
opacity: float = 0.5,
|
551
|
+
color_lookup: ColorLookup = ColorLookup.CLASS,
|
552
|
+
):
|
553
|
+
self.color = color
|
554
|
+
self.opacity = opacity
|
555
|
+
self.color_lookup: ColorLookup = color_lookup
|
556
|
+
|
557
|
+
def annotate(
|
558
|
+
self,
|
559
|
+
scene,
|
560
|
+
detections: Detections,
|
561
|
+
custom_color_lookup=None,
|
562
|
+
):
|
563
|
+
if detections.mask is None:
|
564
|
+
return scene
|
565
|
+
|
566
|
+
colored_mask = scene.copy()
|
567
|
+
|
568
|
+
for detection_idx in np.flip(np.argsort(detections.box_area)):
|
569
|
+
color = resolve_color(
|
570
|
+
color=self.color,
|
571
|
+
detections=detections,
|
572
|
+
detection_idx=detection_idx,
|
573
|
+
color_lookup=(
|
574
|
+
self.color_lookup
|
575
|
+
if custom_color_lookup is None
|
576
|
+
else custom_color_lookup
|
577
|
+
),
|
578
|
+
)
|
579
|
+
mask = detections.mask[detection_idx]
|
580
|
+
colored_mask[mask] = color.as_bgr()
|
581
|
+
|
582
|
+
vf_cv2.addWeighted(
|
583
|
+
colored_mask, self.opacity, scene, 1 - self.opacity, 0, dst=scene
|
584
|
+
)
|
585
|
+
return scene
|
586
|
+
|
587
|
+
|
588
|
+
class MaskStreamWriter:
|
589
|
+
def __init__(self, path: str, shape: tuple):
|
590
|
+
# Shape should be (width, height)
|
591
|
+
assert ocv_cv2 is not None, "OpenCV cv2 is required for ExternDetectionsBuilder"
|
592
|
+
assert type(shape) is tuple, "shape must be a tuple"
|
593
|
+
assert len(shape) == 2, "shape must be a tuple of length 2"
|
594
|
+
self._shape = (shape[1], shape[0])
|
595
|
+
self._writer = ocv_cv2.VideoWriter(
|
596
|
+
path, ocv_cv2.VideoWriter_fourcc(*"FFV1"), 1, shape, isColor=False
|
597
|
+
)
|
598
|
+
assert self._writer.isOpened(), f"Failed to open video writer at {path}"
|
599
|
+
self._i = 0
|
600
|
+
|
601
|
+
def write_detections(self, detections: Detections):
|
602
|
+
if len(detections) == 0:
|
603
|
+
return self._i
|
604
|
+
|
605
|
+
mask = detections.mask
|
606
|
+
assert (
|
607
|
+
mask.shape[1:] == self._shape
|
608
|
+
), f"mask shape ({mask.shape[:1]}) must match the shape of the video ({self._shape})"
|
609
|
+
for i in range(mask.shape[0]):
|
610
|
+
frame_uint8 = detections.mask[i].astype(np.uint8)
|
611
|
+
self._writer.write(frame_uint8)
|
612
|
+
self._i += 1
|
613
|
+
return self._i
|
614
|
+
|
615
|
+
def release(self):
|
616
|
+
self._writer.release()
|
617
|
+
|
618
|
+
|
619
|
+
def populate_mask(
|
620
|
+
detections: Detections, mask_stream: vf_cv2.VideoCapture, frame_idx: int
|
621
|
+
):
|
622
|
+
assert type(detections) is Detections
|
623
|
+
assert detections.mask is None
|
624
|
+
detections.mask = []
|
625
|
+
assert len(detections) + frame_idx <= len(mask_stream)
|
626
|
+
for i in range(len(detections)):
|
627
|
+
mask = mask_stream[frame_idx + i]
|
628
|
+
assert mask.shape[2] == 1, "mask must be a single channel image"
|
629
|
+
detections.mask.append(mask)
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: vidformer
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.12.0
|
4
4
|
Summary: vidformer-py is a Python 🐍 interface for [vidformer](https://github.com/ixlab/vidformer).
|
5
5
|
Author-email: Dominik Winecki <dominikwinecki@gmail.com>
|
6
6
|
Requires-Python: >=3.8
|
@@ -0,0 +1,6 @@
|
|
1
|
+
vidformer/__init__.py,sha256=2_IA8eCF8xIWqgdcpC06CSEsX_b2DPpOww3tuQlY3rg,55692
|
2
|
+
vidformer/cv2/__init__.py,sha256=cp1qJPpxpRGCE3elmoHDxhzafZbopZ9wIkKcZJJI8HM,30105
|
3
|
+
vidformer/supervision/__init__.py,sha256=dRHAcHiZN68gUH_2m3o7Ohsv3NBGxF4XGPeI0pn2_K4,20346
|
4
|
+
vidformer-0.12.0.dist-info/WHEEL,sha256=_2ozNFCLWc93bK4WKHCO-eDUENDlo-dgc9cU3qokYO4,82
|
5
|
+
vidformer-0.12.0.dist-info/METADATA,sha256=C3OsKiJjYPCgiUblJUan2-aQG5TOprdCn2cduqJBow0,1800
|
6
|
+
vidformer-0.12.0.dist-info/RECORD,,
|
@@ -1,6 +0,0 @@
|
|
1
|
-
vidformer/__init__.py,sha256=7ZUQSCEoTkxGFORCWhL1WAgS_ii0Xu_kaipjjstUAn4,44916
|
2
|
-
vidformer/cv2/__init__.py,sha256=DGm5NB4FGCHxPVez-yO748DjocKruxn4QBqqThgskWI,25555
|
3
|
-
vidformer/supervision/__init__.py,sha256=unJMfbabIBQ36iftcs6QUM3mzdWFRxrlLdk_Z1F1oO8,17489
|
4
|
-
vidformer-0.10.1.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
5
|
-
vidformer-0.10.1.dist-info/METADATA,sha256=fzOpw2PCiHhZgeFS6rBxrlutcMs7v6oznjnWz-f6j-Y,1800
|
6
|
-
vidformer-0.10.1.dist-info/RECORD,,
|