numpyimage 2.2.0__tar.gz → 2.3.0__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-2.2.0 → numpyimage-2.3.0}/PKG-INFO +1 -1
- {numpyimage-2.2.0 → numpyimage-2.3.0}/npimage/core.py +41 -18
- {numpyimage-2.2.0 → numpyimage-2.3.0}/npimage/operations.py +13 -6
- {numpyimage-2.2.0 → numpyimage-2.3.0}/numpyimage.egg-info/PKG-INFO +1 -1
- {numpyimage-2.2.0 → numpyimage-2.3.0}/numpyimage.egg-info/SOURCES.txt +2 -2
- {numpyimage-2.2.0 → numpyimage-2.3.0}/pyproject.toml +1 -1
- numpyimage-2.3.0/tests/tests.py +39 -0
- numpyimage-2.2.0/npimage/align_scratch.py +0 -50
- {numpyimage-2.2.0 → numpyimage-2.3.0}/LICENSE +0 -0
- {numpyimage-2.2.0 → numpyimage-2.3.0}/README.md +0 -0
- {numpyimage-2.2.0 → numpyimage-2.3.0}/npimage/__init__.py +0 -0
- {numpyimage-2.2.0 → numpyimage-2.3.0}/npimage/align.py +0 -0
- {numpyimage-2.2.0 → numpyimage-2.3.0}/npimage/filetypes/__init__.py +0 -0
- {numpyimage-2.2.0 → numpyimage-2.3.0}/npimage/filetypes/pbm.py +0 -0
- {numpyimage-2.2.0 → numpyimage-2.3.0}/npimage/graphics.py +0 -0
- {numpyimage-2.2.0 → numpyimage-2.3.0}/npimage/nrrd_utils.py +0 -0
- {numpyimage-2.2.0 → numpyimage-2.3.0}/npimage/utils.py +0 -0
- {numpyimage-2.2.0 → numpyimage-2.3.0}/numpyimage.egg-info/dependency_links.txt +0 -0
- {numpyimage-2.2.0 → numpyimage-2.3.0}/numpyimage.egg-info/requires.txt +0 -0
- {numpyimage-2.2.0 → numpyimage-2.3.0}/numpyimage.egg-info/top_level.txt +0 -0
- {numpyimage-2.2.0 → numpyimage-2.3.0}/setup.cfg +0 -0
- {numpyimage-2.2.0 → numpyimage-2.3.0}/tests/test_pbm.py +0 -0
|
@@ -33,7 +33,7 @@ def load(filename, dim_order='zyx', **kwargs):
|
|
|
33
33
|
images, or yxc for multi-channel (RGB/RGBA) 2D images.
|
|
34
34
|
Set dim_order='xy' if you want to reverse the order of the axes.
|
|
35
35
|
"""
|
|
36
|
-
filename = str(filename)
|
|
36
|
+
filename = os.path.expanduser(str(filename))
|
|
37
37
|
while filename.endswith('/'):
|
|
38
38
|
filename = filename[:-1]
|
|
39
39
|
if 'format' in kwargs:
|
|
@@ -45,7 +45,7 @@ def load(filename, dim_order='zyx', **kwargs):
|
|
|
45
45
|
f' "{filename}". Please specify the file type via'
|
|
46
46
|
' the `format` argument, e.g. format="tif"')
|
|
47
47
|
if extension not in supported_extensions:
|
|
48
|
-
raise ValueError(f'File format "{
|
|
48
|
+
raise ValueError(f'File format of "{filename}" not supported/recognized.')
|
|
49
49
|
|
|
50
50
|
data = None
|
|
51
51
|
metadata = None
|
|
@@ -161,7 +161,7 @@ def save(data,
|
|
|
161
161
|
Currently `pixel_size`, `units`, and `compress` are only recognized
|
|
162
162
|
when saving to .nrrd and .ng files, and ignored otherwise.
|
|
163
163
|
"""
|
|
164
|
-
filename = str(filename)
|
|
164
|
+
filename = os.path.expanduser(str(filename))
|
|
165
165
|
filename = filename.rstrip('/')
|
|
166
166
|
if os.path.exists(filename) and not overwrite:
|
|
167
167
|
raise FileExistsError(f'File {filename} already exists. '
|
|
@@ -352,10 +352,10 @@ write = save # Function name alias
|
|
|
352
352
|
to_file = save # Function name alias
|
|
353
353
|
|
|
354
354
|
|
|
355
|
-
def save_video(data, filename, time_axis=0,
|
|
356
|
-
framerate=30, crf=23, compression_speed='medium'):
|
|
355
|
+
def save_video(data, filename, time_axis=0, color_axis=None, overwrite=False,
|
|
356
|
+
dim_order='yx', framerate=30, crf=23, compression_speed='medium'):
|
|
357
357
|
"""
|
|
358
|
-
Save a 3D numpy array
|
|
358
|
+
Save a 3D numpy array of greyscale values OR a 4D numpy array of RGB values as a video
|
|
359
359
|
|
|
360
360
|
Follows the PyAV cookbook section on generating video from numpy arrays:
|
|
361
361
|
https://pyav.basswood-io.com/docs/develop/cookbook/numpy.html#generating-video
|
|
@@ -363,7 +363,7 @@ def save_video(data, filename, time_axis=0, overwrite=False, dim_order='yx',
|
|
|
363
363
|
Parameters
|
|
364
364
|
----------
|
|
365
365
|
data : numpy.ndarray or list of filenames
|
|
366
|
-
A 3D numpy array of pixel values.
|
|
366
|
+
A 3D (grayscale) or 4D (RGB) numpy array of pixel values.
|
|
367
367
|
|
|
368
368
|
filename : str
|
|
369
369
|
The filename to save the video to.
|
|
@@ -371,24 +371,44 @@ def save_video(data, filename, time_axis=0, overwrite=False, dim_order='yx',
|
|
|
371
371
|
time_axis : int, default 0
|
|
372
372
|
The axis of the data array that will be played as time in the video.
|
|
373
373
|
|
|
374
|
+
color_axis : int or None, default None
|
|
375
|
+
If not None, specifies the axis of the color channels (e.g., -1 for last axis, 1 for second axis).
|
|
376
|
+
If None, data is assumed to be grayscale.
|
|
377
|
+
|
|
374
378
|
overwrite : bool, default False
|
|
375
379
|
Whether to overwrite the file if it already exists.
|
|
376
380
|
|
|
377
381
|
dim_order : 'yx' (default) or 'xy'
|
|
378
382
|
The order of the spatial dimensions in the input data.
|
|
379
383
|
"""
|
|
380
|
-
if not data.ndim == 3:
|
|
381
|
-
raise ValueError('Input data must be a 3D numpy array.')
|
|
382
384
|
try:
|
|
383
385
|
import av
|
|
384
386
|
except ImportError:
|
|
385
|
-
raise ImportError('To save videos, you must have PyAV installed. '
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
387
|
+
raise ImportError('To save videos, you must have PyAV installed. You can install it with "pip install av".')
|
|
388
|
+
|
|
389
|
+
if color_axis is not None:
|
|
390
|
+
if data.ndim != 4:
|
|
391
|
+
raise ValueError('Input data must be a 4D numpy array for color video.')
|
|
392
|
+
# Move time axis to 0, color axis to -1
|
|
393
|
+
data = np.moveaxis(data, time_axis, 0)
|
|
394
|
+
if color_axis != -1:
|
|
395
|
+
data = np.moveaxis(data, color_axis, -1)
|
|
396
|
+
if 'xy' in dim_order:
|
|
397
|
+
data = data.swapaxes(1, 2)
|
|
398
|
+
n_frames = data.shape[0]
|
|
399
|
+
height, width, channels = data.shape[1:]
|
|
400
|
+
if channels != 3:
|
|
401
|
+
raise ValueError('Color video must have 3 channels (RGB).')
|
|
402
|
+
else:
|
|
403
|
+
if data.ndim != 3:
|
|
404
|
+
raise ValueError('Input data must be a 3D numpy array for grayscale video.')
|
|
405
|
+
data = np.moveaxis(data, time_axis, 0)
|
|
406
|
+
if 'xy' in dim_order:
|
|
407
|
+
data = data.swapaxes(1, 2)
|
|
408
|
+
n_frames = data.shape[0]
|
|
409
|
+
height, width = data.shape[1:]
|
|
391
410
|
|
|
411
|
+
filename = os.path.expanduser(str(filename))
|
|
392
412
|
if filename.split('.')[-1].lower() not in ['mp4', 'mkv', 'avi', 'mov']:
|
|
393
413
|
filename += '.mp4'
|
|
394
414
|
if os.path.exists(filename) and not overwrite:
|
|
@@ -397,9 +417,9 @@ def save_video(data, filename, time_axis=0, overwrite=False, dim_order='yx',
|
|
|
397
417
|
extension = filename.split('.')[-1].lower()
|
|
398
418
|
if extension == 'mp4':
|
|
399
419
|
pad = [[0, 0], [0, 0], [0, 0]]
|
|
400
|
-
if
|
|
420
|
+
if height % 2 != 0:
|
|
401
421
|
pad[1][1] = 1
|
|
402
|
-
if
|
|
422
|
+
if width % 2 != 0:
|
|
403
423
|
pad[2][1] = 1
|
|
404
424
|
if pad != [[0, 0], [0, 0], [0, 0]]:
|
|
405
425
|
data = np.pad(data, pad, mode='edge')
|
|
@@ -412,7 +432,10 @@ def save_video(data, filename, time_axis=0, overwrite=False, dim_order='yx',
|
|
|
412
432
|
stream.width = data.shape[2]
|
|
413
433
|
|
|
414
434
|
for frame_i in range(n_frames):
|
|
415
|
-
|
|
435
|
+
if color_axis is not None:
|
|
436
|
+
frame = av.VideoFrame.from_ndarray(data[frame_i], format='rgb24')
|
|
437
|
+
else:
|
|
438
|
+
frame = av.VideoFrame.from_ndarray(data[frame_i], format='gray')
|
|
416
439
|
for packet in stream.encode(frame):
|
|
417
440
|
container.mux(packet)
|
|
418
441
|
|
|
@@ -19,12 +19,14 @@ import numpy as np
|
|
|
19
19
|
from .utils import iround, eq, isint
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def squeeze_dtype(image: np.ndarray):
|
|
22
|
+
def squeeze_dtype(image: np.ndarray, minimum_bits=1):
|
|
23
23
|
"""
|
|
24
24
|
Cast a numpy array to the smallest possible dtype without losing information.
|
|
25
25
|
"""
|
|
26
26
|
if not isinstance(image, np.ndarray):
|
|
27
27
|
raise TypeError('image must be a numpy array')
|
|
28
|
+
if minimum_bits > 64:
|
|
29
|
+
raise ValueError('minimum_bits must be <= 64')
|
|
28
30
|
|
|
29
31
|
im_has_fractions = not np.all(image == np.floor(image))
|
|
30
32
|
if im_has_fractions:
|
|
@@ -35,17 +37,17 @@ def squeeze_dtype(image: np.ndarray):
|
|
|
35
37
|
return image
|
|
36
38
|
elif image.min() < 0:
|
|
37
39
|
candidates = [np.int8, np.int16, np.int32, np.int64]
|
|
38
|
-
elif image.max() <= 1:
|
|
40
|
+
elif image.max() <= 1 and minimum_bits <= 1:
|
|
39
41
|
return image.astype(bool, copy=False)
|
|
40
42
|
else:
|
|
41
43
|
candidates = [np.uint8, np.uint16, np.uint32, np.uint64]
|
|
42
44
|
|
|
43
45
|
image_max = image.max()
|
|
44
|
-
if image_max <= 1:
|
|
46
|
+
if image_max <= 1 and minimum_bits <= 1:
|
|
45
47
|
return image.astype(bool, copy=False)
|
|
46
48
|
for dtype in candidates:
|
|
47
49
|
info = np.iinfo(dtype)
|
|
48
|
-
if image_max <= info.max:
|
|
50
|
+
if image_max <= info.max and info.bits >= minimum_bits:
|
|
49
51
|
return image.astype(dtype, copy=False)
|
|
50
52
|
raise ValueError('Image has values larger than the maximum representable value')
|
|
51
53
|
|
|
@@ -70,7 +72,8 @@ def to_16bit(image: np.ndarray, **kwargs) -> np.ndarray:
|
|
|
70
72
|
|
|
71
73
|
def cast(image: np.ndarray,
|
|
72
74
|
output_dtype: Union[str, np.dtype],
|
|
73
|
-
maximize_contrast=
|
|
75
|
+
maximize_contrast: bool = False,
|
|
76
|
+
round_before_cast_to_int: bool = True,
|
|
74
77
|
bottom_percentile=0.05,
|
|
75
78
|
top_percentile=99.95,
|
|
76
79
|
bottom_value=None,
|
|
@@ -132,11 +135,15 @@ def cast(image: np.ndarray,
|
|
|
132
135
|
raise TypeError(f'Unsupported dtype: {output_dtype}')
|
|
133
136
|
|
|
134
137
|
if not maximize_contrast:
|
|
138
|
+
if (round_before_cast_to_int
|
|
139
|
+
and np.issubdtype(output_dtype, np.integer)
|
|
140
|
+
and not np.issubdtype(image.dtype, np.integer)):
|
|
141
|
+
image = image.round()
|
|
135
142
|
return np.clip(
|
|
136
143
|
image,
|
|
137
144
|
info.min,
|
|
138
145
|
info.max
|
|
139
|
-
).astype(output_dtype)
|
|
146
|
+
).astype(output_dtype, copy=False)
|
|
140
147
|
|
|
141
148
|
if bottom_value is None or top_value is None:
|
|
142
149
|
percentiles = np.percentile(image, [bottom_percentile, top_percentile])
|
|
@@ -3,7 +3,6 @@ README.md
|
|
|
3
3
|
pyproject.toml
|
|
4
4
|
npimage/__init__.py
|
|
5
5
|
npimage/align.py
|
|
6
|
-
npimage/align_scratch.py
|
|
7
6
|
npimage/core.py
|
|
8
7
|
npimage/graphics.py
|
|
9
8
|
npimage/nrrd_utils.py
|
|
@@ -16,4 +15,5 @@ numpyimage.egg-info/SOURCES.txt
|
|
|
16
15
|
numpyimage.egg-info/dependency_links.txt
|
|
17
16
|
numpyimage.egg-info/requires.txt
|
|
18
17
|
numpyimage.egg-info/top_level.txt
|
|
19
|
-
tests/test_pbm.py
|
|
18
|
+
tests/test_pbm.py
|
|
19
|
+
tests/tests.py
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import fire
|
|
5
|
+
|
|
6
|
+
import npimage
|
|
7
|
+
import npimage.operations
|
|
8
|
+
|
|
9
|
+
test_im = np.zeros((8, 8), dtype=np.uint8)
|
|
10
|
+
test_im[0:1, 0:2] = 255
|
|
11
|
+
test_im[1:3, 2:4] = 255
|
|
12
|
+
test_im[3:7, 3:7] = 255
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def mplshow(*args):
|
|
16
|
+
npimage.show(*args, mode='matplotlib')
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_offset():
|
|
20
|
+
mplshow(test_im)
|
|
21
|
+
mplshow(npimage.operations.offset(test_im, (0.5, 0)))
|
|
22
|
+
mplshow(npimage.operations.offset(test_im, (0, 0.5)))
|
|
23
|
+
mplshow(npimage.operations.offset(test_im, (0.5, 0.5)))
|
|
24
|
+
mplshow(npimage.operations.offset(test_im, (1, 1)))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_paste():
|
|
28
|
+
offsets = [('++', [100, 50]),
|
|
29
|
+
('+-', [100, -50]),
|
|
30
|
+
('-+', [-100, 50]),
|
|
31
|
+
('--', [-100, -50])]
|
|
32
|
+
for offset in offsets:
|
|
33
|
+
im = npimage.load('firefox-logo.png', dim_order='xy')
|
|
34
|
+
npimage.operations.paste(im, im, offset[1])
|
|
35
|
+
npimage.save(im, 'firefox-logo_paste' + offset[0] + '.png', dim_order='xy')
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == '__main__':
|
|
39
|
+
fire.Fire()
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def align(im1, im2, num_divisions=4):
|
|
5
|
-
"""
|
|
6
|
-
Return a translated version of im1 that is aligned with im2.
|
|
7
|
-
|
|
8
|
-
To align the images, this function divides im1 into a grid of
|
|
9
|
-
num_divisions x num_divisions regions. For each region, we find the
|
|
10
|
-
region in im2 that is most similar to it. After removing outliers
|
|
11
|
-
for which the similarity score is too low, we calculate the average
|
|
12
|
-
translation between the two images. We then shift im1 by this
|
|
13
|
-
average translation.
|
|
14
|
-
"""
|
|
15
|
-
# The code below is entirely GitHub copilot generated and needs to
|
|
16
|
-
# be looked at, finished, tested, etc
|
|
17
|
-
|
|
18
|
-
# Divide the image into a grid of num_divisions x num_divisions regions
|
|
19
|
-
height, width = im1.shape
|
|
20
|
-
region_height = height // num_divisions
|
|
21
|
-
region_width = width // num_divisions
|
|
22
|
-
|
|
23
|
-
# Initialize lists to store the translations for each region
|
|
24
|
-
translations = []
|
|
25
|
-
|
|
26
|
-
# For each region in im1, find the most similar region in im2
|
|
27
|
-
for i in range(num_divisions):
|
|
28
|
-
for j in range(num_divisions):
|
|
29
|
-
# Define the region in im1
|
|
30
|
-
top_left = (i * region_height, j * region_width)
|
|
31
|
-
bottom_right = ((i + 1) * region_height, (j + 1) * region_width)
|
|
32
|
-
region1 = im1[top_left[0]:bottom_right[0], top_left[1]:bottom_right[1]]
|
|
33
|
-
|
|
34
|
-
# Define the search region in im2
|
|
35
|
-
search_bbox = (slice(max(0, top_left[0] - region_height),
|
|
36
|
-
min(height, bottom_right[0] + region_height)),
|
|
37
|
-
slice(max(0, top_left[1] - region_width),
|
|
38
|
-
min(width, bottom_right[1] + region_width)))
|
|
39
|
-
|
|
40
|
-
# Find the most similar region in im2
|
|
41
|
-
top_left2, score = find_landmark(im2, region1, search_bbox=search_bbox)
|
|
42
|
-
|
|
43
|
-
# Add the translation to the list of translations
|
|
44
|
-
translations.append((top_left[0] - top_left2[0], top_left[1] - top_left2[1]))
|
|
45
|
-
|
|
46
|
-
# Remove outliers from the list of translations
|
|
47
|
-
translations = np.array(translations)
|
|
48
|
-
mean_translations = np.mean(translations)
|
|
49
|
-
# Shift im1 by the average translation
|
|
50
|
-
aligned_im1 = np.roll(im1, mean_translations, axis=(0, 1))
|
|
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
|