stouputils.image module#

This module provides little utilities for image processing.

  • image_resize: Resize an image while preserving its aspect ratio by default.

  • auto_crop: Automatically crop an image to remove zero/uniform regions.

  • numpy_to_gif: Generate a ‘.gif’ file from a 3D numpy array for visualization.

  • numpy_to_obj: Generate a ‘.obj’ file from a 3D numpy array using marching cubes.

See stouputils.data_science.data_processing for lots more image processing utilities.

image_resize(image: Image.Image | NDArray[np.number], max_result_size: int, resampling: Image.Resampling | None = None, min_or_max: ~collections.abc.Callable[[int, int], int] = <built-in function max>, return_type: type[Image.Image | NDArray[np.number]] | str = 'same', keep_aspect_ratio: bool = True) Any[source]#

Resize an image while preserving its aspect ratio by default. Scales the image so that its largest dimension equals max_result_size.

Parameters:
  • image (Image.Image | np.ndarray) – The image to resize.

  • max_result_size (int) – Maximum size for the largest dimension.

  • resampling (Image.Resampling | None) – PIL resampling filter to use (default: Image.Resampling.LANCZOS).

  • min_or_max (Callable) – Function to use to get the minimum or maximum of the two ratios.

  • return_type (type | str) – Type of the return value (Image.Image, np.ndarray, or “same” to match input type).

  • keep_aspect_ratio (bool) – Whether to keep the aspect ratio.

Returns:

The resized image with preserved aspect ratio.

Return type:

Image.Image | NDArray[np.number]

Examples

>>> # Test with (height x width x channels) numpy array
>>> import numpy as np
>>> array = np.random.randint(0, 255, (100, 50, 3), dtype=np.uint8)
>>> image_resize(array, 100).shape
(100, 50, 3)
>>> image_resize(array, 100, min_or_max=max).shape
(100, 50, 3)
>>> image_resize(array, 100, min_or_max=min).shape
(200, 100, 3)
>>> # Test with PIL Image
>>> from PIL import Image
>>> pil_image: Image.Image = Image.new('RGB', (200, 100))
>>> image_resize(pil_image, 50).size
(50, 25)
>>> # Test with different return types
>>> resized_array = image_resize(array, 50, return_type=np.ndarray)
>>> isinstance(resized_array, np.ndarray)
True
>>> resized_array.shape
(50, 25, 3)
>>> # Test with different resampling methods
>>> image_resize(pil_image, 50, resampling=Image.Resampling.NEAREST).size
(50, 25)
auto_crop(
image: Image.Image | NDArray[np.number],
mask: NDArray[np.bool_] | None = None,
threshold: int | float | Callable[[NDArray[np.number]], int | float] | None = None,
return_type: type[Image.Image | NDArray[np.number]] | str = 'same',
contiguous: bool = True,
) Any[source]#

Automatically crop an image to remove zero or uniform regions.

This function crops the image to keep only the region where pixels are non-zero (or above a threshold). It can work with a mask or directly analyze the image.

Parameters:
  • image (Image.Image | NDArray) – The image to crop.

  • mask (NDArray[np.bool_] | None) – Optional binary mask indicating regions to keep.

  • threshold (int | float | Callable) – Threshold value or function (default: np.min).

  • return_type (type | str) – Type of the return value (Image.Image, NDArray[np.number], or “same” to match input type).

  • contiguous (bool) – If True (default), crop to bounding box. If False, remove entire rows/columns with no content.

Returns:

The cropped image.

Return type:

Image.Image | NDArray[np.number]

Examples

>>> # Test with numpy array with zeros on edges
>>> import numpy as np
>>> array = np.zeros((100, 100, 3), dtype=np.uint8)
>>> array[20:80, 30:70] = 255  # White rectangle in center
>>> cropped = auto_crop(array, return_type=np.ndarray)
>>> cropped.shape
(60, 40, 3)
>>> # Test with custom mask
>>> mask = np.zeros((100, 100), dtype=bool)
>>> mask[10:90, 10:90] = True
>>> cropped_with_mask = auto_crop(array, mask=mask, return_type=np.ndarray)
>>> cropped_with_mask.shape
(80, 80, 3)
>>> # Test with PIL Image
>>> from PIL import Image
>>> pil_image = Image.new('RGB', (100, 100), (0, 0, 0))
>>> from PIL import ImageDraw
>>> draw = ImageDraw.Draw(pil_image)
>>> draw.rectangle([25, 25, 75, 75], fill=(255, 255, 255))
>>> cropped_pil = auto_crop(pil_image)
>>> cropped_pil.size
(51, 51)
>>> # Test with threshold
>>> array_gray = np.ones((100, 100), dtype=np.uint8) * 10
>>> array_gray[20:80, 30:70] = 255
>>> cropped_threshold = auto_crop(array_gray, threshold=50, return_type=np.ndarray)
>>> cropped_threshold.shape
(60, 40)
>>> # Test with callable threshold (using lambda to avoid min value)
>>> array_gray2 = np.ones((100, 100), dtype=np.uint8) * 10
>>> array_gray2[20:80, 30:70] = 255
>>> cropped_max = auto_crop(array_gray2, threshold=lambda x: 50, return_type=np.ndarray)
>>> cropped_max.shape
(60, 40)
>>> # Test with non-contiguous crop
>>> array_sparse = np.zeros((100, 100, 3), dtype=np.uint8)
>>> array_sparse[10, 10] = 255
>>> array_sparse[50, 50] = 255
>>> array_sparse[90, 90] = 255
>>> cropped_contiguous = auto_crop(array_sparse, contiguous=True, return_type=np.ndarray)
>>> cropped_contiguous.shape  # Bounding box from (10,10) to (90,90)
(81, 81, 3)
>>> cropped_non_contiguous = auto_crop(array_sparse, contiguous=False, return_type=np.ndarray)
>>> cropped_non_contiguous.shape  # Only rows/cols 10, 50, 90
(3, 3, 3)
>>> # Test with 3D crop on depth dimension
>>> array_3d = np.zeros((50, 50, 10), dtype=np.uint8)
>>> array_3d[10:40, 10:40, 2:8] = 255  # Content only in depth slices 2-7
>>> cropped_3d = auto_crop(array_3d, contiguous=True, return_type=np.ndarray)
>>> cropped_3d.shape  # Should crop all 3 dimensions
(30, 30, 6)
numpy_to_gif(
path: str,
array: NDArray[np.integer | np.floating | np.bool_],
duration: int = 100,
loop: int = 0,
mkdir: bool = True,
**kwargs: Any,
) None[source]#

Generate a ‘.gif’ file from a numpy array for 3D visualization.

Parameters:
  • path (str) – Path to the output .gif file.

  • array (NDArray) – Numpy array to be dumped (must be 3D with depth as first axis, e.g. 64x1024x1024).

  • duration (int) – Duration between frames in milliseconds.

  • loop (int) – Number of loops (0 = infinite).

  • mkdir (bool) – Create the directory if it does not exist.

  • **kwargs (Any) – Additional keyword arguments for PIL.Image.save().

Examples

> array = np.random.randint(0, 256, (10, 100, 100), dtype=np.uint8)
> numpy_to_gif("output_10_frames_100x100.gif", array, duration=200, loop=0)

> total_duration = 1000  # 1 second
> numpy_to_gif("output_1s.gif", array, duration=total_duration // len(array))
numpy_to_obj(
path: str,
array: NDArray[np.integer | np.floating | np.bool_],
threshold: float = 0.5,
step_size: int = 1,
pad_array: bool = True,
verbose: int = 0,
) None[source]#

Generate a ‘.obj’ file from a numpy array for 3D visualization using marching cubes.

Parameters:
  • path (str) – Path to the output .obj file.

  • array (NDArray) – Numpy array to be dumped (must be 3D).

  • threshold (float) – Threshold level for marching cubes (0.5 for binary data).

  • step_size (int) – Step size for marching cubes (higher = simpler mesh, faster generation).

  • pad_array (bool) – If True, pad array with zeros to ensure closed volumes for border cells.

  • verbose (int) – Verbosity level (0 = no output, 1 = some output, 2 = full output).

Examples

> array = np.random.rand(64, 64, 64) > 0.5  # Binary volume
> numpy_to_obj("output_mesh.obj", array, threshold=0.5, step_size=2, pad_array=True, verbose=1)

> array = my_3d_data  # Some 3D numpy array (e.g. human lung scan)
> numpy_to_obj("output_mesh.obj", array, threshold=0.3)