numpyimage 3.6.3__tar.gz → 3.7.1__tar.gz
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.
- {numpyimage-3.6.3 → numpyimage-3.7.1}/PKG-INFO +18 -2
- {numpyimage-3.6.3 → numpyimage-3.7.1}/README.md +6 -0
- {numpyimage-3.6.3 → numpyimage-3.7.1}/npimage/align.py +15 -6
- {numpyimage-3.6.3 → numpyimage-3.7.1}/npimage/imageio.py +26 -0
- {numpyimage-3.6.3 → numpyimage-3.7.1}/npimage/vidio.py +13 -3
- {numpyimage-3.6.3 → numpyimage-3.7.1}/numpyimage.egg-info/PKG-INFO +18 -2
- numpyimage-3.7.1/numpyimage.egg-info/requires.txt +24 -0
- {numpyimage-3.6.3 → numpyimage-3.7.1}/pyproject.toml +22 -3
- numpyimage-3.7.1/tests/test_heic.py +58 -0
- numpyimage-3.7.1/tests/test_pbm.py +37 -0
- numpyimage-3.7.1/tests/test_video_streamer.py +71 -0
- numpyimage-3.7.1/tests/test_video_writers.py +37 -0
- numpyimage-3.6.3/numpyimage.egg-info/requires.txt +0 -11
- numpyimage-3.6.3/tests/test_heic.py +0 -61
- numpyimage-3.6.3/tests/test_pbm.py +0 -61
- numpyimage-3.6.3/tests/test_video_streamer.py +0 -92
- numpyimage-3.6.3/tests/test_video_writers.py +0 -170
- {numpyimage-3.6.3 → numpyimage-3.7.1}/LICENSE +0 -0
- {numpyimage-3.6.3 → numpyimage-3.7.1}/npimage/__init__.py +0 -0
- {numpyimage-3.6.3 → numpyimage-3.7.1}/npimage/graphics.py +0 -0
- {numpyimage-3.6.3 → numpyimage-3.7.1}/npimage/nrrd_utils.py +0 -0
- {numpyimage-3.6.3 → numpyimage-3.7.1}/npimage/operations.py +0 -0
- {numpyimage-3.6.3 → numpyimage-3.7.1}/npimage/utils.py +0 -0
- {numpyimage-3.6.3 → numpyimage-3.7.1}/numpyimage.egg-info/SOURCES.txt +0 -0
- {numpyimage-3.6.3 → numpyimage-3.7.1}/numpyimage.egg-info/dependency_links.txt +0 -0
- {numpyimage-3.6.3 → numpyimage-3.7.1}/numpyimage.egg-info/top_level.txt +0 -0
- {numpyimage-3.6.3 → numpyimage-3.7.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: numpyimage
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.7.1
|
|
4
4
|
Summary: Load, save, & manipulate image files as numpy arrays
|
|
5
5
|
Author-email: Jasper Phelps <jasper.s.phelps@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -42,12 +42,28 @@ Requires-Dist: pillow-heif
|
|
|
42
42
|
Requires-Dist: tifffile
|
|
43
43
|
Requires-Dist: pynrrd
|
|
44
44
|
Requires-Dist: matplotlib
|
|
45
|
-
|
|
45
|
+
Provides-Extra: vid
|
|
46
|
+
Requires-Dist: av; extra == "vid"
|
|
47
|
+
Provides-Extra: align
|
|
48
|
+
Requires-Dist: opencv-python-headless; extra == "align"
|
|
46
49
|
Provides-Extra: all
|
|
47
50
|
Requires-Dist: av; extra == "all"
|
|
51
|
+
Requires-Dist: opencv-python-headless; extra == "all"
|
|
52
|
+
Provides-Extra: dev
|
|
53
|
+
Requires-Dist: av; extra == "dev"
|
|
54
|
+
Requires-Dist: opencv-python-headless; extra == "dev"
|
|
55
|
+
Requires-Dist: pytest; extra == "dev"
|
|
56
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
57
|
+
Requires-Dist: psutil; extra == "dev"
|
|
48
58
|
Dynamic: license-file
|
|
49
59
|
|
|
50
60
|
# npimage
|
|
61
|
+
|
|
62
|
+
[](https://github.com/jasper-tms/npimage/actions/workflows/tests.yml)
|
|
63
|
+
[](https://codecov.io/gh/jasper-tms/npimage)
|
|
64
|
+
[](https://pypi.org/project/numpyimage/)
|
|
65
|
+
[](https://pypi.org/project/numpyimage/)
|
|
66
|
+
[](https://github.com/jasper-tms/npimage/blob/main/LICENSE)
|
|
51
67
|
Need to load pixel values from image files as numpy arrays, and hate having to remember whether you should use PIL, tifffile, matplotlib, or something else? Hate having to deal with the fact that those libraries all use different function names and syntaxes? Wish you could just provide a filename and get back a numpy array? This library's `imageio.py` does that, with `array = npimage.load(filename)`, `npimage.save(array, filename)`, and `npimage.show(array)` functions that let you easily handle a number of common image file formats without having to remember library-specific syntax. Additionally, `vidio.py` provides `array = npimage.load_video(filename)` and `npimage.save_video(array, filename)` for videos as well. (Another similar library to consider using is [imageio](https://pypi.org/project/imageio/).)
|
|
52
68
|
|
|
53
69
|
Want to draw simple shapes like lines, triangles, and circles into 3D numpy arrays? Frustrated that the python libraries you can find online like `opencv` and `skimage.draw` work on 2D arrays but not 3D? I wrote some functions in `graphics.py` that do the trick in 3D. (If you know of another library that can do this, please let me know!)
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
# npimage
|
|
2
|
+
|
|
3
|
+
[](https://github.com/jasper-tms/npimage/actions/workflows/tests.yml)
|
|
4
|
+
[](https://codecov.io/gh/jasper-tms/npimage)
|
|
5
|
+
[](https://pypi.org/project/numpyimage/)
|
|
6
|
+
[](https://pypi.org/project/numpyimage/)
|
|
7
|
+
[](https://github.com/jasper-tms/npimage/blob/main/LICENSE)
|
|
2
8
|
Need to load pixel values from image files as numpy arrays, and hate having to remember whether you should use PIL, tifffile, matplotlib, or something else? Hate having to deal with the fact that those libraries all use different function names and syntaxes? Wish you could just provide a filename and get back a numpy array? This library's `imageio.py` does that, with `array = npimage.load(filename)`, `npimage.save(array, filename)`, and `npimage.show(array)` functions that let you easily handle a number of common image file formats without having to remember library-specific syntax. Additionally, `vidio.py` provides `array = npimage.load_video(filename)` and `npimage.save_video(array, filename)` for videos as well. (Another similar library to consider using is [imageio](https://pypi.org/project/imageio/).)
|
|
3
9
|
|
|
4
10
|
Want to draw simple shapes like lines, triangles, and circles into 3D numpy arrays? Frustrated that the python libraries you can find online like `opencv` and `skimage.draw` work on 2D arrays but not 3D? I wrote some functions in `graphics.py` that do the trick in 3D. (If you know of another library that can do this, please let me know!)
|
|
@@ -2,18 +2,15 @@
|
|
|
2
2
|
"""
|
|
3
3
|
Functions for aligning images.
|
|
4
4
|
"""
|
|
5
|
-
from typing import Optional, Tuple
|
|
5
|
+
from typing import Optional, Tuple
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
|
-
import cv2 as cv
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
def find_landmark(image: np.ndarray,
|
|
12
11
|
landmark: np.ndarray,
|
|
13
12
|
search_bbox: Optional[Tuple[Tuple[int, int], Tuple[int, int]]] = None,
|
|
14
|
-
metric:
|
|
15
|
-
cv.TM_CCORR, cv.TM_CCORR_NORMED,
|
|
16
|
-
cv.TM_CCOEFF, cv.TM_CCOEFF_NORMED] = cv.TM_CCOEFF_NORMED,
|
|
13
|
+
metric: Optional[int] = None,
|
|
17
14
|
subpixel_accuracy: bool = True
|
|
18
15
|
) -> Tuple[Tuple[int, int], float]:
|
|
19
16
|
"""
|
|
@@ -45,7 +42,8 @@ def find_landmark(image: np.ndarray,
|
|
|
45
42
|
metric : cv.TemplateMatchModes, optional
|
|
46
43
|
The metric to use to compare the landmark to the image. Options are
|
|
47
44
|
cv.TM_SQDIFF, cv.TM_SQDIFF_NORMED, cv.TM_CCORR, cv.TM_CCORR_NORMED,
|
|
48
|
-
cv.TM_CCOEFF, cv.TM_CCOEFF_NORMED.
|
|
45
|
+
cv.TM_CCOEFF, cv.TM_CCOEFF_NORMED.
|
|
46
|
+
If left as None, defaults to cv.TM_CCOEFF_NORMED.
|
|
49
47
|
|
|
50
48
|
Returns
|
|
51
49
|
-------
|
|
@@ -57,6 +55,17 @@ def find_landmark(image: np.ndarray,
|
|
|
57
55
|
The score of the match, from 0 to 1. A score of 1 indicates
|
|
58
56
|
a perfect match.
|
|
59
57
|
"""
|
|
58
|
+
try:
|
|
59
|
+
import cv2 as cv
|
|
60
|
+
except ImportError as e:
|
|
61
|
+
raise ImportError(
|
|
62
|
+
'find_landmark requires opencv. Install it with '
|
|
63
|
+
'`pip install numpyimage[align]` or `pip install opencv-python-headless`.'
|
|
64
|
+
) from e
|
|
65
|
+
|
|
66
|
+
if metric is None:
|
|
67
|
+
metric = cv.TM_CCOEFF_NORMED
|
|
68
|
+
|
|
60
69
|
if search_bbox is not None:
|
|
61
70
|
if all(isinstance(el, slice) for el in search_bbox):
|
|
62
71
|
img_to_search = image[search_bbox[0], search_bbox[1]]
|
|
@@ -452,6 +452,32 @@ def _ensure_heif_opener_registered() -> None:
|
|
|
452
452
|
"""Register the HEIF opener if not already registered."""
|
|
453
453
|
global _heif_opener_registered
|
|
454
454
|
if not _heif_opener_registered:
|
|
455
|
+
_check_pyav_not_loaded_before_heif()
|
|
455
456
|
from pillow_heif import register_heif_opener
|
|
456
457
|
register_heif_opener()
|
|
457
458
|
_heif_opener_registered = True
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _check_pyav_not_loaded_before_heif() -> None:
|
|
462
|
+
"""Raise a clear error if PyAV has been imported before HEIF.
|
|
463
|
+
|
|
464
|
+
On macOS, loading PyAV's bundled native libraries before libheif causes a
|
|
465
|
+
segfault inside pillow_heif when it first initializes. Loading libheif
|
|
466
|
+
first (or not loading PyAV at all) avoids the crash. Once libheif is
|
|
467
|
+
loaded, further HEIF operations and PyAV use coexist fine in the same
|
|
468
|
+
process.
|
|
469
|
+
"""
|
|
470
|
+
import sys
|
|
471
|
+
if sys.platform != 'darwin' or 'av' not in sys.modules:
|
|
472
|
+
return
|
|
473
|
+
raise RuntimeError(
|
|
474
|
+
'Cannot open a HEIC file: PyAV (`av`) has already been imported in '
|
|
475
|
+
'this process, which on macOS causes pillow_heif to segfault when it '
|
|
476
|
+
'first loads libheif.\n\n'
|
|
477
|
+
'Workarounds:\n'
|
|
478
|
+
' 1. Open/save your HEIC files before any code path that imports '
|
|
479
|
+
'`av` runs (e.g. before using npimage.VideoStreamer, AVVideoWriter, '
|
|
480
|
+
'or any other code that uses PyAV).\n'
|
|
481
|
+
' 2. Do HEIC I/O in a separate Python process from your video I/O.\n'
|
|
482
|
+
' 3. Use a different image format (PNG, JPEG, TIFF).'
|
|
483
|
+
)
|
|
@@ -249,7 +249,7 @@ class VideoStreamer:
|
|
|
249
249
|
self._build_index(cache_index=cache_index)
|
|
250
250
|
|
|
251
251
|
def _build_index(self, cache_index='auto'):
|
|
252
|
-
if self.index_filename.exists():
|
|
252
|
+
if cache_index and self.index_filename.exists():
|
|
253
253
|
return self._load_index()
|
|
254
254
|
if self.verbose:
|
|
255
255
|
print('Building frame timestamp index for fast random frame access...')
|
|
@@ -301,9 +301,8 @@ class VideoStreamer:
|
|
|
301
301
|
if len(frames_pts) == 0:
|
|
302
302
|
raise RuntimeError('No timestamps found in frame metadata')
|
|
303
303
|
|
|
304
|
-
self.frames_pts = frames_pts
|
|
305
304
|
self.n_frames = len(frames_pts)
|
|
306
|
-
self.pts0 =
|
|
305
|
+
self.pts0 = frames_pts[0]
|
|
307
306
|
self.rotation = _get_rotation_from_metadata(self.filename)
|
|
308
307
|
index = {}
|
|
309
308
|
|
|
@@ -319,10 +318,14 @@ class VideoStreamer:
|
|
|
319
318
|
else:
|
|
320
319
|
index['framerate'] = {'numerator': self._framerate.numerator,
|
|
321
320
|
'denominator': self._framerate.denominator}
|
|
321
|
+
self.frames_pts = range(self.pts0,
|
|
322
|
+
self.pts0 + self.pts_delta * self.n_frames,
|
|
323
|
+
self.pts_delta)
|
|
322
324
|
else:
|
|
323
325
|
# The video is variable framerate
|
|
324
326
|
self._framerate = 'variable'
|
|
325
327
|
index['framerate'] = 'variable'
|
|
328
|
+
self.frames_pts = frames_pts
|
|
326
329
|
|
|
327
330
|
index['n_frames'] = self.n_frames
|
|
328
331
|
index['rotation'] = self.rotation
|
|
@@ -368,6 +371,9 @@ class VideoStreamer:
|
|
|
368
371
|
raise ValueError('pts_delta does not appear to be an integer. This is'
|
|
369
372
|
' unexpected and may indicate a malformed index.')
|
|
370
373
|
self.pts_delta = self.pts_delta.numerator
|
|
374
|
+
self.frames_pts = range(self.pts0,
|
|
375
|
+
self.pts0 + self.pts_delta * self.n_frames,
|
|
376
|
+
self.pts_delta)
|
|
371
377
|
|
|
372
378
|
@property
|
|
373
379
|
def framerate(self) -> Union[float, Literal['variable']]:
|
|
@@ -394,6 +400,10 @@ class VideoStreamer:
|
|
|
394
400
|
return 'variable'
|
|
395
401
|
return float(1.0 / self._framerate)
|
|
396
402
|
|
|
403
|
+
@property
|
|
404
|
+
def duration(self) -> float:
|
|
405
|
+
return float((self.frames_pts[-1] - self.frames_pts[0]) * self.time_base)
|
|
406
|
+
|
|
397
407
|
def frame_number_to_pts(self, frame_number: int) -> int:
|
|
398
408
|
if hasattr(frame_number, '__iter__'):
|
|
399
409
|
return [self.frame_number_to_pts(n) for n in frame_number]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: numpyimage
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.7.1
|
|
4
4
|
Summary: Load, save, & manipulate image files as numpy arrays
|
|
5
5
|
Author-email: Jasper Phelps <jasper.s.phelps@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -42,12 +42,28 @@ Requires-Dist: pillow-heif
|
|
|
42
42
|
Requires-Dist: tifffile
|
|
43
43
|
Requires-Dist: pynrrd
|
|
44
44
|
Requires-Dist: matplotlib
|
|
45
|
-
|
|
45
|
+
Provides-Extra: vid
|
|
46
|
+
Requires-Dist: av; extra == "vid"
|
|
47
|
+
Provides-Extra: align
|
|
48
|
+
Requires-Dist: opencv-python-headless; extra == "align"
|
|
46
49
|
Provides-Extra: all
|
|
47
50
|
Requires-Dist: av; extra == "all"
|
|
51
|
+
Requires-Dist: opencv-python-headless; extra == "all"
|
|
52
|
+
Provides-Extra: dev
|
|
53
|
+
Requires-Dist: av; extra == "dev"
|
|
54
|
+
Requires-Dist: opencv-python-headless; extra == "dev"
|
|
55
|
+
Requires-Dist: pytest; extra == "dev"
|
|
56
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
57
|
+
Requires-Dist: psutil; extra == "dev"
|
|
48
58
|
Dynamic: license-file
|
|
49
59
|
|
|
50
60
|
# npimage
|
|
61
|
+
|
|
62
|
+
[](https://github.com/jasper-tms/npimage/actions/workflows/tests.yml)
|
|
63
|
+
[](https://codecov.io/gh/jasper-tms/npimage)
|
|
64
|
+
[](https://pypi.org/project/numpyimage/)
|
|
65
|
+
[](https://pypi.org/project/numpyimage/)
|
|
66
|
+
[](https://github.com/jasper-tms/npimage/blob/main/LICENSE)
|
|
51
67
|
Need to load pixel values from image files as numpy arrays, and hate having to remember whether you should use PIL, tifffile, matplotlib, or something else? Hate having to deal with the fact that those libraries all use different function names and syntaxes? Wish you could just provide a filename and get back a numpy array? This library's `imageio.py` does that, with `array = npimage.load(filename)`, `npimage.save(array, filename)`, and `npimage.show(array)` functions that let you easily handle a number of common image file formats without having to remember library-specific syntax. Additionally, `vidio.py` provides `array = npimage.load_video(filename)` and `npimage.save_video(array, filename)` for videos as well. (Another similar library to consider using is [imageio](https://pypi.org/project/imageio/).)
|
|
52
68
|
|
|
53
69
|
Want to draw simple shapes like lines, triangles, and circles into 3D numpy arrays? Frustrated that the python libraries you can find online like `opencv` and `skimage.draw` work on 2D arrays but not 3D? I wrote some functions in `graphics.py` that do the trick in 3D. (If you know of another library that can do this, please let me know!)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
numpy
|
|
2
|
+
tqdm
|
|
3
|
+
pillow
|
|
4
|
+
pillow-heif
|
|
5
|
+
tifffile
|
|
6
|
+
pynrrd
|
|
7
|
+
matplotlib
|
|
8
|
+
|
|
9
|
+
[align]
|
|
10
|
+
opencv-python-headless
|
|
11
|
+
|
|
12
|
+
[all]
|
|
13
|
+
av
|
|
14
|
+
opencv-python-headless
|
|
15
|
+
|
|
16
|
+
[dev]
|
|
17
|
+
av
|
|
18
|
+
opencv-python-headless
|
|
19
|
+
pytest
|
|
20
|
+
pytest-cov
|
|
21
|
+
psutil
|
|
22
|
+
|
|
23
|
+
[vid]
|
|
24
|
+
av
|
|
@@ -4,7 +4,7 @@ build-backend = 'setuptools.build_meta'
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = 'numpyimage'
|
|
7
|
-
version = '3.
|
|
7
|
+
version = '3.7.1'
|
|
8
8
|
description = 'Load, save, & manipulate image files as numpy arrays'
|
|
9
9
|
readme.file = 'README.md'
|
|
10
10
|
readme.content-type = 'text/markdown'
|
|
@@ -28,12 +28,31 @@ dependencies = [
|
|
|
28
28
|
'tifffile',
|
|
29
29
|
'pynrrd',
|
|
30
30
|
'matplotlib',
|
|
31
|
-
'opencv-python-headless',
|
|
32
31
|
]
|
|
33
32
|
|
|
34
33
|
[project.optional-dependencies]
|
|
34
|
+
vid = [
|
|
35
|
+
'av',
|
|
36
|
+
]
|
|
37
|
+
align = [
|
|
38
|
+
'opencv-python-headless',
|
|
39
|
+
]
|
|
35
40
|
all = [
|
|
36
|
-
'av'
|
|
41
|
+
'av',
|
|
42
|
+
'opencv-python-headless',
|
|
43
|
+
]
|
|
44
|
+
dev = [
|
|
45
|
+
'av',
|
|
46
|
+
'opencv-python-headless',
|
|
47
|
+
'pytest',
|
|
48
|
+
'pytest-cov',
|
|
49
|
+
'psutil',
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[tool.pytest.ini_options]
|
|
53
|
+
testpaths = ['tests']
|
|
54
|
+
markers = [
|
|
55
|
+
'slow: marks tests that shell out to ffmpeg or write real video files (deselect with -m "not slow")',
|
|
37
56
|
]
|
|
38
57
|
|
|
39
58
|
[tool.setuptools.packages.find]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Tests for HEIC read/write support."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
import textwrap
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
import npimage
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_heic_extension_registered():
|
|
14
|
+
"""HEIC is in the set of supported file extensions."""
|
|
15
|
+
assert 'heic' in npimage.imageio.supported_extensions
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_heic_load_save_roundtrip(tmp_path):
|
|
19
|
+
"""A uint8 RGB image roundtripped through HEIC keeps its shape and dtype."""
|
|
20
|
+
data = np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8)
|
|
21
|
+
path = tmp_path / 'test_image.heic'
|
|
22
|
+
|
|
23
|
+
npimage.save(data, str(path))
|
|
24
|
+
assert path.exists()
|
|
25
|
+
|
|
26
|
+
loaded = npimage.load(str(path))
|
|
27
|
+
assert loaded.shape == data.shape
|
|
28
|
+
assert loaded.dtype == np.uint8
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.mark.skipif(sys.platform != 'darwin', reason='guard only fires on macOS')
|
|
32
|
+
def test_heic_guards_against_pyav_already_imported(tmp_path):
|
|
33
|
+
"""
|
|
34
|
+
On macOS, importing PyAV before libheif causes a segfault inside
|
|
35
|
+
pillow_heif. npimage guards against this by raising a RuntimeError with a
|
|
36
|
+
clear message instead of letting the segfault happen.
|
|
37
|
+
"""
|
|
38
|
+
script = textwrap.dedent(f"""
|
|
39
|
+
import av # noqa: F401
|
|
40
|
+
import numpy as np
|
|
41
|
+
import npimage
|
|
42
|
+
data = np.zeros((10, 10, 3), dtype=np.uint8)
|
|
43
|
+
try:
|
|
44
|
+
npimage.save(data, r'{tmp_path / "x.heic"}')
|
|
45
|
+
except RuntimeError as e:
|
|
46
|
+
print('GUARD_FIRED:', e)
|
|
47
|
+
raise SystemExit(0)
|
|
48
|
+
raise SystemExit('guard did not fire')
|
|
49
|
+
""")
|
|
50
|
+
result = subprocess.run(
|
|
51
|
+
[sys.executable, '-c', script], capture_output=True, text=True
|
|
52
|
+
)
|
|
53
|
+
assert result.returncode == 0, (
|
|
54
|
+
f'subprocess exited {result.returncode}. stdout={result.stdout!r} '
|
|
55
|
+
f'stderr={result.stderr!r}'
|
|
56
|
+
)
|
|
57
|
+
assert 'GUARD_FIRED:' in result.stdout
|
|
58
|
+
assert 'PyAV' in result.stdout
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Tests for PBM read/write roundtripping."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
import npimage
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _roundtrip(data, tmp_path, filename):
|
|
9
|
+
path = tmp_path / filename
|
|
10
|
+
npimage.save(data, str(path))
|
|
11
|
+
|
|
12
|
+
expected_size = npimage.filetypes.pbm.predict_file_size(data)
|
|
13
|
+
assert path.stat().st_size == expected_size
|
|
14
|
+
|
|
15
|
+
loaded = npimage.load(str(path))
|
|
16
|
+
assert np.array_equal(data, loaded)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_pbm_arbitrary_width(tmp_path):
|
|
20
|
+
"""PBM roundtrip with a width that is not a multiple of 8."""
|
|
21
|
+
data = np.array([
|
|
22
|
+
[1, 0, 1, 0, 1],
|
|
23
|
+
[0, 1, 0, 1, 0],
|
|
24
|
+
[1, 1, 1, 0, 0],
|
|
25
|
+
], dtype=bool)
|
|
26
|
+
_roundtrip(data, tmp_path, 'arbitrary_width.pbm')
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_pbm_width_multiple_of_8(tmp_path):
|
|
30
|
+
"""PBM roundtrip with a width that is a multiple of 8."""
|
|
31
|
+
data = np.array([
|
|
32
|
+
[1, 0, 1, 0, 1, 0, 1, 0],
|
|
33
|
+
[0, 1, 0, 1, 0, 1, 0, 1],
|
|
34
|
+
[1, 1, 1, 1, 0, 0, 0, 0],
|
|
35
|
+
[0, 0, 0, 0, 1, 1, 1, 1],
|
|
36
|
+
], dtype=bool)
|
|
37
|
+
_roundtrip(data, tmp_path, 'multiple_of_8.pbm')
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for VideoStreamer, particularly handling of HEVC videos with
|
|
3
|
+
negative-PTS priming packets (common in iPhone recordings).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import subprocess
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
import npimage
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.mark.slow
|
|
15
|
+
def test_negative_pts_priming_packet(tmp_path):
|
|
16
|
+
"""
|
|
17
|
+
VideoStreamer correctly handles HEVC videos where the first packet has a
|
|
18
|
+
negative PTS (a "priming" packet used for B-frame prediction that doesn't
|
|
19
|
+
produce a decoded frame).
|
|
20
|
+
|
|
21
|
+
This is common in iPhone HEVC recordings. The bug was that the index was
|
|
22
|
+
built from demuxed packets, which included the priming packet's negative
|
|
23
|
+
PTS. When trying to access frame 0, it would seek to that negative PTS,
|
|
24
|
+
which no decoded frame ever has, causing a VideoSeekError.
|
|
25
|
+
"""
|
|
26
|
+
import av
|
|
27
|
+
|
|
28
|
+
video_path = tmp_path / 'test_negative_pts.mp4'
|
|
29
|
+
|
|
30
|
+
# Create a small HEVC video with a negative-PTS priming packet.
|
|
31
|
+
# -output_ts_offset shifts timestamps so the first keyframe packet gets a
|
|
32
|
+
# negative PTS, mimicking iPhone HEVC behavior.
|
|
33
|
+
result = subprocess.run([
|
|
34
|
+
'ffmpeg', '-y',
|
|
35
|
+
'-f', 'lavfi', '-i', 'color=c=red:size=320x240:rate=30:d=1',
|
|
36
|
+
'-c:v', 'libx265', '-preset', 'ultrafast',
|
|
37
|
+
'-bf', '2', '-x265-params', 'bframes=2',
|
|
38
|
+
'-tag:v', 'hvc1',
|
|
39
|
+
'-output_ts_offset', '-0.033',
|
|
40
|
+
str(video_path),
|
|
41
|
+
], capture_output=True, text=True)
|
|
42
|
+
assert result.returncode == 0, f'ffmpeg failed: {result.stderr}'
|
|
43
|
+
|
|
44
|
+
# Verify the test video actually has the problematic structure: more
|
|
45
|
+
# packets than decoded frames due to the priming packet.
|
|
46
|
+
with av.open(str(video_path)) as container:
|
|
47
|
+
stream = container.streams.video[0]
|
|
48
|
+
pkt_pts = sorted(
|
|
49
|
+
p.pts for p in container.demux(stream) if p.pts is not None
|
|
50
|
+
)
|
|
51
|
+
with av.open(str(video_path)) as container:
|
|
52
|
+
stream = container.streams.video[0]
|
|
53
|
+
frame_pts = [f.pts for f in container.decode(stream)]
|
|
54
|
+
|
|
55
|
+
assert len(pkt_pts) > len(frame_pts), (
|
|
56
|
+
f'Test video should have more packets ({len(pkt_pts)}) than decoded '
|
|
57
|
+
f'frames ({len(frame_pts)}). The test setup may need updating if '
|
|
58
|
+
f'ffmpeg behavior has changed.'
|
|
59
|
+
)
|
|
60
|
+
assert pkt_pts[0] < 0, f'First packet PTS should be negative, got {pkt_pts[0]}'
|
|
61
|
+
|
|
62
|
+
vid = npimage.VideoStreamer(str(video_path))
|
|
63
|
+
|
|
64
|
+
assert vid.n_frames == len(frame_pts)
|
|
65
|
+
|
|
66
|
+
frame = vid[0]
|
|
67
|
+
assert isinstance(frame, np.ndarray)
|
|
68
|
+
assert frame.shape == (240, 320, 3)
|
|
69
|
+
|
|
70
|
+
last_frame = vid[vid.n_frames - 1]
|
|
71
|
+
assert isinstance(last_frame, np.ndarray)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Tests for FFmpegVideoWriter and AVVideoWriter."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
import npimage
|
|
7
|
+
from npimage.vidio import FFmpegVideoWriter, AVVideoWriter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _animated_frames(base_image, num_frames=30, movement_range=30):
|
|
11
|
+
"""Generate frames that move `base_image` in a small circle."""
|
|
12
|
+
frames = []
|
|
13
|
+
for i in range(num_frames):
|
|
14
|
+
angle = 2 * np.pi * i / num_frames
|
|
15
|
+
dx = int(movement_range * np.cos(angle))
|
|
16
|
+
dy = int(movement_range * np.sin(angle))
|
|
17
|
+
frames.append(npimage.operations.offset(base_image, (dy, dx)))
|
|
18
|
+
return frames
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.mark.slow
|
|
22
|
+
@pytest.mark.parametrize('writer_class', [FFmpegVideoWriter, AVVideoWriter])
|
|
23
|
+
def test_video_writer_roundtrip(writer_class, table_tennis_emoji, tmp_path):
|
|
24
|
+
"""Writing frames with each writer produces a readable, non-empty file."""
|
|
25
|
+
frames = _animated_frames(table_tennis_emoji, num_frames=30)
|
|
26
|
+
output = tmp_path / f'{writer_class.__name__}.mp4'
|
|
27
|
+
|
|
28
|
+
with writer_class(str(output), framerate=30, overwrite=True) as writer:
|
|
29
|
+
for frame in frames:
|
|
30
|
+
writer.write(frame)
|
|
31
|
+
|
|
32
|
+
assert output.exists()
|
|
33
|
+
assert output.stat().st_size > 0
|
|
34
|
+
|
|
35
|
+
vid = npimage.VideoStreamer(str(output))
|
|
36
|
+
assert vid.n_frames == len(frames)
|
|
37
|
+
assert vid[0].shape[:2] == table_tennis_emoji.shape[:2]
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import tempfile
|
|
5
|
-
import numpy as np
|
|
6
|
-
import npimage
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def test_heic_load_save():
|
|
10
|
-
"""Test loading and saving HEIC images."""
|
|
11
|
-
# Create a simple test image
|
|
12
|
-
test_data = np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8)
|
|
13
|
-
|
|
14
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
|
15
|
-
# Test saving as HEIC
|
|
16
|
-
heic_filename = os.path.join(tmpdir, "test_image.heic")
|
|
17
|
-
|
|
18
|
-
try:
|
|
19
|
-
# Save the test data as HEIC
|
|
20
|
-
npimage.save(test_data, heic_filename)
|
|
21
|
-
|
|
22
|
-
# Verify the file was created
|
|
23
|
-
assert os.path.exists(heic_filename), "HEIC file was not created"
|
|
24
|
-
|
|
25
|
-
# Load the HEIC file back
|
|
26
|
-
loaded_data = npimage.load(heic_filename)
|
|
27
|
-
|
|
28
|
-
# Check that the loaded data has the same shape
|
|
29
|
-
assert loaded_data.shape == test_data.shape, f"Shape mismatch: expected {test_data.shape}, got {loaded_data.shape}"
|
|
30
|
-
|
|
31
|
-
# Check that the data type is correct
|
|
32
|
-
assert loaded_data.dtype == np.uint8, f"Data type mismatch: expected uint8, got {loaded_data.dtype}"
|
|
33
|
-
|
|
34
|
-
print("Test passed: HEIC load/save functionality works correctly.")
|
|
35
|
-
|
|
36
|
-
except Exception as e:
|
|
37
|
-
print(f"Test failed with error: {e}")
|
|
38
|
-
raise
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def test_heic_extension_support():
|
|
42
|
-
"""Test that HEIC extension is properly recognized."""
|
|
43
|
-
# Test that HEIC is in the supported extensions
|
|
44
|
-
assert 'heic' in npimage.core.supported_extensions, "HEIC extension not in supported extensions"
|
|
45
|
-
|
|
46
|
-
# Test that the extension is properly parsed
|
|
47
|
-
filename = "test_image.heic"
|
|
48
|
-
extension = filename.split('.')[-1].lower()
|
|
49
|
-
assert extension == 'heic', f"Extension parsing failed: expected 'heic', got '{extension}'"
|
|
50
|
-
|
|
51
|
-
print("Test passed: HEIC extension is properly supported.")
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if __name__ == '__main__':
|
|
55
|
-
try:
|
|
56
|
-
test_heic_extension_support()
|
|
57
|
-
test_heic_load_save()
|
|
58
|
-
print("All HEIC tests passed!")
|
|
59
|
-
except Exception as e:
|
|
60
|
-
print(f"HEIC tests failed: {e}")
|
|
61
|
-
raise
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
|
|
5
|
-
import numpy as np
|
|
6
|
-
|
|
7
|
-
import npimage
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def test_pbm_load_save():
|
|
11
|
-
test_cases = [
|
|
12
|
-
{
|
|
13
|
-
"data": np.array([
|
|
14
|
-
[1, 0, 1, 0, 1],
|
|
15
|
-
[0, 1, 0, 1, 0],
|
|
16
|
-
[1, 1, 1, 0, 0]
|
|
17
|
-
], dtype=bool),
|
|
18
|
-
"filename": "test_arbitrary_width.pbm",
|
|
19
|
-
"description": "PBM read/write, arbitrary width"
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"data": np.array([
|
|
23
|
-
[1, 0, 1, 0, 1, 0, 1, 0],
|
|
24
|
-
[0, 1, 0, 1, 0, 1, 0, 1],
|
|
25
|
-
[1, 1, 1, 1, 0, 0, 0, 0],
|
|
26
|
-
[0, 0, 0, 0, 1, 1, 1, 1]
|
|
27
|
-
], dtype=bool),
|
|
28
|
-
"filename": "test_multiple_of_8.pbm",
|
|
29
|
-
"description": "PBM read/write, width a multiple of 8"
|
|
30
|
-
}
|
|
31
|
-
]
|
|
32
|
-
|
|
33
|
-
for case in test_cases:
|
|
34
|
-
test_data = case["data"]
|
|
35
|
-
test_filename = case["filename"]
|
|
36
|
-
description = case["description"]
|
|
37
|
-
|
|
38
|
-
try:
|
|
39
|
-
# Save the test data to a PBM file
|
|
40
|
-
npimage.save(test_data, test_filename)
|
|
41
|
-
|
|
42
|
-
# Verify the file size
|
|
43
|
-
expected_file_size = npimage.filetypes.pbm.predict_file_size(test_data)
|
|
44
|
-
actual_file_size = os.path.getsize(test_filename)
|
|
45
|
-
assert actual_file_size == expected_file_size, f"File size mismatch: expected {expected_file_size}, got {actual_file_size}"
|
|
46
|
-
|
|
47
|
-
# Load the data back from the PBM file
|
|
48
|
-
loaded_data = npimage.load(test_filename)
|
|
49
|
-
|
|
50
|
-
# Assert that the loaded data matches the original data
|
|
51
|
-
assert np.array_equal(test_data, loaded_data), f"Loaded data does not match original data for {description}"
|
|
52
|
-
|
|
53
|
-
print(f"Test passed: {description} works correctly.")
|
|
54
|
-
finally:
|
|
55
|
-
# Clean up the test file
|
|
56
|
-
if os.path.exists(test_filename):
|
|
57
|
-
os.remove(test_filename)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if __name__ == '__main__':
|
|
61
|
-
test_pbm_load_save()
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Tests for VideoStreamer, particularly handling of HEVC videos with
|
|
4
|
-
negative-PTS priming packets (common in iPhone recordings).
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import os
|
|
8
|
-
import subprocess
|
|
9
|
-
import tempfile
|
|
10
|
-
|
|
11
|
-
import numpy as np
|
|
12
|
-
|
|
13
|
-
import npimage
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def test_negative_pts_priming_packet():
|
|
17
|
-
"""
|
|
18
|
-
Test that VideoStreamer correctly handles HEVC videos where the first
|
|
19
|
-
packet has a negative PTS (a "priming" packet used for B-frame prediction
|
|
20
|
-
that doesn't produce a decoded frame).
|
|
21
|
-
|
|
22
|
-
This is common in iPhone HEVC recordings. The bug was that the index was
|
|
23
|
-
built from demuxed packets, which included the priming packet's negative
|
|
24
|
-
PTS. When trying to access frame 0, it would seek to that negative PTS,
|
|
25
|
-
which no decoded frame ever has, causing a VideoSeekError.
|
|
26
|
-
"""
|
|
27
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
|
28
|
-
video_path = os.path.join(tmpdir, 'test_negative_pts.mp4')
|
|
29
|
-
|
|
30
|
-
# Create a small HEVC video with a negative-PTS priming packet.
|
|
31
|
-
# The -output_ts_offset shifts timestamps so the first keyframe packet
|
|
32
|
-
# gets a negative PTS, mimicking iPhone HEVC behavior.
|
|
33
|
-
result = subprocess.run([
|
|
34
|
-
'ffmpeg', '-y',
|
|
35
|
-
'-f', 'lavfi', '-i', 'color=c=red:size=320x240:rate=30:d=1',
|
|
36
|
-
'-c:v', 'libx265', '-preset', 'ultrafast',
|
|
37
|
-
'-bf', '2', '-x265-params', 'bframes=2',
|
|
38
|
-
'-tag:v', 'hvc1',
|
|
39
|
-
'-output_ts_offset', '-0.033',
|
|
40
|
-
video_path
|
|
41
|
-
], capture_output=True, text=True)
|
|
42
|
-
assert result.returncode == 0, f'ffmpeg failed: {result.stderr}'
|
|
43
|
-
|
|
44
|
-
# Verify the test video actually has the problematic structure:
|
|
45
|
-
# more packets than decoded frames due to the priming packet
|
|
46
|
-
import av
|
|
47
|
-
with av.open(video_path) as container:
|
|
48
|
-
stream = container.streams.video[0]
|
|
49
|
-
pkt_pts = sorted(
|
|
50
|
-
p.pts for p in container.demux(stream) if p.pts is not None
|
|
51
|
-
)
|
|
52
|
-
with av.open(video_path) as container:
|
|
53
|
-
stream = container.streams.video[0]
|
|
54
|
-
frame_pts = [f.pts for f in container.decode(stream)]
|
|
55
|
-
|
|
56
|
-
assert len(pkt_pts) > len(frame_pts), (
|
|
57
|
-
f'Test video should have more packets ({len(pkt_pts)}) than '
|
|
58
|
-
f'decoded frames ({len(frame_pts)}). The test setup may need '
|
|
59
|
-
f'updating if ffmpeg behavior has changed.'
|
|
60
|
-
)
|
|
61
|
-
assert pkt_pts[0] < 0, (
|
|
62
|
-
f'First packet PTS should be negative, got {pkt_pts[0]}'
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
# Now test that VideoStreamer handles this correctly
|
|
66
|
-
vid = npimage.VideoStreamer(video_path)
|
|
67
|
-
|
|
68
|
-
# The number of frames should match decoded frames, not packets
|
|
69
|
-
assert vid.n_frames == len(frame_pts), (
|
|
70
|
-
f'Expected {len(frame_pts)} frames, got {vid.n_frames}'
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
# Accessing frame 0 should work without raising VideoSeekError
|
|
74
|
-
frame = vid[0]
|
|
75
|
-
assert isinstance(frame, np.ndarray)
|
|
76
|
-
assert frame.shape == (240, 320, 3)
|
|
77
|
-
|
|
78
|
-
# Accessing the last frame should also work
|
|
79
|
-
last_frame = vid[vid.n_frames - 1]
|
|
80
|
-
assert isinstance(last_frame, np.ndarray)
|
|
81
|
-
|
|
82
|
-
print(f'PASSED: Loaded {vid.n_frames} frames from video with '
|
|
83
|
-
f'negative-PTS priming packet')
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def main():
|
|
87
|
-
test_negative_pts_priming_packet()
|
|
88
|
-
print('\nAll VideoStreamer tests passed.')
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if __name__ == '__main__':
|
|
92
|
-
main()
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Test script to compare FFmpegVideoWriter and AVVideoWriter implementations.
|
|
4
|
-
Uses the table tennis emoji image with animated movement to create test videos.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import os
|
|
8
|
-
import time
|
|
9
|
-
import tempfile
|
|
10
|
-
import psutil
|
|
11
|
-
import gc
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
|
|
14
|
-
import numpy as np
|
|
15
|
-
|
|
16
|
-
import npimage
|
|
17
|
-
from npimage.vidio import FFmpegVideoWriter, AVVideoWriter
|
|
18
|
-
|
|
19
|
-
script_dir = Path(__file__).parent
|
|
20
|
-
|
|
21
|
-
# Memory monitoring
|
|
22
|
-
process = psutil.Process(os.getpid())
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def print_memory(label):
|
|
26
|
-
"""Print current memory usage"""
|
|
27
|
-
gc.collect()
|
|
28
|
-
mem = process.memory_info().rss / 1024 / 1024
|
|
29
|
-
print(f"{label}: {mem:.1f} MB")
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def create_animated_frames(base_image, num_frames=60, movement_range=128):
|
|
33
|
-
"""Create animated frames by moving the image around"""
|
|
34
|
-
frames = []
|
|
35
|
-
height, width = base_image.shape[:2]
|
|
36
|
-
|
|
37
|
-
for i in range(num_frames):
|
|
38
|
-
# Create circular motion
|
|
39
|
-
angle = 2 * np.pi * i / num_frames
|
|
40
|
-
dx = int(movement_range * np.cos(angle))
|
|
41
|
-
dy = int(movement_range * np.sin(angle))
|
|
42
|
-
|
|
43
|
-
# Apply offset to create movement
|
|
44
|
-
frame = npimage.operations.offset(base_image, (dy, dx))
|
|
45
|
-
frames.append(frame)
|
|
46
|
-
|
|
47
|
-
return frames
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def test_video_writer(writer_class, filename, frames, writer_name):
|
|
51
|
-
"""Test a specific video writer class"""
|
|
52
|
-
print(f"\n=== Testing {writer_name} ===")
|
|
53
|
-
print_memory(f"Before {writer_name}")
|
|
54
|
-
|
|
55
|
-
start_time = time.time()
|
|
56
|
-
|
|
57
|
-
try:
|
|
58
|
-
with writer_class(filename, framerate=30, overwrite=True) as writer:
|
|
59
|
-
for i, frame in enumerate(frames):
|
|
60
|
-
if i % 20 == 0: # Print progress every 20 frames
|
|
61
|
-
print(f" Writing frame {i}/{len(frames)}")
|
|
62
|
-
writer.write(frame)
|
|
63
|
-
|
|
64
|
-
elapsed = time.time() - start_time
|
|
65
|
-
file_size = os.path.getsize(filename) / 1024 # KB
|
|
66
|
-
|
|
67
|
-
print_memory(f"After {writer_name}")
|
|
68
|
-
print(f"✓ {writer_name} completed:")
|
|
69
|
-
print(f" Time: {elapsed:.2f} seconds")
|
|
70
|
-
print(f" File size: {file_size:.1f} KB")
|
|
71
|
-
print(f" Frames per second: {len(frames)/elapsed:.1f}")
|
|
72
|
-
|
|
73
|
-
return True
|
|
74
|
-
|
|
75
|
-
except Exception as e:
|
|
76
|
-
print(f"✗ {writer_name} failed: {e}")
|
|
77
|
-
return False
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def test_memory_leak(writer_class, frames, writer_name, num_iterations=5):
|
|
81
|
-
"""Test for memory leaks by creating multiple videos"""
|
|
82
|
-
print(f"\n=== Memory Leak Test for {writer_name} ===")
|
|
83
|
-
print_memory("Initial memory")
|
|
84
|
-
|
|
85
|
-
for i in range(num_iterations):
|
|
86
|
-
filename = script_dir / "videos" / f"memory_test_{writer_name.lower()}_{i+1}.mp4"
|
|
87
|
-
|
|
88
|
-
print_memory(f"Before iteration {i+1}")
|
|
89
|
-
|
|
90
|
-
with writer_class(filename, framerate=30, overwrite=True) as writer:
|
|
91
|
-
for frame in frames:
|
|
92
|
-
writer.write(frame)
|
|
93
|
-
|
|
94
|
-
print_memory(f"After iteration {i+1}")
|
|
95
|
-
|
|
96
|
-
print_memory(f"Final memory after {num_iterations} iterations")
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def main():
|
|
100
|
-
print("Video Writer Comparison Test")
|
|
101
|
-
print("=" * 60)
|
|
102
|
-
|
|
103
|
-
# Check FFmpeg version
|
|
104
|
-
print("\nChecking FFmpeg version...")
|
|
105
|
-
import subprocess
|
|
106
|
-
try:
|
|
107
|
-
result = subprocess.run(['ffmpeg', '-version'], capture_output=True, text=True)
|
|
108
|
-
ffmpeg_version = result.stdout.split('\n')[0]
|
|
109
|
-
print(f"FFmpeg version: {ffmpeg_version}")
|
|
110
|
-
except Exception as e:
|
|
111
|
-
print(f"Error checking FFmpeg version: {e}")
|
|
112
|
-
|
|
113
|
-
# Load the table tennis emoji image
|
|
114
|
-
print("\nLoading table tennis emoji image...")
|
|
115
|
-
image_path = script_dir / "table-tennis-emoji.png"
|
|
116
|
-
if not os.path.exists(image_path):
|
|
117
|
-
print(f"Error: Image not found at {image_path}")
|
|
118
|
-
return
|
|
119
|
-
|
|
120
|
-
base_image = npimage.load(image_path)
|
|
121
|
-
print(f"Loaded image: {base_image.shape}, dtype: {base_image.dtype}")
|
|
122
|
-
|
|
123
|
-
# Create animated frames
|
|
124
|
-
print("\nCreating animated frames...")
|
|
125
|
-
frames = create_animated_frames(base_image, num_frames=60, movement_range=30)
|
|
126
|
-
print(f"Created {len(frames)} animated frames")
|
|
127
|
-
|
|
128
|
-
# Test both video writers
|
|
129
|
-
results = {}
|
|
130
|
-
|
|
131
|
-
# Test FFmpegVideoWriter
|
|
132
|
-
ffmpeg_filename = script_dir / "videos" / "table_tennis_ffmpeg.mp4"
|
|
133
|
-
results['FFmpegVideoWriter'] = test_video_writer(
|
|
134
|
-
FFmpegVideoWriter, ffmpeg_filename, frames, "FFmpegVideoWriter"
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
# Test AVVideoWriter
|
|
138
|
-
av_filename = script_dir / "videos" / "table_tennis_av.mp4"
|
|
139
|
-
results['AVVideoWriter'] = test_video_writer(
|
|
140
|
-
AVVideoWriter, av_filename, frames, "AVVideoWriter"
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
# Memory leak tests
|
|
144
|
-
print("\n" + "=" * 60)
|
|
145
|
-
print("MEMORY LEAK TESTS")
|
|
146
|
-
print("=" * 60)
|
|
147
|
-
|
|
148
|
-
# Use a smaller set of frames for memory leak testing
|
|
149
|
-
test_frames = frames[:30] # 30 frames for faster testing
|
|
150
|
-
|
|
151
|
-
if results.get('FFmpegVideoWriter', False):
|
|
152
|
-
test_memory_leak(FFmpegVideoWriter, test_frames, "FFmpegVideoWriter")
|
|
153
|
-
|
|
154
|
-
if results.get('AVVideoWriter', False):
|
|
155
|
-
test_memory_leak(AVVideoWriter, test_frames, "AVVideoWriter")
|
|
156
|
-
|
|
157
|
-
# Summary
|
|
158
|
-
print("\n" + "=" * 60)
|
|
159
|
-
print("SUMMARY")
|
|
160
|
-
print("=" * 60)
|
|
161
|
-
|
|
162
|
-
for writer_name, success in results.items():
|
|
163
|
-
status = "✓ PASSED" if success else "✗ FAILED"
|
|
164
|
-
print(f"{writer_name}: {status}")
|
|
165
|
-
|
|
166
|
-
print("\n🎉 Test completed!")
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if __name__ == "__main__":
|
|
170
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|