numpyimage 3.6.0__tar.gz → 3.6.2__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.0/numpyimage.egg-info → numpyimage-3.6.2}/PKG-INFO +1 -1
- {numpyimage-3.6.0 → numpyimage-3.6.2}/npimage/imageio.py +7 -1
- {numpyimage-3.6.0 → numpyimage-3.6.2}/npimage/vidio.py +38 -15
- {numpyimage-3.6.0 → numpyimage-3.6.2/numpyimage.egg-info}/PKG-INFO +1 -1
- {numpyimage-3.6.0 → numpyimage-3.6.2}/pyproject.toml +3 -3
- {numpyimage-3.6.0 → numpyimage-3.6.2}/LICENSE +0 -0
- {numpyimage-3.6.0 → numpyimage-3.6.2}/README.md +0 -0
- {numpyimage-3.6.0 → numpyimage-3.6.2}/npimage/__init__.py +0 -0
- {numpyimage-3.6.0 → numpyimage-3.6.2}/npimage/align.py +0 -0
- {numpyimage-3.6.0 → numpyimage-3.6.2}/npimage/graphics.py +0 -0
- {numpyimage-3.6.0 → numpyimage-3.6.2}/npimage/nrrd_utils.py +0 -0
- {numpyimage-3.6.0 → numpyimage-3.6.2}/npimage/operations.py +0 -0
- {numpyimage-3.6.0 → numpyimage-3.6.2}/npimage/utils.py +0 -0
- {numpyimage-3.6.0 → numpyimage-3.6.2}/numpyimage.egg-info/SOURCES.txt +0 -0
- {numpyimage-3.6.0 → numpyimage-3.6.2}/numpyimage.egg-info/dependency_links.txt +0 -0
- {numpyimage-3.6.0 → numpyimage-3.6.2}/numpyimage.egg-info/requires.txt +0 -0
- {numpyimage-3.6.0 → numpyimage-3.6.2}/numpyimage.egg-info/top_level.txt +0 -0
- {numpyimage-3.6.0 → numpyimage-3.6.2}/setup.cfg +0 -0
- {numpyimage-3.6.0 → numpyimage-3.6.2}/tests/test_heic.py +0 -0
- {numpyimage-3.6.0 → numpyimage-3.6.2}/tests/test_pbm.py +0 -0
- {numpyimage-3.6.0 → numpyimage-3.6.2}/tests/test_video_streamer.py +0 -0
- {numpyimage-3.6.0 → numpyimage-3.6.2}/tests/test_video_writers.py +0 -0
|
@@ -152,7 +152,8 @@ def save(data,
|
|
|
152
152
|
units=None,
|
|
153
153
|
compress=None,
|
|
154
154
|
compression_level=3,
|
|
155
|
-
metadata=None
|
|
155
|
+
metadata=None,
|
|
156
|
+
**kwargs) -> None:
|
|
156
157
|
"""
|
|
157
158
|
Save a numpy array to file with a file type specified by the
|
|
158
159
|
filename extension.
|
|
@@ -173,7 +174,12 @@ def save(data,
|
|
|
173
174
|
raise FileExistsError(f'File {filename} already exists. '
|
|
174
175
|
'Set overwrite=True to overwrite.')
|
|
175
176
|
extension = filename.split('.')[-1].lower()
|
|
177
|
+
if extension in vidio.supported_extensions:
|
|
178
|
+
return vidio.save_video(data, filename, dim_order=dim_order,
|
|
179
|
+
overwrite=overwrite, **kwargs)
|
|
176
180
|
assert extension in supported_extensions, f'Filetype {extension} not supported'
|
|
181
|
+
if kwargs:
|
|
182
|
+
raise TypeError(f'save() got unexpected keyword arguments: {list(kwargs.keys())}')
|
|
177
183
|
|
|
178
184
|
if compress is not None and extension not in ['nrrd', 'ng']:
|
|
179
185
|
print('WARNING: compress argument is ignored because not saving as '
|
|
@@ -395,6 +395,8 @@ class VideoStreamer:
|
|
|
395
395
|
return float(1.0 / self._framerate)
|
|
396
396
|
|
|
397
397
|
def frame_number_to_pts(self, frame_number: int) -> int:
|
|
398
|
+
if hasattr(frame_number, '__iter__'):
|
|
399
|
+
return [self.frame_number_to_pts(n) for n in frame_number]
|
|
398
400
|
frame_number = self._normalize_frame_number(frame_number)
|
|
399
401
|
if self._framerate == 'variable':
|
|
400
402
|
return self.frames_pts[frame_number]
|
|
@@ -402,9 +404,13 @@ class VideoStreamer:
|
|
|
402
404
|
return int(frame_number) * self.pts_delta + self.pts0
|
|
403
405
|
|
|
404
406
|
def frame_number_to_time(self, frame_number: int) -> float:
|
|
407
|
+
if hasattr(frame_number, '__iter__'):
|
|
408
|
+
return [self.frame_number_to_time(n) for n in frame_number]
|
|
405
409
|
return float(self.frame_number_to_pts(frame_number) * self.time_base)
|
|
406
410
|
|
|
407
411
|
def pts_to_frame_number(self, pts: int) -> int:
|
|
412
|
+
if hasattr(pts, '__iter__'):
|
|
413
|
+
return [self.pts_to_frame_number(p) for p in pts]
|
|
408
414
|
if self._framerate == 'variable':
|
|
409
415
|
if pts not in self.frames_pts:
|
|
410
416
|
raise ValueError(f'PTS {pts} not in video index.')
|
|
@@ -776,14 +782,37 @@ class FFmpegVideoWriter:
|
|
|
776
782
|
self._closed = False
|
|
777
783
|
self._width = None
|
|
778
784
|
self._height = None
|
|
779
|
-
self.
|
|
785
|
+
self._pixel_format_in = None
|
|
786
|
+
self._pixel_format_out = 'yuv420p'
|
|
780
787
|
|
|
781
788
|
@property
|
|
782
789
|
def framerate(self):
|
|
783
790
|
return float(self._framerate)
|
|
784
791
|
|
|
785
|
-
def _initialize_process(self, width, height,
|
|
792
|
+
def _initialize_process(self, width, height, pixel_format_in):
|
|
786
793
|
"""Initialize the FFmpeg subprocess for video encoding"""
|
|
794
|
+
self._width = width
|
|
795
|
+
self._height = height
|
|
796
|
+
self._pixel_format_in = pixel_format_in
|
|
797
|
+
|
|
798
|
+
# Check if width or height needs to be padded to an even value
|
|
799
|
+
if (self._pixel_format_out == 'yuv420p'
|
|
800
|
+
and (width % 2 != 0 or height % 2 != 0)):
|
|
801
|
+
pad = [[0, 0], [0, 0], [0, 0]] # [h, w, c]
|
|
802
|
+
if height % 2 != 0:
|
|
803
|
+
print('INFO: Height must be even for yuv420p pixel format but image'
|
|
804
|
+
f' has height {height}, so the bottom row will be duplicated.')
|
|
805
|
+
pad[0][1] = 1
|
|
806
|
+
height = height + 1
|
|
807
|
+
if width % 2 != 0:
|
|
808
|
+
print('INFO: Width must be even for yuv420p pixel format but image'
|
|
809
|
+
f' has width {width}, so the right column will be duplicated.')
|
|
810
|
+
pad[1][1] = 1
|
|
811
|
+
width = width + 1
|
|
812
|
+
self._pad = pad
|
|
813
|
+
else:
|
|
814
|
+
self._pad = 0
|
|
815
|
+
|
|
787
816
|
command = [
|
|
788
817
|
'ffmpeg',
|
|
789
818
|
'-hide_banner',
|
|
@@ -793,12 +822,12 @@ class FFmpegVideoWriter:
|
|
|
793
822
|
'-f', 'rawvideo',
|
|
794
823
|
'-vcodec', 'rawvideo',
|
|
795
824
|
'-s', f'{width}x{height}',
|
|
796
|
-
'-pix_fmt',
|
|
825
|
+
'-pix_fmt', pixel_format_in, # Input pixel format comes before -i
|
|
797
826
|
'-r', str(self._framerate),
|
|
798
827
|
'-i', '-', # Read from stdin
|
|
799
828
|
'-an', # No audio
|
|
800
829
|
'-c:v', self.codec,
|
|
801
|
-
'-pix_fmt',
|
|
830
|
+
'-pix_fmt', self._pixel_format_out, # Output pixel format after -i
|
|
802
831
|
'-crf', str(self.crf),
|
|
803
832
|
]
|
|
804
833
|
if self.codec in ('libvpx', 'libvpx-vp9'):
|
|
@@ -816,9 +845,7 @@ class FFmpegVideoWriter:
|
|
|
816
845
|
stderr=subprocess.PIPE # Capture errors
|
|
817
846
|
)
|
|
818
847
|
self._stdin = self._process.stdin
|
|
819
|
-
|
|
820
|
-
self._height = height
|
|
821
|
-
self._pixel_format = pixel_format
|
|
848
|
+
|
|
822
849
|
|
|
823
850
|
def write(self, frame):
|
|
824
851
|
"""Write a frame to the video file"""
|
|
@@ -861,6 +888,9 @@ class FFmpegVideoWriter:
|
|
|
861
888
|
raise ValueError(f'Cannot write image of size (w={width}, h={height}) to video'
|
|
862
889
|
f' already containing images of size {(self._width, self._height)}')
|
|
863
890
|
|
|
891
|
+
if self._pad:
|
|
892
|
+
frame = np.pad(frame, self._pad, mode='edge')
|
|
893
|
+
|
|
864
894
|
# Convert frame to bytes and write to FFmpeg
|
|
865
895
|
frame_bytes = frame.tobytes()
|
|
866
896
|
self._stdin.write(frame_bytes)
|
|
@@ -993,16 +1023,9 @@ def save_video(data, filename, time_axis=0, color_axis=None, overwrite=False,
|
|
|
993
1023
|
height, width = data.shape[1:]
|
|
994
1024
|
|
|
995
1025
|
extension = filename.suffix.lower().lstrip('.')
|
|
996
|
-
if extension == 'mp4':
|
|
997
|
-
pad = [[0, 0], [0, 0], [0, 0]]
|
|
998
|
-
if height % 2 != 0:
|
|
999
|
-
pad[1][1] = 1
|
|
1000
|
-
if width % 2 != 0:
|
|
1001
|
-
pad[2][1] = 1
|
|
1002
|
-
if pad != [[0, 0], [0, 0], [0, 0]]:
|
|
1003
|
-
data = np.pad(data, pad, mode='edge')
|
|
1004
1026
|
|
|
1005
1027
|
if extension == 'gif':
|
|
1028
|
+
# We make gifs with PIL instead of using FFmpeg
|
|
1006
1029
|
from PIL import Image
|
|
1007
1030
|
pil_images = [Image.fromarray(frame) for frame in data]
|
|
1008
1031
|
|
|
@@ -4,12 +4,12 @@ build-backend = 'setuptools.build_meta'
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = 'numpyimage'
|
|
7
|
-
version = '3.6.
|
|
7
|
+
version = '3.6.2'
|
|
8
8
|
description = 'Load, save, & manipulate image files as numpy arrays'
|
|
9
9
|
readme.file = 'README.md'
|
|
10
10
|
readme.content-type = 'text/markdown'
|
|
11
|
-
requires-python =
|
|
12
|
-
license = {file =
|
|
11
|
+
requires-python = '>=3.6'
|
|
12
|
+
license = {file = 'LICENSE'}
|
|
13
13
|
keywords = ['images', 'pixel arrays', 'image formats', 'convert', 'graphics', 'draw simple shapes']
|
|
14
14
|
authors = [{name = 'Jasper Phelps', email = 'jasper.s.phelps@gmail.com'}]
|
|
15
15
|
classifiers = [
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|