vidformer 0.8.0__py3-none-any.whl → 0.10.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/cv2/vf_cv2.py DELETED
@@ -1,669 +0,0 @@
1
- """
2
- vidformer.cv2 is the cv2 frontend for [vidformer](https://github.com/ixlab/vidformer).
3
-
4
- > ⚠️ This module is a work in progress. See the [implemented functions list](https://ixlab.github.io/vidformer/opencv-filters.html).
5
-
6
- **Quick links:**
7
- * [📦 PyPI](https://pypi.org/project/vidformer/)
8
- * [📘 Documentation - vidformer-py](https://ixlab.github.io/vidformer/vidformer-py/)
9
- * [📘 Documentation - vidformer.cv2](https://ixlab.github.io/vidformer/vidformer-py-cv2/)
10
- * [🧑‍💻 Source Code](https://github.com/ixlab/vidformer/tree/main/vidformer-py/)
11
- """
12
-
13
- from .. import vf
14
-
15
- try:
16
- import cv2 as _opencv2
17
- except:
18
- _opencv2 = None
19
-
20
- import numpy as np
21
-
22
- import uuid
23
- from fractions import Fraction
24
- from bisect import bisect_right
25
-
26
- CAP_PROP_POS_MSEC = 0
27
- CAP_PROP_POS_FRAMES = 1
28
- CAP_PROP_FRAME_WIDTH = 3
29
- CAP_PROP_FRAME_HEIGHT = 4
30
- CAP_PROP_FPS = 5
31
- CAP_PROP_FRAME_COUNT = 7
32
-
33
- FONT_HERSHEY_SIMPLEX = 0
34
- FONT_HERSHEY_PLAIN = 1
35
- FONT_HERSHEY_DUPLEX = 2
36
- FONT_HERSHEY_COMPLEX = 3
37
- FONT_HERSHEY_TRIPLEX = 4
38
- FONT_HERSHEY_COMPLEX_SMALL = 5
39
- FONT_HERSHEY_SCRIPT_SIMPLEX = 6
40
- FONT_HERSHEY_SCRIPT_COMPLEX = 7
41
- FONT_ITALIC = 16
42
-
43
- FILLED = -1
44
- LINE_4 = 4
45
- LINE_8 = 8
46
- LINE_AA = 16
47
-
48
- _inline_mat = vf.Filter("_inline_mat")
49
- _slice_mat = vf.Filter("_slice_mat")
50
- _slice_write_mat = vf.Filter("_slice_write_mat")
51
-
52
-
53
- _filter_scale = vf.Filter("Scale")
54
- _filter_rectangle = vf.Filter("cv2.rectangle")
55
- _filter_putText = vf.Filter("cv2.putText")
56
- _filter_arrowedLine = vf.Filter("cv2.arrowedLine")
57
- _filter_line = vf.Filter("cv2.line")
58
- _filter_circle = vf.Filter("cv2.circle")
59
- _filter_addWeighted = vf.Filter("cv2.addWeighted")
60
-
61
-
62
- def _ts_to_fps(timestamps):
63
- return int(1 / (timestamps[1] - timestamps[0])) # TODO: Fix for non-integer fps
64
-
65
-
66
- def _fps_to_ts(fps, n_frames):
67
- assert type(fps) == int
68
- return [Fraction(i, fps) for i in range(n_frames)]
69
-
70
-
71
- _global_cv2_server = None
72
-
73
-
74
- def _server():
75
- global _global_cv2_server
76
- if _global_cv2_server is None:
77
- _global_cv2_server = vf.YrdenServer()
78
- return _global_cv2_server
79
-
80
-
81
- def set_cv2_server(server: vf.YrdenServer):
82
- """Set the server to use for the cv2 frontend."""
83
- global _global_cv2_server
84
- assert isinstance(server, vf.YrdenServer)
85
- _global_cv2_server = server
86
-
87
-
88
- class Frame:
89
- def __init__(self, f, fmt):
90
- self._f = f
91
- self._fmt = fmt
92
- self.shape = (fmt["height"], fmt["width"], 3)
93
-
94
- # denotes that the frame has not yet been modified
95
- # when a frame is modified, it is converted to rgb24 first
96
- self._modified = False
97
-
98
- def _mut(self):
99
- if self._modified:
100
- assert self._fmt["pix_fmt"] == "rgb24"
101
- return
102
-
103
- self._modified = True
104
- if self._fmt["pix_fmt"] != "rgb24":
105
- self._f = _filter_scale(self._f, pix_fmt="rgb24")
106
- self._fmt["pix_fmt"] = "rgb24"
107
-
108
- def numpy(self):
109
- """
110
- Return the frame as a numpy array.
111
- """
112
-
113
- self._mut()
114
- spec = vf.Spec([Fraction(0, 1)], lambda t, i: self._f, self._fmt)
115
- loader = spec.load(_server())
116
-
117
- frame_raster_rgb24 = loader[0]
118
- assert type(frame_raster_rgb24) == bytes
119
- assert len(frame_raster_rgb24) == self.shape[0] * self.shape[1] * 3
120
- raw_data_array = np.frombuffer(frame_raster_rgb24, dtype=np.uint8)
121
- frame = raw_data_array.reshape(self.shape)
122
- frame = frame[:, :, ::-1] # convert RGB to BGR
123
- return frame
124
-
125
- def __getitem__(self, key):
126
- if not isinstance(key, tuple):
127
- raise NotImplementedError("Only 2D slicing is supported")
128
-
129
- if len(key) != 2:
130
- raise NotImplementedError("Only 2D slicing is supported")
131
-
132
- if not all(isinstance(x, slice) for x in key):
133
- raise NotImplementedError("Only 2D slicing is supported")
134
-
135
- miny = key[0].start if key[0].start is not None else 0
136
- maxy = key[0].stop if key[0].stop is not None else self.shape[0]
137
- minx = key[1].start if key[1].start is not None else 0
138
- maxx = key[1].stop if key[1].stop is not None else self.shape[1]
139
-
140
- # handle negative indices
141
- if miny < 0:
142
- miny = self.shape[0] + miny
143
- if maxy < 0:
144
- maxy = self.shape[0] + maxy
145
- if minx < 0:
146
- minx = self.shape[1] + minx
147
- if maxx < 0:
148
- maxx = self.shape[1] + maxx
149
-
150
- if (
151
- maxy <= miny
152
- or maxx <= minx
153
- or miny < 0
154
- or minx < 0
155
- or maxy > self.shape[0]
156
- or maxx > self.shape[1]
157
- ):
158
- raise NotImplementedError("Invalid slice")
159
-
160
- f = _slice_mat(self._f, miny, maxy, minx, maxx)
161
- fmt = self._fmt.copy()
162
- fmt["width"] = maxx - minx
163
- fmt["height"] = maxy - miny
164
- return Frame(f, fmt)
165
-
166
- def __setitem__(self, key, value):
167
- value = frameify(value, "value")
168
-
169
- if not isinstance(key, tuple):
170
- raise NotImplementedError("Only 2D slicing is supported")
171
-
172
- if len(key) != 2:
173
- raise NotImplementedError("Only 2D slicing is supported")
174
-
175
- if not all(isinstance(x, slice) for x in key):
176
- raise NotImplementedError("Only 2D slicing is supported")
177
-
178
- miny = key[0].start if key[0].start is not None else 0
179
- maxy = key[0].stop if key[0].stop is not None else self.shape[0]
180
- minx = key[1].start if key[1].start is not None else 0
181
- maxx = key[1].stop if key[1].stop is not None else self.shape[1]
182
-
183
- # handle negative indices
184
- if miny < 0:
185
- miny = self.shape[0] + miny
186
- if maxy < 0:
187
- maxy = self.shape[0] + maxy
188
- if minx < 0:
189
- minx = self.shape[1] + minx
190
- if maxx < 0:
191
- maxx = self.shape[1] + maxx
192
-
193
- if (
194
- maxy <= miny
195
- or maxx <= minx
196
- or miny < 0
197
- or minx < 0
198
- or maxy > self.shape[0]
199
- or maxx > self.shape[1]
200
- ):
201
- raise NotImplementedError("Invalid slice")
202
-
203
- if value.shape[0] != maxy - miny or value.shape[1] != maxx - minx:
204
- raise NotImplementedError("Shape mismatch")
205
-
206
- self._mut()
207
- value._mut()
208
-
209
- self._f = _slice_write_mat(self._f, value._f, miny, maxy, minx, maxx)
210
-
211
-
212
- def _inline_frame(arr):
213
- assert arr.dtype == np.uint8
214
- assert arr.ndim == 3
215
- assert arr.shape[2] == 3
216
-
217
- # convert BGR to RGB
218
- arr = arr[:, :, ::-1]
219
-
220
- width = arr.shape[1]
221
- height = arr.shape[0]
222
- pix_fmt = "rgb24"
223
-
224
- f = _inline_mat(arr.tobytes(), width=width, height=height, pix_fmt=pix_fmt)
225
- fmt = {"width": width, "height": height, "pix_fmt": pix_fmt}
226
- return Frame(f, fmt)
227
-
228
-
229
- class VideoCapture:
230
- def __init__(self, path):
231
- self._path = path
232
- server = _server()
233
- self._source = vf.Source(server, str(uuid.uuid4()), path, 0)
234
- self._next_frame_idx = 0
235
-
236
- def isOpened(self):
237
- return True
238
-
239
- def get(self, prop):
240
- if prop == CAP_PROP_FPS:
241
- return _ts_to_fps(self._source.ts())
242
- elif prop == CAP_PROP_FRAME_WIDTH:
243
- return self._source.fmt()["width"]
244
- elif prop == CAP_PROP_FRAME_HEIGHT:
245
- return self._source.fmt()["height"]
246
- elif prop == CAP_PROP_FRAME_COUNT:
247
- return len(self._source.ts())
248
- elif prop == CAP_PROP_POS_FRAMES:
249
- return self._next_frame_idx
250
-
251
- raise Exception(f"Unknown property {prop}")
252
-
253
- def set(self, prop, value):
254
- if prop == CAP_PROP_POS_FRAMES:
255
- assert value >= 0 and value < len(self._source.ts())
256
- self._next_frame_idx = value
257
- elif prop == CAP_PROP_POS_MSEC:
258
- t = Fraction(value, 1000)
259
- ts = self._source.ts()
260
- next_frame_idx = bisect_right(ts, t)
261
- self._next_frame_idx = next_frame_idx
262
- else:
263
- raise Exception(f"Unsupported property {prop}")
264
-
265
- def read(self):
266
- if self._next_frame_idx >= len(self._source.ts()):
267
- return False, None
268
- frame = self._source.iloc[self._next_frame_idx]
269
- self._next_frame_idx += 1
270
- frame = Frame(frame, self._source.fmt())
271
- return True, frame
272
-
273
- def release(self):
274
- pass
275
-
276
-
277
- class VideoWriter:
278
- def __init__(self, path, fourcc, fps, size):
279
- assert isinstance(fourcc, VideoWriter_fourcc)
280
- if path is not None and not isinstance(path, str):
281
- raise Exception("path must be a string or None")
282
- self._path = path
283
- self._fourcc = fourcc
284
- self._fps = fps
285
- self._size = size
286
-
287
- self._frames = []
288
- self._pix_fmt = "yuv420p"
289
-
290
- def write(self, frame):
291
- frame = frameify(frame, "frame")
292
-
293
- if frame._fmt["pix_fmt"] != self._pix_fmt:
294
- f_obj = _filter_scale(frame._f, pix_fmt=self._pix_fmt)
295
- self._frames.append(f_obj)
296
- else:
297
- self._frames.append(frame._f)
298
-
299
- def release(self):
300
- if self._path is None:
301
- return
302
-
303
- spec = self.spec()
304
- server = _server()
305
- spec.save(server, self._path)
306
-
307
- def spec(self) -> vf.Spec:
308
- fmt = {
309
- "width": self._size[0],
310
- "height": self._size[1],
311
- "pix_fmt": self._pix_fmt,
312
- }
313
- domain = _fps_to_ts(self._fps, len(self._frames))
314
- spec = vf.Spec(domain, lambda t, i: self._frames[i], fmt)
315
- return spec
316
-
317
-
318
- class VideoWriter_fourcc:
319
- def __init__(self, *args):
320
- self._args = args
321
-
322
-
323
- def frameify(obj, field_name=None):
324
- """
325
- Turn an object (e.g., ndarray) into a Frame.
326
- """
327
-
328
- if isinstance(obj, Frame):
329
- return obj
330
- elif isinstance(obj, np.ndarray):
331
- return _inline_frame(obj)
332
- else:
333
- if field_name is not None:
334
- raise Exception(
335
- f"Unsupported type for field {field_name}, expected Frame or np.ndarray"
336
- )
337
- else:
338
- raise Exception("Unsupported type, expected Frame or np.ndarray")
339
-
340
-
341
- def imread(path, *args):
342
- if len(args) > 0:
343
- raise NotImplementedError("imread does not support additional arguments")
344
-
345
- assert path.lower().endswith((".jpg", ".jpeg", ".png"))
346
- server = _server()
347
- source = vf.Source(server, str(uuid.uuid4()), path, 0)
348
- frame = Frame(source.iloc[0], source.fmt())
349
- return frame
350
-
351
-
352
- def imwrite(path, img, *args):
353
- if len(args) > 0:
354
- raise NotImplementedError("imwrite does not support additional arguments")
355
-
356
- img = frameify(img)
357
-
358
- fmt = img._fmt.copy()
359
- width = fmt["width"]
360
- height = fmt["height"]
361
- f = img._f
362
-
363
- domain = [Fraction(0, 1)]
364
-
365
- if path.lower().endswith(".png"):
366
- img._mut() # Make sure it's in rgb24
367
- spec = vf.Spec(
368
- domain,
369
- lambda t, i: img._f,
370
- {"width": width, "height": height, "pix_fmt": "rgb24"},
371
- )
372
- spec.save(_server(), path, encoder="png")
373
- elif path.lower().endswith((".jpg", ".jpeg")):
374
- if img._modified:
375
- # it's rgb24, we need to convert to something jpeg can handle
376
- f = _filter_scale(img._f, pix_fmt="yuv420p")
377
- fmt["pix_fmt"] = "yuv420p"
378
- else:
379
- if fmt["pix_fmt"] not in ["yuvj420p", "yuvj422p", "yuvj444p"]:
380
- f = _filter_scale(img._f, pix_fmt="yuvj420p")
381
- fmt["pix_fmt"] = "yuvj420p"
382
-
383
- spec = vf.Spec(domain, lambda t, i: f, fmt)
384
- spec.save(_server(), path, encoder="mjpeg")
385
- else:
386
- raise Exception("Unsupported image format")
387
-
388
-
389
- def vidplay(video, *args, **kwargs):
390
- """
391
- Play a vidformer video specification.
392
-
393
- Args:
394
- video: one of [vidformer.Spec, vidformer.Source, vidformer.cv2.VideoWriter]
395
- """
396
-
397
- if isinstance(video, vf.Spec):
398
- return video.play(_server(), *args, **kwargs)
399
- elif isinstance(video, vf.Source):
400
- return video.play(_server(), *args, **kwargs)
401
- elif isinstance(video, VideoWriter):
402
- return video.spec().play(_server(), *args, **kwargs)
403
- else:
404
- raise Exception("Unsupported video type to vidplay")
405
-
406
-
407
- def rectangle(img, pt1, pt2, color, thickness=None, lineType=None, shift=None):
408
- """
409
- cv.rectangle( img, pt1, pt2, color[, thickness[, lineType[, shift]]] )
410
- """
411
-
412
- img = frameify(img)
413
- img._mut()
414
-
415
- assert len(pt1) == 2
416
- assert len(pt2) == 2
417
- assert all(isinstance(x, int) for x in pt1)
418
- assert all(isinstance(x, int) for x in pt2)
419
-
420
- assert len(color) == 3 or len(color) == 4
421
- color = [float(x) for x in color]
422
- if len(color) == 3:
423
- color.append(255.0)
424
-
425
- args = []
426
- if thickness is not None:
427
- assert isinstance(thickness, int)
428
- args.append(thickness)
429
- if lineType is not None:
430
- assert isinstance(lineType, int)
431
- assert thickness is not None
432
- args.append(lineType)
433
- if shift is not None:
434
- assert isinstance(shift, int)
435
- assert shift is not None
436
- args.append(shift)
437
-
438
- img._f = _filter_rectangle(img._f, pt1, pt2, color, *args)
439
-
440
-
441
- def putText(
442
- img,
443
- text,
444
- org,
445
- fontFace,
446
- fontScale,
447
- color,
448
- thickness=None,
449
- lineType=None,
450
- bottomLeftOrigin=None,
451
- ):
452
- """
453
- cv.putText( img, text, org, fontFace, fontScale, color[, thickness[, lineType[, bottomLeftOrigin]]] )
454
- """
455
-
456
- img = frameify(img)
457
- img._mut()
458
-
459
- assert isinstance(text, str)
460
-
461
- assert len(org) == 2
462
- assert all(isinstance(x, int) for x in org)
463
-
464
- assert isinstance(fontFace, int)
465
- assert isinstance(fontScale, float) or isinstance(fontScale, int)
466
- fontScale = float(fontScale)
467
-
468
- assert len(color) == 3 or len(color) == 4
469
- color = [float(x) for x in color]
470
- if len(color) == 3:
471
- color.append(255.0)
472
-
473
- args = []
474
- if thickness is not None:
475
- assert isinstance(thickness, int)
476
- args.append(thickness)
477
- if lineType is not None:
478
- assert isinstance(lineType, int)
479
- assert thickness is not None
480
- args.append(lineType)
481
- if bottomLeftOrigin is not None:
482
- assert isinstance(bottomLeftOrigin, bool)
483
- assert lineType is not None
484
- args.append(bottomLeftOrigin)
485
-
486
- img._f = _filter_putText(img._f, text, org, fontFace, fontScale, color, *args)
487
-
488
-
489
- def arrowedLine(
490
- img, pt1, pt2, color, thickness=None, line_type=None, shift=None, tipLength=None
491
- ):
492
- """
493
- cv.arrowedLine( img, pt1, pt2, color[, thickness[, line_type[, shift[, tipLength]]]] )
494
- """
495
- img = frameify(img)
496
- img._mut()
497
-
498
- assert len(pt1) == 2
499
- assert len(pt2) == 2
500
- assert all(isinstance(x, int) for x in pt1)
501
- assert all(isinstance(x, int) for x in pt2)
502
-
503
- assert len(color) == 3 or len(color) == 4
504
- color = [float(x) for x in color]
505
- if len(color) == 3:
506
- color.append(255.0)
507
-
508
- args = []
509
- if thickness is not None:
510
- assert isinstance(thickness, int)
511
- args.append(thickness)
512
- if line_type is not None:
513
- assert isinstance(line_type, int)
514
- assert thickness is not None
515
- args.append(line_type)
516
- if shift is not None:
517
- assert isinstance(shift, int)
518
- assert shift is not None
519
- args.append(shift)
520
- if tipLength is not None:
521
- assert isinstance(tipLength, float)
522
- assert shift is not None
523
- args.append(tipLength)
524
-
525
- img._f = _filter_arrowedLine(img._f, pt1, pt2, color, *args)
526
-
527
-
528
- def line(img, pt1, pt2, color, thickness=None, lineType=None, shift=None):
529
- img = frameify(img)
530
- img._mut()
531
-
532
- assert len(pt1) == 2
533
- assert len(pt2) == 2
534
- assert all(isinstance(x, int) for x in pt1)
535
- assert all(isinstance(x, int) for x in pt2)
536
-
537
- assert len(color) == 3 or len(color) == 4
538
- color = [float(x) for x in color]
539
- if len(color) == 3:
540
- color.append(255.0)
541
-
542
- args = []
543
- if thickness is not None:
544
- assert isinstance(thickness, int)
545
- args.append(thickness)
546
- if lineType is not None:
547
- assert isinstance(lineType, int)
548
- assert thickness is not None
549
- args.append(lineType)
550
- if shift is not None:
551
- assert isinstance(shift, int)
552
- assert shift is not None
553
- args.append(shift)
554
-
555
- img._f = _filter_line(img._f, pt1, pt2, color, *args)
556
-
557
-
558
- def circle(img, center, radius, color, thickness=None, lineType=None, shift=None):
559
- img = frameify(img)
560
- img._mut()
561
-
562
- assert len(center) == 2
563
- assert all(isinstance(x, int) for x in center)
564
-
565
- assert isinstance(radius, int)
566
-
567
- assert len(color) == 3 or len(color) == 4
568
- color = [float(x) for x in color]
569
- if len(color) == 3:
570
- color.append(255.0)
571
-
572
- args = []
573
- if thickness is not None:
574
- assert isinstance(thickness, int)
575
- args.append(thickness)
576
- if lineType is not None:
577
- assert isinstance(lineType, int)
578
- assert thickness is not None
579
- args.append(lineType)
580
- if shift is not None:
581
- assert isinstance(shift, int)
582
- assert shift is not None
583
- args.append(shift)
584
-
585
- img._f = _filter_circle(img._f, center, radius, color, *args)
586
-
587
-
588
- def getFontScaleFromHeight(*args, **kwargs):
589
- """
590
- cv.getFontScaleFromHeight( fontFace, pixelHeight[, thickness] )
591
- """
592
- if _opencv2 is None:
593
- raise NotImplementedError("getFontScaleFromHeight requires the cv2 module")
594
- return _opencv2.getFontScaleFromHeight(*args, **kwargs)
595
-
596
-
597
- def getTextSize(*args, **kwargs):
598
- """
599
- cv.getTextSize( text, fontFace, fontScale, thickness )
600
- """
601
- if _opencv2 is None:
602
- raise NotImplementedError("getTextSize requires the cv2 module")
603
- return _opencv2.getTextSize(*args, **kwargs)
604
-
605
-
606
- def addWeighted(src1, alpha, src2, beta, gamma, dst=None, dtype=-1):
607
- """
608
- cv.addWeighted( src1, alpha, src2, beta, gamma[, dst[, dtype]] ) -> dst
609
- """
610
- src1 = frameify(src1, "src1")
611
- src2 = frameify(src2, "src2")
612
- src1._mut()
613
- src2._mut()
614
-
615
- if dst is None:
616
- dst = Frame(src1._f, src1._fmt.copy())
617
- else:
618
- assert isinstance(dst, Frame), "dst must be a Frame"
619
- dst._mut()
620
-
621
- assert isinstance(alpha, float) or isinstance(alpha, int)
622
- assert isinstance(beta, float) or isinstance(beta, int)
623
- assert isinstance(gamma, float) or isinstance(gamma, int)
624
- alpha = float(alpha)
625
- beta = float(beta)
626
- gamma = float(gamma)
627
-
628
- if dtype != -1:
629
- raise Exception("addWeighted does not support the dtype argument")
630
-
631
- dst._f = _filter_addWeighted(src1._f, alpha, src2._f, beta, gamma)
632
- return dst
633
-
634
-
635
- # Stubs for unimplemented functions
636
-
637
-
638
- def clipLine(*args, **kwargs):
639
- raise NotImplementedError("clipLine is not yet implemented in the cv2 frontend")
640
-
641
-
642
- def drawContours(*args, **kwargs):
643
- raise NotImplementedError("drawContours is not yet implemented in the cv2 frontend")
644
-
645
-
646
- def drawMarker(*args, **kwargs):
647
- raise NotImplementedError("drawMarker is not yet implemented in the cv2 frontend")
648
-
649
-
650
- def ellipse(*args, **kwargs):
651
- raise NotImplementedError("ellipse is not yet implemented in the cv2 frontend")
652
-
653
-
654
- def ellipse2Poly(*args, **kwargs):
655
- raise NotImplementedError("ellipse2Poly is not yet implemented in the cv2 frontend")
656
-
657
-
658
- def fillConvexPoly(*args, **kwargs):
659
- raise NotImplementedError(
660
- "fillConvexPoly is not yet implemented in the cv2 frontend"
661
- )
662
-
663
-
664
- def fillPoly(*args, **kwargs):
665
- raise NotImplementedError("fillPoly is not yet implemented in the cv2 frontend")
666
-
667
-
668
- def polylines(*args, **kwargs):
669
- raise NotImplementedError("polylines is not yet implemented in the cv2 frontend")