vidformer 0.10.0__py3-none-any.whl → 0.11.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 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.10.0"
12
+ __version__ = "0.11.0"
13
13
 
14
14
 
15
- import subprocess
16
- from fractions import Fraction
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 uuid
24
- import threading
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:
@@ -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
- response = requests.get(
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 = requests.get(
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 = requests.get(
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 = requests.delete(
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 = requests.post(
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 = requests.post(
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 = requests.get(
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 = requests.get(
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 = requests.post(
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 = requests.delete(
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 = requests.post(
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, arrays, self._fmt)
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
- arrays = []
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, arrays, self._fmt)
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, arrays, self._fmt, None, None)
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, arrays, fmt):
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"],
vidformer/cv2/__init__.py CHANGED
@@ -18,13 +18,13 @@ try:
18
18
  except Exception:
19
19
  _opencv2 = None
20
20
 
21
- import numpy as np
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 re
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
- self.shape = (fmt["height"], fmt["width"], 3)
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"] == "rgb24"
125
+ assert self._fmt["pix_fmt"] in ["rgb24", "gray"]
105
126
  return
106
127
 
107
128
  self._modified = True
108
- if self._fmt["pix_fmt"] != "rgb24":
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
- spec = vf.YrdenSpec([Fraction(0, 1)], lambda t, i: self._f, self._fmt)
122
- loader = spec.load(_server())
123
-
124
- frame_raster_rgb24 = loader[0]
125
- assert type(frame_raster_rgb24) is bytes
126
- assert len(frame_raster_rgb24) == self.shape[0] * self.shape[1] * 3
127
- raw_data_array = np.frombuffer(frame_raster_rgb24, dtype=np.uint8)
128
- frame = raw_data_array.reshape(self.shape)
129
- frame = frame[:, :, ::-1] # convert RGB to BGR
130
- return frame
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,71 @@ class Frame:
171
214
  return Frame(f, fmt)
172
215
 
173
216
  def __setitem__(self, key, value):
174
- value = frameify(value, "value")
175
-
176
- if not isinstance(key, tuple):
177
- raise NotImplementedError("Only 2D slicing is supported")
178
-
179
- if len(key) != 2:
180
- raise NotImplementedError("Only 2D slicing is supported")
181
-
182
- if not all(isinstance(x, slice) for x in key):
183
- raise NotImplementedError("Only 2D slicing is supported")
184
-
185
- miny = key[0].start if key[0].start is not None else 0
186
- maxy = key[0].stop if key[0].stop is not None else self.shape[0]
187
- minx = key[1].start if key[1].start is not None else 0
188
- maxx = key[1].stop if key[1].stop is not None else self.shape[1]
189
-
190
- # handle negative indices
191
- if miny < 0:
192
- miny = self.shape[0] + miny
193
- if maxy < 0:
194
- maxy = self.shape[0] + maxy
195
- if minx < 0:
196
- minx = self.shape[1] + minx
197
- if maxx < 0:
198
- maxx = self.shape[1] + maxx
199
-
200
- if (
201
- maxy <= miny
202
- or maxx <= minx
203
- or miny < 0
204
- or minx < 0
205
- or maxy > self.shape[0]
206
- or maxx > self.shape[1]
207
- ):
208
- raise NotImplementedError("Invalid slice")
209
-
210
- if value.shape[0] != maxy - miny or value.shape[1] != maxx - minx:
211
- raise NotImplementedError("Shape mismatch")
212
-
213
- self._mut()
214
- value._mut()
215
-
216
- self._f = _slice_write_mat(self._f, value._f, miny, maxy, minx, maxx)
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 or len(value) not in [3, 4]:
269
+ raise NotImplementedError("Value should be a 3 or 4 element list")
270
+ value = [float(x) for x in value]
271
+ if len(value) == 3:
272
+ value.append(255.0)
273
+
274
+ self._mut()
275
+ key._mut()
276
+
277
+ self._f = _set_to(self._f, value, key._f)
278
+ else:
279
+ raise NotImplementedError(
280
+ "__setitem__ only supports slicing by a 2d tuple or a mask frame"
281
+ )
217
282
 
218
283
 
219
284
  def _inline_frame(arr):
@@ -341,6 +406,8 @@ class _IgniVideoWriter:
341
406
  fps,
342
407
  size,
343
408
  batch_size=1024,
409
+ compression="gzip",
410
+ ttl=3600,
344
411
  vod_segment_length=Fraction(2, 1),
345
412
  ):
346
413
  server = _server()
@@ -358,27 +425,35 @@ class _IgniVideoWriter:
358
425
 
359
426
  assert isinstance(size, tuple) or isinstance(size, list)
360
427
  assert len(size) == 2
361
- width, height = size
428
+ height, width = size
429
+ assert ttl is None or isinstance(ttl, int)
362
430
  self._spec = server.create_spec(
363
- width, height, "yuv420p", vod_segment_length, 1 / self._f_time
431
+ width, height, "yuv420p", vod_segment_length, 1 / self._f_time, ttl=ttl
364
432
  )
365
433
  self._batch_size = batch_size
434
+ assert compression is None or compression in ["gzip"]
435
+ self._compression = compression
366
436
  self._idx = 0
367
- self._frame_buffer = []
437
+ self._feb = vf._FrameExpressionBlock()
368
438
 
369
439
  def _flush(self, terminal=False):
370
440
  server = _server()
371
- server.push_spec_part(
372
- self._spec,
373
- self._idx - len(self._frame_buffer),
374
- self._frame_buffer,
375
- terminal=terminal,
376
- )
377
- self._frame_buffer = []
378
-
379
- def _explicit_terminate(self):
380
- server = _server()
381
- server.push_spec_part(self._spec._id, self._idx, [], terminal=True)
441
+ if len(self._feb) > 0:
442
+ server.push_spec_part_block(
443
+ self._spec,
444
+ self._idx - len(self._feb),
445
+ [self._feb],
446
+ terminal=terminal,
447
+ compression=self._compression,
448
+ )
449
+ self._feb = vf._FrameExpressionBlock()
450
+ else:
451
+ server.push_spec_part_block(
452
+ self._spec,
453
+ self._idx - len(self._feb),
454
+ [],
455
+ terminal=terminal,
456
+ )
382
457
 
383
458
  def spec(self):
384
459
  return self._spec
@@ -397,18 +472,14 @@ class _IgniVideoWriter:
397
472
  if frame._fmt["pix_fmt"] != self._spec._fmt["pix_fmt"]:
398
473
  f_obj = _filter_scale(frame._f, pix_fmt=self._spec._fmt["pix_fmt"])
399
474
  frame = Frame(f_obj, self._spec._fmt)
400
- t = self._f_time * self._idx
401
- self._frame_buffer.append((t, frame._f if frame is not None else None))
475
+ self._feb.insert_frame(frame._f if frame is not None else None)
402
476
  self._idx += 1
403
477
 
404
- if len(self._frame_buffer) >= self._batch_size:
478
+ if len(self._feb) >= self._batch_size:
405
479
  self._flush()
406
480
 
407
481
  def release(self):
408
- if len(self._frame_buffer) > 0:
409
- self._flush(True)
410
- else:
411
- self._explicit_terminate()
482
+ self._flush(True)
412
483
 
413
484
 
414
485
  class _YrdenVideoWriter:
@@ -443,8 +514,8 @@ class _YrdenVideoWriter:
443
514
 
444
515
  def spec(self) -> vf.YrdenSpec:
445
516
  fmt = {
446
- "width": self._size[0],
447
- "height": self._size[1],
517
+ "width": self._size[1],
518
+ "height": self._size[0],
448
519
  "pix_fmt": self._pix_fmt,
449
520
  }
450
521
  domain = _fps_to_ts(self._fps, len(self._frames))
@@ -478,18 +549,33 @@ def frameify(obj, field_name=None):
478
549
  def imread(path, *args):
479
550
  if len(args) > 0:
480
551
  raise NotImplementedError("imread does not support additional arguments")
481
-
482
552
  assert path.lower().endswith((".jpg", ".jpeg", ".png"))
483
553
  server = _server()
484
- source = vf.YrdenSource(server, str(uuid.uuid4()), path, 0)
485
- frame = Frame(source.iloc[0], source.fmt())
486
- return frame
554
+
555
+ if type(server) is vf.YrdenServer:
556
+ source = vf.YrdenSource(server, str(uuid.uuid4()), path, 0)
557
+ frame = Frame(source.iloc[0], source.fmt())
558
+ return frame
559
+ else:
560
+ cap = VideoCapture(path)
561
+ assert cap.isOpened()
562
+ assert len(cap._source) == 1
563
+ ret, frame = cap.read()
564
+ assert ret
565
+ cap.release()
566
+ return frame
487
567
 
488
568
 
489
569
  def imwrite(path, img, *args):
490
570
  if len(args) > 0:
491
571
  raise NotImplementedError("imwrite does not support additional arguments")
492
572
 
573
+ server = _server()
574
+ if type(server) is vf.IgniServer:
575
+ raise NotImplementedError(
576
+ "imwrite is only supported with YrdenServer, not IgniServer"
577
+ )
578
+
493
579
  img = frameify(img)
494
580
 
495
581
  fmt = img._fmt.copy()
@@ -518,7 +604,7 @@ def imwrite(path, img, *args):
518
604
  fmt["pix_fmt"] = "yuvj420p"
519
605
 
520
606
  spec = vf.YrdenSpec(domain, lambda t, i: f, fmt)
521
- spec.save(_server(), path, encoder="mjpeg")
607
+ spec.save(server, path, encoder="mjpeg")
522
608
  else:
523
609
  raise Exception("Unsupported image format")
524
610
 
@@ -546,6 +632,39 @@ def vidplay(video, *args, **kwargs):
546
632
  raise Exception("Unsupported video type to vidplay")
547
633
 
548
634
 
635
+ def zeros(shape, dtype=np.uint8):
636
+ """
637
+ Create a black frame. Mimics numpy.zeros.
638
+ """
639
+ assert isinstance(shape, tuple) or isinstance(shape, list)
640
+ assert len(shape) == 3
641
+ assert shape[2] in [1, 3]
642
+ assert dtype == np.uint8
643
+
644
+ height, width, channels = shape
645
+ if channels == 1:
646
+ pix_fmt = "gray"
647
+ else:
648
+ pix_fmt = "rgb24"
649
+
650
+ f = _black(width=width, height=height, pix_fmt=pix_fmt)
651
+ fmt = {"width": width, "height": height, "pix_fmt": pix_fmt}
652
+ return Frame(f, fmt)
653
+
654
+
655
+ def resize(src, dsize):
656
+ src = frameify(src)
657
+ src._mut()
658
+
659
+ assert isinstance(dsize, tuple) or isinstance(dsize, list)
660
+ assert len(dsize) == 2
661
+ height, width = dsize
662
+
663
+ f = _filter_scale(src._f, width=width, height=height)
664
+ fmt = {"width": width, "height": height, "pix_fmt": src._fmt["pix_fmt"]}
665
+ return Frame(f, fmt)
666
+
667
+
549
668
  def rectangle(img, pt1, pt2, color, thickness=None, lineType=None, shift=None):
550
669
  """
551
670
  cv.rectangle( img, pt1, pt2, color[, thickness[, lineType[, shift]]] )
@@ -2,17 +2,18 @@
2
2
  vidformer.supervision is the [supervision](https://supervision.roboflow.com/) frontend for [vidformer](https://github.com/ixlab/vidformer).
3
3
  """
4
4
 
5
- import vidformer.cv2 as vf_cv2
5
+ from math import sqrt
6
6
 
7
- import supervision as _sv
8
7
  import numpy as np
9
- from supervision import Color, ColorPalette, ColorLookup, Detections
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 math import sqrt
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
+
16
17
  CV2_FONT = vf_cv2.FONT_HERSHEY_SIMPLEX
17
18
 
18
19
 
@@ -499,31 +500,40 @@ class LabelAnnotator:
499
500
 
500
501
  border_radius = min(border_radius, min(width, height) // 2)
501
502
 
502
- rectangle_coordinates = [
503
- ((x1 + border_radius, y1), (x2 - border_radius, y2)),
504
- ((x1, y1 + border_radius), (x2, y2 - border_radius)),
505
- ]
506
- circle_centers = [
507
- (x1 + border_radius, y1 + border_radius),
508
- (x2 - border_radius, y1 + border_radius),
509
- (x1 + border_radius, y2 - border_radius),
510
- (x2 - border_radius, y2 - border_radius),
511
- ]
512
-
513
- for coordinates in rectangle_coordinates:
503
+ if border_radius <= 0:
514
504
  vf_cv2.rectangle(
515
505
  img=scene,
516
- pt1=coordinates[0],
517
- pt2=coordinates[1],
518
- color=color,
519
- thickness=-1,
520
- )
521
- for center in circle_centers:
522
- vf_cv2.circle(
523
- img=scene,
524
- center=center,
525
- radius=border_radius,
506
+ pt1=(x1, y1),
507
+ pt2=(x2, y2),
526
508
  color=color,
527
509
  thickness=-1,
528
510
  )
511
+ else:
512
+ rectangle_coordinates = [
513
+ ((x1 + border_radius, y1), (x2 - border_radius, y2)),
514
+ ((x1, y1 + border_radius), (x2, y2 - border_radius)),
515
+ ]
516
+ circle_centers = [
517
+ (x1 + border_radius, y1 + border_radius),
518
+ (x2 - border_radius, y1 + border_radius),
519
+ (x1 + border_radius, y2 - border_radius),
520
+ (x2 - border_radius, y2 - border_radius),
521
+ ]
522
+
523
+ for coordinates in rectangle_coordinates:
524
+ vf_cv2.rectangle(
525
+ img=scene,
526
+ pt1=coordinates[0],
527
+ pt2=coordinates[1],
528
+ color=color,
529
+ thickness=-1,
530
+ )
531
+ for center in circle_centers:
532
+ vf_cv2.circle(
533
+ img=scene,
534
+ center=center,
535
+ radius=border_radius,
536
+ color=color,
537
+ thickness=-1,
538
+ )
529
539
  return scene
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: vidformer
3
- Version: 0.10.0
3
+ Version: 0.11.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=lbbyaiV57QsaXmvHfrz_RXLaRnFMfm5ulK2dN701X-E,55465
2
+ vidformer/cv2/__init__.py,sha256=9J_PV306rHYlf4FgBeQqJnlJJ6d2Mcb9s0TfiH8fASA,29528
3
+ vidformer/supervision/__init__.py,sha256=KR-keBgDG29TSyIFU4Czgd8Yc5qckJKlSaMcPj_z-Zc,17490
4
+ vidformer-0.11.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
5
+ vidformer-0.11.0.dist-info/METADATA,sha256=K3-g51c1iXRrkmqRwoYLUN8uJThtSCkjMs7kzr2SvNw,1800
6
+ vidformer-0.11.0.dist-info/RECORD,,
@@ -1,6 +0,0 @@
1
- vidformer/__init__.py,sha256=qpWcttHsW1wnaGx5__qJqaT-m5VF7yMiHCxdm10Fjek,44916
2
- vidformer/cv2/__init__.py,sha256=DGm5NB4FGCHxPVez-yO748DjocKruxn4QBqqThgskWI,25555
3
- vidformer/supervision/__init__.py,sha256=T2QJ3gKtUSoKOlxAf06TG4fD9IgIDuBpiVOBKVk3qAw,17150
4
- vidformer-0.10.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
5
- vidformer-0.10.0.dist-info/METADATA,sha256=f_aUIFbQUoVJuYTKtqexwyN5uzWRoS2Fvd2dHZ_EGbo,1800
6
- vidformer-0.10.0.dist-info/RECORD,,