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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: numpyimage
3
- Version: 2.2.0
3
+ Version: 2.3.0
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: GNU GENERAL PUBLIC LICENSE
@@ -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 "{extension}" not supported/recognized.')
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, overwrite=False, dim_order='yx',
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 as a video, with a specified axis as the time axis.
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
- 'You can install it with "pip install av".')
387
- data = np.moveaxis(data, time_axis, 0)
388
- if 'xy' in dim_order:
389
- data = data.swapaxes(1, 2)
390
- n_frames = data.shape[0]
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 data.shape[1] % 2 != 0:
420
+ if height % 2 != 0:
401
421
  pad[1][1] = 1
402
- if data.shape[2] % 2 != 0:
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
- frame = av.VideoFrame.from_ndarray(data[frame_i], format='gray')
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=True,
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])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: numpyimage
3
- Version: 2.2.0
3
+ Version: 2.3.0
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: GNU GENERAL PUBLIC LICENSE
@@ -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
@@ -4,7 +4,7 @@ build-backend = 'setuptools.build_meta'
4
4
 
5
5
  [project]
6
6
  name = 'numpyimage'
7
- version = '2.2.0'
7
+ version = '2.3.0'
8
8
  description = 'Load, save, & manipulate image files as numpy arrays'
9
9
  readme.file = 'README.md'
10
10
  readme.content-type = 'text/markdown'
@@ -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