numpyimage 3.2.0__tar.gz → 3.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-3.2.0/numpyimage.egg-info → numpyimage-3.3.0}/PKG-INFO +1 -1
- {numpyimage-3.2.0 → numpyimage-3.3.0}/npimage/imageio.py +1 -0
- {numpyimage-3.2.0 → numpyimage-3.3.0}/npimage/operations.py +161 -97
- {numpyimage-3.2.0 → numpyimage-3.3.0}/npimage/utils.py +56 -10
- {numpyimage-3.2.0 → numpyimage-3.3.0}/npimage/vidio.py +3 -2
- {numpyimage-3.2.0 → numpyimage-3.3.0/numpyimage.egg-info}/PKG-INFO +1 -1
- {numpyimage-3.2.0 → numpyimage-3.3.0}/pyproject.toml +1 -1
- {numpyimage-3.2.0 → numpyimage-3.3.0}/LICENSE +0 -0
- {numpyimage-3.2.0 → numpyimage-3.3.0}/README.md +0 -0
- {numpyimage-3.2.0 → numpyimage-3.3.0}/npimage/__init__.py +0 -0
- {numpyimage-3.2.0 → numpyimage-3.3.0}/npimage/align.py +0 -0
- {numpyimage-3.2.0 → numpyimage-3.3.0}/npimage/graphics.py +0 -0
- {numpyimage-3.2.0 → numpyimage-3.3.0}/npimage/nrrd_utils.py +0 -0
- {numpyimage-3.2.0 → numpyimage-3.3.0}/numpyimage.egg-info/SOURCES.txt +0 -0
- {numpyimage-3.2.0 → numpyimage-3.3.0}/numpyimage.egg-info/dependency_links.txt +0 -0
- {numpyimage-3.2.0 → numpyimage-3.3.0}/numpyimage.egg-info/requires.txt +0 -0
- {numpyimage-3.2.0 → numpyimage-3.3.0}/numpyimage.egg-info/top_level.txt +0 -0
- {numpyimage-3.2.0 → numpyimage-3.3.0}/setup.cfg +0 -0
- {numpyimage-3.2.0 → numpyimage-3.3.0}/tests/test_heic.py +0 -0
- {numpyimage-3.2.0 → numpyimage-3.3.0}/tests/test_pbm.py +0 -0
- {numpyimage-3.2.0 → numpyimage-3.3.0}/tests/test_video_writers.py +0 -0
|
@@ -12,11 +12,11 @@ Function list:
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
from typing import Iterable, Literal, Union, Optional, Tuple, List
|
|
15
|
-
import gc
|
|
16
15
|
|
|
17
16
|
import numpy as np
|
|
17
|
+
from tqdm import tqdm
|
|
18
18
|
|
|
19
|
-
from .utils import iround, eq, isint
|
|
19
|
+
from .utils import iround, eq, isint, find_channel_axis
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def squeeze_dtype(image: np.ndarray, minimum_bits=1):
|
|
@@ -233,29 +233,50 @@ def downsample(image: np.ndarray,
|
|
|
233
233
|
image : np.ndarray
|
|
234
234
|
The image to downsample.
|
|
235
235
|
|
|
236
|
-
factor : int or iterable of ints
|
|
236
|
+
factor : int or iterable of ints, default 2
|
|
237
237
|
An iterable with length matching the number of axes in the image,
|
|
238
238
|
specifying a downsampling factor along each axis.
|
|
239
239
|
If factor is provided as an int, that int will be used for each axis.
|
|
240
|
-
If the image
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
the image.
|
|
240
|
+
If the image has a channel axis (RGB/RGBA), it is not necessary to
|
|
241
|
+
specify a factor for that axis and so the 'factor' iterable can be
|
|
242
|
+
one element shorter than the number of axes in the image.
|
|
244
243
|
|
|
245
|
-
keep_input_dtype : bool
|
|
244
|
+
keep_input_dtype : bool, default True
|
|
246
245
|
If True, the output image will have the same dtype as the input image.
|
|
247
246
|
If False, the output image will have dtype float64 to keep full precision.
|
|
247
|
+
|
|
248
|
+
Returns
|
|
249
|
+
-------
|
|
250
|
+
np.ndarray
|
|
251
|
+
The downsampled image.
|
|
252
|
+
|
|
253
|
+
Examples
|
|
254
|
+
--------
|
|
255
|
+
Downsample a shape (2, 4) array using default settings of factor=2, keep_input_dtype=True
|
|
256
|
+
|
|
257
|
+
>>> downsample(np.array([[1, 2, 3, 4],
|
|
258
|
+
... [5, 6, 7, 8]]))
|
|
259
|
+
array([[4, 6]])
|
|
260
|
+
|
|
261
|
+
Note that the output dtype is kept as int, which loses some precision. Compare to:
|
|
262
|
+
|
|
263
|
+
>>> downsample(np.array([[1, 2, 3, 4],
|
|
264
|
+
... [5, 6, 7, 8]]),
|
|
265
|
+
... keep_input_dtype=False)
|
|
266
|
+
array([[3.5, 5.5]])
|
|
248
267
|
"""
|
|
268
|
+
channel_axis = find_channel_axis(image)
|
|
249
269
|
if isinstance(factor, int):
|
|
250
|
-
if
|
|
270
|
+
if channel_axis is not None:
|
|
251
271
|
# If RGB/RGBA image, don't downsample the colors axis
|
|
252
272
|
factor = (factor,) * (len(image.shape) - 1)
|
|
253
273
|
else:
|
|
254
274
|
factor = (factor,) * len(image.shape)
|
|
255
|
-
if len(factor) == len(image.shape) - 1 and
|
|
275
|
+
if len(factor) == len(image.shape) - 1 and channel_axis is not None:
|
|
256
276
|
if verbose:
|
|
257
|
-
print('RGB/RGBA image detected - not downsampling
|
|
258
|
-
factor = (
|
|
277
|
+
print('RGB/RGBA image detected - not downsampling channel axis.')
|
|
278
|
+
factor = list(factor)
|
|
279
|
+
factor.insert(channel_axis, 1)
|
|
259
280
|
if any([f > l > 1 for f, l in zip(factor, image.shape)]):
|
|
260
281
|
raise ValueError('Downsampling factor must be <= image size along each axis')
|
|
261
282
|
|
|
@@ -306,10 +327,10 @@ def offset(image: np.ndarray,
|
|
|
306
327
|
distance: Union[float, Iterable[float]],
|
|
307
328
|
axis: int = None,
|
|
308
329
|
expand_bounds: bool = False,
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
330
|
+
fill_empty_with: float = 0,
|
|
331
|
+
keep_input_dtype: bool = True,
|
|
332
|
+
fill_transparent: bool = False,
|
|
333
|
+
verbose: bool = False) -> np.ndarray:
|
|
313
334
|
"""
|
|
314
335
|
Offset an image by a given distance.
|
|
315
336
|
|
|
@@ -321,7 +342,7 @@ def offset(image: np.ndarray,
|
|
|
321
342
|
|
|
322
343
|
If edge_mode is set to 'constant', the pixels no longer occupied by the
|
|
323
344
|
original image as a result of the offset will be filled in with
|
|
324
|
-
'
|
|
345
|
+
'fill_empty_value'.
|
|
325
346
|
|
|
326
347
|
See also scipy.ndimage.shift, which performs a very similar operation
|
|
327
348
|
"""
|
|
@@ -333,19 +354,21 @@ def offset(image: np.ndarray,
|
|
|
333
354
|
' did a mix of those two.')
|
|
334
355
|
except TypeError:
|
|
335
356
|
distance_iter = [0] * len(image.shape)
|
|
357
|
+
if len(distance_iter) == 1 and axis is None:
|
|
358
|
+
axis = 0
|
|
336
359
|
if axis is None:
|
|
337
360
|
raise ValueError('Must specify axis when giving distance as a'
|
|
338
361
|
' single number.')
|
|
339
362
|
distance_iter[axis] = distance
|
|
340
363
|
distance = distance_iter
|
|
341
|
-
if edge_mode not in ['extend', 'wrap', 'reflect', 'constant']:
|
|
342
|
-
raise ValueError("edge_mode must be one of 'extend', 'wrap', 'reflect', or 'constant'")
|
|
343
|
-
if edge_mode in ['wrap', 'reflect']:
|
|
344
|
-
raise NotImplementedError("edge_mode '{}' not yet implemented".format(edge_mode))
|
|
345
364
|
|
|
346
|
-
if len(image.shape) == len(distance) + 1
|
|
347
|
-
|
|
348
|
-
|
|
365
|
+
if len(image.shape) == len(distance) + 1:
|
|
366
|
+
channel_axis = find_channel_axis(image)
|
|
367
|
+
if channel_axis is not None:
|
|
368
|
+
# Specify no offset along the channels axis, if not specified by user
|
|
369
|
+
distance = list(distance)
|
|
370
|
+
distance.insert(channel_axis, 0)
|
|
371
|
+
distance = tuple(distance)
|
|
349
372
|
|
|
350
373
|
if len(image.shape) != len(distance):
|
|
351
374
|
m = (f'distance must have length {len(image.shape)} to specify an'
|
|
@@ -353,29 +376,41 @@ def offset(image: np.ndarray,
|
|
|
353
376
|
f' {len(distance)}')
|
|
354
377
|
raise ValueError(m)
|
|
355
378
|
|
|
356
|
-
if
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
379
|
+
new_dtype = (image.dtype if keep_input_dtype or all(eq(d, int(d)) for d in distance)
|
|
380
|
+
else np.float64)
|
|
381
|
+
distance_int = [int(d) for d in distance]
|
|
382
|
+
new_shape = (image.shape if not expand_bounds else
|
|
383
|
+
np.array(image.shape) + np.array([int(max(0, d)) for d in distance]))
|
|
384
|
+
new_image = np.full(new_shape, fill_empty_with, dtype=new_dtype)
|
|
385
|
+
|
|
386
|
+
if fill_transparent:
|
|
387
|
+
raise NotImplementedError('fill_transparent not yet implemented')
|
|
388
|
+
# Previous implementation of fill_transparent:
|
|
389
|
+
#if image.shape[-1] == 4 and not fill_transparent:
|
|
390
|
+
# # If rgba, set alpha channel value to max
|
|
391
|
+
# # The line below means new_image[:, :, :, ..., :, -1] = 255
|
|
392
|
+
# new_image[tuple([slice(None, None)] * (len(image.shape)-1) + [-1])] = 255
|
|
393
|
+
|
|
394
|
+
source_range = [slice(max(0, -d), min(s, s-d))
|
|
395
|
+
for d, s in zip(distance_int, new_image.shape)]
|
|
396
|
+
target_range = [slice(max(0, d), min(s, s+d))
|
|
397
|
+
for d, s in zip(distance_int, new_image.shape)]
|
|
371
398
|
|
|
399
|
+
if verbose:
|
|
400
|
+
print('Performing integer offset...')
|
|
401
|
+
# This is the line that does the main operation
|
|
372
402
|
new_image[tuple(target_range)] = image[tuple(source_range)]
|
|
373
403
|
|
|
374
404
|
for i, d in enumerate(distance):
|
|
405
|
+
progress_msg = f'Performing subpixel offset along axis {i}' if verbose else None
|
|
375
406
|
if not eq(d, int(d)):
|
|
376
407
|
_offset_subpixel(new_image, d - int(d), i,
|
|
377
|
-
|
|
378
|
-
|
|
408
|
+
edge_mode='constant',
|
|
409
|
+
constant_edge_value=fill_empty_with,
|
|
410
|
+
keep_input_dtype=True,
|
|
411
|
+
fill_transparent=fill_transparent,
|
|
412
|
+
inplace=True,
|
|
413
|
+
progress_msg=progress_msg)
|
|
379
414
|
|
|
380
415
|
return new_image
|
|
381
416
|
|
|
@@ -385,9 +420,11 @@ def _offset_subpixel(image: np.ndarray,
|
|
|
385
420
|
axis: int,
|
|
386
421
|
edge_mode: Literal['extend', 'wrap',
|
|
387
422
|
'reflect', 'constant'] = 'extend',
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
423
|
+
constant_edge_value: Optional[float] = None,
|
|
424
|
+
keep_input_dtype: bool = True,
|
|
425
|
+
fill_transparent: bool = False,
|
|
426
|
+
inplace: bool = False,
|
|
427
|
+
progress_msg: Optional[str] = None):
|
|
391
428
|
"""
|
|
392
429
|
Offset an image by a fraction of a pixel along a single specified axis.
|
|
393
430
|
|
|
@@ -402,61 +439,80 @@ def _offset_subpixel(image: np.ndarray,
|
|
|
402
439
|
The pixels no longer occupied by the original image as a result of the
|
|
403
440
|
offset will be filled in with 'edge_fill_value'.
|
|
404
441
|
"""
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
442
|
+
if inplace and not keep_input_dtype:
|
|
443
|
+
raise ValueError("inplace=True doesn't make sense with keep_input_dtype=False")
|
|
444
|
+
if distance < -1 or distance > 1:
|
|
445
|
+
raise ValueError('subpixel offset distance must be between -1 and 1')
|
|
446
|
+
if abs(distance) < 1e-6:
|
|
447
|
+
return image if inplace else image.copy()
|
|
448
|
+
if edge_mode not in ['extend', 'wrap', 'reflect', 'constant']:
|
|
449
|
+
raise ValueError('edge_mode must be one of "extend", "wrap",'
|
|
450
|
+
' "reflect", or "constant"')
|
|
451
|
+
if fill_transparent:
|
|
452
|
+
raise NotImplementedError('fill_transparent not yet implemented')
|
|
453
|
+
|
|
454
|
+
if not inplace:
|
|
455
|
+
if keep_input_dtype:
|
|
456
|
+
image = image.copy()
|
|
457
|
+
else:
|
|
458
|
+
image = image.astype('float64')
|
|
459
|
+
|
|
460
|
+
abs_distance = abs(distance)
|
|
461
|
+
sign = 1 if distance > 0 else -1
|
|
462
|
+
axis_size = image.shape[axis]
|
|
463
|
+
slicer: List[Union[slice, int]] = [slice(None)] * image.ndim
|
|
464
|
+
|
|
465
|
+
# Handle last slice which attempts to pull data from out of bounds
|
|
466
|
+
final_index = 0 if sign > 0 else -1
|
|
467
|
+
slicer[axis] = final_index
|
|
468
|
+
final_slice = tuple(slicer)
|
|
469
|
+
|
|
470
|
+
if edge_mode == 'extend':
|
|
471
|
+
edge_data = image[final_slice].copy()
|
|
472
|
+
elif edge_mode == 'wrap':
|
|
473
|
+
slicer[axis] -= sign
|
|
474
|
+
edge_data = image[tuple(slicer)].copy()
|
|
475
|
+
elif edge_mode == 'reflect':
|
|
476
|
+
slicer[axis] += sign
|
|
477
|
+
edge_data = image[tuple(slicer)].copy()
|
|
478
|
+
elif edge_mode == 'constant':
|
|
479
|
+
if constant_edge_value is None:
|
|
480
|
+
raise ValueError('constant_edge_value must be provided when'
|
|
481
|
+
' edge_mode is "constant"')
|
|
482
|
+
edge_data = constant_edge_value
|
|
483
|
+
|
|
484
|
+
loop_range = range(axis_size - 1, 0, -1) if sign > 0 else range(0, axis_size - 1, 1)
|
|
485
|
+
|
|
486
|
+
for i in tqdm(loop_range, desc=progress_msg, disable=not bool(progress_msg)):
|
|
487
|
+
slicer[axis] = i
|
|
488
|
+
current_slice = tuple(slicer)
|
|
489
|
+
slicer[axis] = i - sign
|
|
490
|
+
adjacent_slice = tuple(slicer)
|
|
491
|
+
|
|
492
|
+
new_values = (
|
|
493
|
+
(1 - abs_distance) * image[current_slice]
|
|
494
|
+
+ abs_distance * image[adjacent_slice]
|
|
495
|
+
)
|
|
496
|
+
if keep_input_dtype and np.issubdtype(image.dtype, np.integer):
|
|
497
|
+
new_values = iround(new_values, output_dtype=image.dtype)
|
|
498
|
+
image[current_slice] = new_values
|
|
499
|
+
|
|
500
|
+
image[final_slice] = (
|
|
501
|
+
(1 - abs_distance) * image[final_slice]
|
|
502
|
+
+ abs_distance * edge_data
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
if not inplace:
|
|
506
|
+
return image
|
|
455
507
|
|
|
456
508
|
|
|
457
509
|
def paste(image: np.ndarray,
|
|
458
510
|
target: np.ndarray,
|
|
459
|
-
offset: Iterable[float]
|
|
511
|
+
offset: Iterable[float],
|
|
512
|
+
subpixel_edge_mode: Literal['extend', 'wrap',
|
|
513
|
+
'reflect', 'constant'] = 'extend',
|
|
514
|
+
constant_edge_value: Optional[float] = None,
|
|
515
|
+
verbose: bool = False):
|
|
460
516
|
"""
|
|
461
517
|
Paste an image onto another image at a given offset.
|
|
462
518
|
|
|
@@ -468,12 +524,20 @@ def paste(image: np.ndarray,
|
|
|
468
524
|
except TypeError:
|
|
469
525
|
offset = [offset] * len(image.shape)
|
|
470
526
|
if len(offset) != image.ndim:
|
|
471
|
-
raise ValueError('The length of the offset must match the number of
|
|
527
|
+
raise ValueError('The length of the offset must match the number of '
|
|
528
|
+
'dimensions in the image.')
|
|
472
529
|
offset_int = [int(x) for x in offset]
|
|
473
530
|
offset_subpixel = [x - int(x) for x in offset]
|
|
531
|
+
if any(not eq(o, 0) for o in offset_subpixel):
|
|
532
|
+
image = image.copy()
|
|
474
533
|
for i, offset in enumerate(offset_subpixel):
|
|
534
|
+
progress_msg = f'Performing subpixel offset along axis {i}' if verbose else None
|
|
475
535
|
if not eq(offset, 0):
|
|
476
|
-
_offset_subpixel(image, offset, i,
|
|
536
|
+
_offset_subpixel(image, offset, i,
|
|
537
|
+
edge_mode=subpixel_edge_mode,
|
|
538
|
+
constant_edge_value=constant_edge_value,
|
|
539
|
+
inplace=True,
|
|
540
|
+
progress_msg=progress_msg)
|
|
477
541
|
|
|
478
542
|
source_range = [slice(max(0, -o), min(s, t-o))
|
|
479
543
|
for o, s, t in zip(offset_int, image.shape, target.shape)]
|
|
@@ -60,30 +60,46 @@ def isint(n):
|
|
|
60
60
|
|
|
61
61
|
def find_channel_axis(data,
|
|
62
62
|
possible_channel_axes=[-1, 0],
|
|
63
|
-
possible_channel_lengths=[2, 3, 4]
|
|
63
|
+
possible_channel_lengths=[2, 3, 4],
|
|
64
|
+
minimum_image_size=(32, 32)) -> Union[int, None]:
|
|
64
65
|
"""
|
|
65
66
|
If the given numpy array has a shape suggesting that it has a
|
|
66
|
-
channel (color) axis (that is,
|
|
67
|
+
channel (color) axis (that is, an axis with length 2 (2-color),
|
|
67
68
|
3 (RGB), or 4 (RGBA)), return the index of that axis.
|
|
68
69
|
|
|
70
|
+
With the default parameters, the smallest array shapes that will be
|
|
71
|
+
recognized as having a channel axis are (2, 32, 32) and (32, 32, 2).
|
|
72
|
+
Note that shapes (32, 2) and (32, 16, 2) will NOT be recognized due
|
|
73
|
+
to the minimum_image_size parameter. If you have arrays that look
|
|
74
|
+
like this and you want to recognize them as having a channel axis,
|
|
75
|
+
you can adjust the minimum_image_size parameter accordingly.
|
|
76
|
+
|
|
69
77
|
Parameters
|
|
70
78
|
----------
|
|
71
79
|
data : numpy.ndarray
|
|
72
80
|
The numpy array to check for a channel axis.
|
|
73
81
|
|
|
74
|
-
possible_channel_axes : int or list of int, default [
|
|
75
|
-
If None, any axis having length
|
|
76
|
-
a channel axis.
|
|
82
|
+
possible_channel_axes : int or list of int, default [-1, 0]
|
|
83
|
+
If None, any axis having length in possible_channel_lengths will
|
|
84
|
+
be considered a channel axis.
|
|
77
85
|
If an int, only that axis index will be checked.
|
|
78
|
-
If a list of ints, all axes with those indices will be checked
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
If a list of ints, all axes with those indices will be checked,
|
|
87
|
+
and the first one that satisfies the other criteria will be returned.
|
|
88
|
+
The default value of [-1, 0] checks the last and first axes (in that
|
|
89
|
+
order), which is almost always where a channel axis will be found.
|
|
81
90
|
|
|
82
91
|
possible_channel_lengths : int or list of int, default [2, 3, 4]
|
|
83
92
|
If an int, only that length will be considered a channel axis.
|
|
84
93
|
If a list of ints, an axis with any of those lengths will be considered
|
|
85
94
|
a channel axis.
|
|
86
95
|
|
|
96
|
+
minimum_image_size : tuple of int, default (32, 32)
|
|
97
|
+
In addition to having an axis with a length in possible_channel_lengths,
|
|
98
|
+
the data must also have other axes with at least these lengths in order
|
|
99
|
+
to be considered to have a channel axis. This prevents small arrays
|
|
100
|
+
with shapes like (3, 3, 3) or (128, 3) from being misinterpreted
|
|
101
|
+
as having a channel axis when they are probably not intended as such.
|
|
102
|
+
|
|
87
103
|
Returns
|
|
88
104
|
-------
|
|
89
105
|
int or None
|
|
@@ -99,16 +115,46 @@ def find_channel_axis(data,
|
|
|
99
115
|
axis, so be careful not to do a test like `if find_channel_axis(data):`
|
|
100
116
|
because 0 will evaluate to False even though the data has a channel axis.
|
|
101
117
|
Instead write `if find_channel_axis(data) is not None:`
|
|
118
|
+
|
|
119
|
+
Examples
|
|
120
|
+
--------
|
|
121
|
+
>>> npimage.find_channel_axis(np.empty((3, 1024, 1024)))
|
|
122
|
+
0
|
|
123
|
+
>>> npimage.find_channel_axis(np.empty((1024, 1024, 3)))
|
|
124
|
+
2
|
|
125
|
+
>>> npimage.find_channel_axis(np.empty((1024, 3, 1024)))
|
|
126
|
+
None # Due to possible_channel_axes=[-1, 0] not being met
|
|
127
|
+
>>> npimage.find_channel_axis(np.empty((256, 128, 128)))
|
|
128
|
+
None # Due to possible_channel_lengths=[2, 3, 4] not being met
|
|
129
|
+
>>> npimage.find_channel_axis(np.empty((1024, 4)))
|
|
130
|
+
None # Due to minimum_image_size=(32, 32) not being met
|
|
131
|
+
>>> npimage.find_channel_axis(np.empty((30, 30, 4)))
|
|
132
|
+
None # Due to minimum_image_size=(32, 32) not being met
|
|
133
|
+
>>> npimage.find_channel_axis(np.empty((30, 4)), minimum_image_size=0)
|
|
134
|
+
1
|
|
102
135
|
"""
|
|
103
136
|
if isinstance(possible_channel_axes, int):
|
|
104
137
|
possible_channel_axes = [possible_channel_axes]
|
|
105
138
|
if possible_channel_axes is None:
|
|
106
139
|
possible_channel_axes = range(data.ndim)
|
|
140
|
+
|
|
107
141
|
if isinstance(possible_channel_lengths, int):
|
|
108
142
|
possible_channel_lengths = [possible_channel_lengths]
|
|
143
|
+
|
|
144
|
+
if isinstance(minimum_image_size, int):
|
|
145
|
+
minimum_image_size = (minimum_image_size,)
|
|
146
|
+
|
|
109
147
|
for axis in possible_channel_axes:
|
|
110
|
-
if data.shape[axis] in possible_channel_lengths:
|
|
111
|
-
|
|
148
|
+
if data.shape[axis] not in possible_channel_lengths:
|
|
149
|
+
continue
|
|
150
|
+
other_axis_lengths = [data.shape[i] for i in range(data.ndim) if i != axis]
|
|
151
|
+
if len(other_axis_lengths) < len(minimum_image_size):
|
|
152
|
+
continue
|
|
153
|
+
other_axis_lengths = sorted(other_axis_lengths, reverse=True)
|
|
154
|
+
other_axis_lengths = other_axis_lengths[:len(minimum_image_size)]
|
|
155
|
+
if any(i < j for i, j in zip(other_axis_lengths, minimum_image_size)):
|
|
156
|
+
continue
|
|
157
|
+
return axis % data.ndim
|
|
112
158
|
return None
|
|
113
159
|
|
|
114
160
|
|
|
@@ -844,9 +844,10 @@ def save_video(data, filename, time_axis=0, color_axis=None, overwrite=False,
|
|
|
844
844
|
raise ImportError('Missing optional dependency for video processing,'
|
|
845
845
|
' run `pip install av tqdm`')
|
|
846
846
|
|
|
847
|
-
filename =
|
|
847
|
+
filename = str(filename)
|
|
848
848
|
if filename.split('.')[-1].lower() not in supported_extensions:
|
|
849
849
|
filename += '.mp4'
|
|
850
|
+
filename = Path(filename).expanduser()
|
|
850
851
|
if filename.exists() and not overwrite:
|
|
851
852
|
raise FileExistsError(f'File {filename} already exists. '
|
|
852
853
|
'Set overwrite=True to overwrite.')
|
|
@@ -879,7 +880,7 @@ def save_video(data, filename, time_axis=0, color_axis=None, overwrite=False,
|
|
|
879
880
|
n_frames = data.shape[0]
|
|
880
881
|
height, width = data.shape[1:]
|
|
881
882
|
|
|
882
|
-
extension = filename.
|
|
883
|
+
extension = filename.suffix.lower().lstrip('.')
|
|
883
884
|
if extension == 'mp4':
|
|
884
885
|
pad = [[0, 0], [0, 0], [0, 0]]
|
|
885
886
|
if height % 2 != 0:
|
|
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
|