zignal
zero-dependency image processing library.
Solve the assignment problem using the Hungarian algorithm.
Finds the optimal one-to-one assignment that minimizes or maximizes the total cost in O(n³) time. Handles both square and rectangular matrices.
Parameters
cost_matrix(Matrix): Cost matrix where element (i,j) is the cost of assigning row i to column jpolicy(OptimizationPolicy): Whether to minimize or maximize total cost (default: MIN)
Returns
Assignment: Object containing the optimal assignments and total cost
Examples
from zignal import Matrix, OptimizationPolicy, solve_assignment_problem
matrix = Matrix([[1, 2, 6], [5, 3, 6], [4, 5, 0]])
for p in [OptimizationPolicy.MIN, OptimizationPolicy.MAX]:
result = solve_assignment_problem(matrix, p)
print("minimum cost") if p == OptimizationPolicy.MIN else print("maximum profit")
print(f" - Total cost: {result.total_cost}")
print(f" - Assignments: {result.assignments}")
Sample 3D Perlin noise using Zignal's implementation.
This computes classic Perlin noise with configurable amplitude, frequency, octave count, persistence, and lacunarity. All parameters are applied in a streaming fashion, making it convenient for procedural textures and augmentation workflows.
Parameters
x(float): X coordinate in noise space.y(float): Y coordinate in noise space.z(float, optional): Z coordinate (default 0.0). Use for animated noise.amplitude(float, default 1.0): Output scaling factor (> 0).frequency(float, default 1.0): Base spatial frequency (> 0).octaves(int, default 1): Number of summed octaves (1-32).persistence(float, default 0.5): Amplitude decay per octave (0-1).lacunarity(float, default 2.0): Frequency growth per octave (1.0-16).
Returns
float: Perlin noise sample at (x, y, z) with the given parameters.
Image for processing and manipulation.
Pixel access via indexing returns a proxy object that allows in-place
modification. Use .item() on the proxy to extract the color value:
pixel = img[row, col] # Returns pixel proxy
color = pixel.item() # Extracts color object (Rgb/Rgba/int)
This object is iterable: iterating yields (row, col, pixel) in native
dtype in row-major order. For bulk numeric work, prefer to_numpy().
Create a new Image with the specified dimensions and optional fill color.
Parameters
rows(int): Number of rows (height) of the imagecols(int): Number of columns (width) of the imagecolor(optional): Fill color. Can be:- Integer (0-255) for grayscale
- RGB tuple (r, g, b) with values 0-255
- RGBA tuple (r, g, b, a) with values 0-255
- Any color object (Rgb, Hsl, Hsv, etc.)
- Defaults to transparent (0, 0, 0, 0)
dtype(type, optional): Pixel data type specifying storage type.zignal.Grayscale→ single-channel u8 (NumPy shape (H, W, 1))zignal.Rgb(default) → 3-channel RGB (NumPy shape (H, W, 3))zignal.Rgba→ 4-channel RGBA (NumPy shape (H, W, 4))
Examples
# Create a 100x200 black image (default RGB)
img = Image(100, 200)
# Create a 100x200 red image (RGBA)
img = Image(100, 200, (255, 0, 0, 255))
# Create a 100x200 grayscale image with mid-gray fill
img = Image(100, 200, 128, dtype=zignal.Grayscale)
# Create a 100x200 RGB image (dtype overrides the color value)
img = Image(100, 200, (0, 255, 0, 255), dtype=zignal.Rgb)
# Create an image from numpy array dimensions
img = Image(*arr.shape[:2])
# Create with semi-transparent blue (requires RGBA)
img = Image(100, 100, (0, 0, 255, 128), dtype=zignal.Rgba)
Load an image from file (PNG or JPEG).
The pixel format (Grayscale, Rgb, or Rgba) is automatically determined from the file metadata. For PNGs, the format matches the file's color type. For JPEGs, grayscale images load as Grayscale, color images as Rgb.
Parameters
path(str): Path to the PNG or JPEG file to load
Returns
Image: A new Image object with pixels in the format matching the file
Raises
FileNotFoundError: If the file does not existValueError: If the file format is unsupportedMemoryError: If allocation fails during loadingPermissionError: If read permission is denied
Examples
# Load images with automatic format detection
img = Image.load("photo.png") # May be Rgba
img2 = Image.load("grayscale.jpg") # Will be Grayscale
img3 = Image.load("rgb.png") # Will be Rgb
# Check format after loading
print(img.dtype) # e.g., Rgba, Rgb, or Grayscale
Load an image from an in-memory bytes-like object (PNG or JPEG).
Accepts any object that implements the Python buffer protocol, such as
bytes, bytearray, or memoryview. The image format is detected from
the data's file signature, so no file extension is required.
Parameters
data(bytes-like): Raw PNG or JPEG bytes.
Returns
Image: A new Image with pixel storage matching the encoded file (Grayscale, Rgb, or Rgba).
Raises
ValueError: If the buffer is empty or the format is unsupportedMemoryError: If allocation fails during decoding
Examples
payload = http_response.read()
img = Image.load_from_bytes(payload)
Save the image to a file (PNG or JPEG format).
The format is determined by the file extension (.png, .jpg, or .jpeg).
Parameters
path(str): Path where the image file will be saved. Must have .png, .jpg, or .jpeg extension.
Raises
ValueError: If the file has an unsupported extensionMemoryError: If allocation fails during savePermissionError: If write permission is deniedFileNotFoundError: If the directory does not exist
Examples
img = Image.load("input.png")
img.save("output.png") # Save as PNG
img.save("output.jpg") # Save as JPEG
Create a deep copy of the image.
Returns a new Image with the same dimensions and pixel data, but with its own allocated memory.
Examples
img = Image.load("photo.png")
copy = img.copy()
# Modifying copy doesn't affect original
copy[0, 0] = (255, 0, 0)
Fill the entire image with a solid color.
Parameters
color: Fill color. Can be:- Integer (0-255) for grayscale images
- RGB tuple (r, g, b) with values 0-255
- RGBA tuple (r, g, b, a) with values 0-255
- Any color object (Rgb, Hsl, Hsv, etc.)
Examples
img = Image(100, 100)
img.fill((255, 0, 0)) # Fill with red
Create a view of the image or a sub-region (zero-copy).
Creates a new Image that shares the same underlying pixel data. Changes to the view affect the original image and vice versa.
Parameters
rect(Rectangle | tuple[float, float, float, float] | None): Optional rectangle defining the sub-region to view. If None, creates a view of the entire image. When providing a tuple, it should be (left, top, right, bottom).
Returns
Image: A view of the image that shares the same pixel data
Examples
img = Image.load("photo.png")
# View entire image
view = img.view()
# View sub-region
rect = Rectangle(10, 10, 100, 100)
sub = img.view(rect)
# Modifications to view affect original
sub.fill((255, 0, 0)) # Fills region in original image
Set the image border outside a rectangle to a value.
Sets pixels outside the given rectangle to the provided color/value, leaving the interior untouched. The rectangle may be provided as a Rectangle or a tuple (left, top, right, bottom). It is clipped to the image bounds.
Parameters
rect(Rectangle | tuple[float, float, float, float]): Inner rectangle to preserve.color(optional): Fill value for border. Accepts the same types asfill. If omitted, uses zeros for the current dtype (0, Rgb(0,0,0), or Rgba(0,0,0,0)).
Examples
img = Image(100, 100)
rect = Rectangle(10, 10, 90, 90)
img.set_border(rect) # zero border
img.set_border(rect, (255, 0, 0)) # red border
# Common pattern: set a uniform 16px border using shrink()
img.set_border(img.get_rectangle().shrink(16))
Check if the image data is stored contiguously in memory.
Returns True if pixels are stored without gaps (stride == cols), False for views or images with custom strides.
Examples
img = Image(100, 100)
print(img.is_contiguous()) # True
view = img.view(Rectangle(10, 10, 50, 50))
print(view.is_contiguous()) # False
Convert the image to a different pixel data type.
Supported targets: Grayscale, Rgb, Rgba.
Returns a new Image with the requested format.
Get a Canvas object for drawing on this image.
Returns a Canvas that can be used to draw shapes, lines, and text directly onto the image pixels.
Examples
img = Image(200, 200)
cv = img.canvas()
cv.draw_circle(100, 100, 50, (255, 0, 0))
cv.fill_rect(10, 10, 50, 50, (0, 255, 0))
Calculate Peak Signal-to-Noise Ratio between two images.
PSNR is a quality metric where higher values indicate greater similarity. Typical values: 30-50 dB (higher is better). Returns infinity for identical images.
Parameters
other(Image): The image to compare against. Must have same dimensions and dtype.
Returns
float: PSNR value in decibels (dB), or inf for identical images
Raises
ValueError: If images have different dimensions or dtypes
Examples
original = Image.load("original.png")
compressed = Image.load("compressed.png")
quality = original.psnr(compressed)
print(f"PSNR: {quality:.2f} dB")
Calculate Structural Similarity Index between two images.
SSIM is a perceptual metric in the range [0, 1] where higher values indicate greater structural similarity.
Parameters
other(Image): The image to compare against. Must have same dimensions and dtype.
Returns
float: SSIM value between 0 and 1 (inclusive)
Raises
ValueError: If images have different dimensions or dtypes, or are smaller than 11x11
Examples
original = Image.load("frame.png")
processed = pipeline(original)
score = original.ssim(processed)
print(f"SSIM: {score:.4f}")
Calculate mean absolute pixel error between two images, normalized to [0, 1].
Parameters
other(Image): The image to compare against. Must have same dimensions and dtype.
Returns
float: Mean absolute pixel error in [0, 1] (0 = identical, higher = more different)
Raises
ValueError: If images have different dimensions or dtypes
Examples
original = Image.load("photo.png")
noisy = add_noise(original)
percent = original.mean_pixel_error(noisy) * 100
print(f"Mean pixel error: {percent:.3f}%")
Create Image from a NumPy array with dtype uint8.
Zero-copy is used for arrays with these shapes:
- Grayscale: (rows, cols, 1) → Image(Grayscale)
- RGB: (rows, cols, 3) → Image(Rgb)
- RGBA: (rows, cols, 4) → Image(Rgba)
The array can have row strides (e.g., from views or slicing) as long as pixels
within each row are contiguous. For arrays with incompatible strides (e.g., transposed),
use numpy.ascontiguousarray() first.
Parameters
array(NDArray[np.uint8]): NumPy array with shape (rows, cols, 1), (rows, cols, 3) or (rows, cols, 4) and dtype uint8. Pixels within rows must be contiguous.
Raises
TypeError: If array is None or has wrong dtypeValueError: If array has wrong shape or incompatible strides
Notes
The array can have row strides (padding between rows) but pixels within each row must be contiguous. For incompatible layouts (e.g., transposed arrays), use np.ascontiguousarray() first:
arr = np.ascontiguousarray(arr)
img = Image.from_numpy(arr)
Examples
arr = np.zeros((100, 200, 3), dtype=np.uint8)
img = Image.from_numpy(arr)
print(img.rows, img.cols)
# Output: 100 200
Convert the image to a NumPy array (zero-copy when possible).
Returns an array in the image's native dtype:
Grayscale → shape (rows, cols, 1)
Rgb → shape (rows, cols, 3)
Rgba → shape (rows, cols, 4)
Examples
img = Image.load("photo.png")
arr = img.to_numpy()
print(arr.shape, arr.dtype)
# Example: (H, W, C) uint8 where C is 1, 3, or 4
Resize the image to the specified size.
Parameters
size(float or tuple[int, int]):- If float: scale factor (e.g., 0.5 for half size, 2.0 for double size)
- If tuple: target dimensions as (rows, cols)
method(Interpolation, optional): Interpolation method to use. Default isInterpolation.BILINEAR.
Resize image to fit within the specified size while preserving aspect ratio.
The image is scaled to fit within the target dimensions and centered with black borders (letterboxing) to maintain the original aspect ratio.
Parameters
size(int or tuple[int, int]):- If int: creates a square output of size x size
- If tuple: target dimensions as (rows, cols)
method(Interpolation, optional): Interpolation method to use. Default isInterpolation.BILINEAR.
Rotate the image by the specified angle around its center.
The output image is automatically sized to fit the entire rotated image without clipping.
Parameters
angle(float): Rotation angle in radians counter-clockwise.method(Interpolation, optional): Interpolation method to use. Default isInterpolation.BILINEAR.
Examples
import math
img = Image.load("photo.png")
# Rotate 45 degrees with default bilinear interpolation
rotated = img.rotate(math.radians(45))
# Rotate 90 degrees with nearest neighbor (faster, lower quality)
rotated = img.rotate(math.radians(90), Interpolation.NEAREST_NEIGHBOR)
# Rotate -30 degrees with Lanczos (slower, higher quality)
rotated = img.rotate(math.radians(-30), Interpolation.LANCZOS)
Apply a geometric transform to the image.
This method warps an image using a geometric transform (Similarity, Affine, or Projective). For each pixel in the output image, it applies the transform to find the corresponding location in the source image and samples using the specified interpolation method.
Parameters
transform: A geometric transform object (SimilarityTransform, AffineTransform, or ProjectiveTransform)shape(optional): Output image shape as (rows, cols) tuple. Defaults to input image shape.method(optional): Interpolation method. Defaults to Interpolation.BILINEAR.
Examples
# Apply similarity transform
from_points = [(0, 0), (100, 0), (100, 100)]
to_points = [(10, 10), (110, 20), (105, 115)]
transform = SimilarityTransform(from_points, to_points)
warped = img.warp(transform)
# Apply with custom output size and interpolation
warped = img.warp(transform, shape=(512, 512), method=Interpolation.BICUBIC)
Flip image left-to-right (horizontal mirror).
Returns a new image that is a horizontal mirror of the original.
flipped = img.flip_left_right()
Flip image top-to-bottom (vertical mirror).
Returns a new image that is a vertical mirror of the original.
flipped = img.flip_top_bottom()
Extract a rectangular region from the image.
Returns a new Image containing the cropped region. Pixels outside the original image bounds are filled with transparent black (0, 0, 0, 0).
Parameters
rect(Rectangle): The rectangular region to extract
Examples
img = Image.load("photo.png")
rect = Rectangle(10, 10, 110, 110) # 100x100 region starting at (10, 10)
cropped = img.crop(rect)
print(cropped.rows, cropped.cols) # 100 100
Extract a rotated rectangular region from the image and resample it.
Returns a new Image containing the extracted and resampled region.
Parameters
rect(Rectangle): The rectangular region to extract (before rotation)angle(float, optional): Rotation angle in radians (counter-clockwise). Default: 0.0size(int or tuple[int, int], optional). If not specified, uses the rectangle's dimensions.- If int: output is a square of side
size - If tuple: output size as (rows, cols)
- If int: output is a square of side
method(Interpolation, optional): Interpolation method. Default: BILINEAR
Examples
import math
img = Image.load("photo.png")
rect = Rectangle(10, 10, 110, 110)
# Extract without rotation
extracted = img.extract(rect)
# Extract with 45-degree rotation
rotated = img.extract(rect, angle=math.radians(45))
# Extract and resize to specific dimensions
resized = img.extract(rect, size=(50, 75))
# Extract to a 64x64 square
square = img.extract(rect, size=64)
Insert a source image into this image at a specified rectangle with optional rotation.
This method modifies the image in-place.
Parameters
source(Image): The image to insertrect(Rectangle): Destination rectangle where the source will be placedangle(float, optional): Rotation angle in radians (counter-clockwise). Default: 0.0method(Interpolation, optional): Interpolation method. Default: BILINEARblend_mode(Blending, optional): Compositing mode for RGBA images. Default: NONE
Examples
import math
canvas = Image(500, 500)
logo = Image.load("logo.png")
# Insert at top-left
rect = Rectangle(10, 10, 110, 110)
canvas.insert(logo, rect)
# Insert with rotation
rect2 = Rectangle(200, 200, 300, 300)
canvas.insert(logo, rect2, angle=math.radians(45))
Apply a box blur to the image.
Parameters
radius(int): Non-negative blur radius in pixels.0returns an unmodified copy.
Examples
img = Image.load("photo.png")
soft = img.box_blur(2)
identity = img.box_blur(0) # no-op copy
Apply a median blur (order-statistic filter) to the image.
Parameters
radius(int): Non-negative blur radius in pixels.0returns an unmodified copy.
Notes
- Uses
BorderMode.MIRRORat the image edges to avoid introducing artificial borders.
Examples
img = Image.load("noisy.png")
denoised = img.median_blur(2)
Apply a minimum filter (rank 0 percentile) to the image.
Parameters
radius(int): Non-negative blur radius in pixels.0returns an unmodified copy.border(BorderMode, optional): Border handling strategy. Defaults toBorderMode.MIRROR.
Use case
- Morphological erosion to remove "salt" noise or shrink bright speckles.
Apply a maximum filter (rank 1 percentile) to the image.
Parameters
radius(int): Non-negative blur radius in pixels.border(BorderMode, optional): Border handling strategy. Defaults toBorderMode.MIRROR.
Use case
- Morphological dilation to expand highlights or fill gaps in masks.
Apply a midpoint filter (average of min and max) to the image.
Parameters
radius(int): Non-negative blur radius.border(BorderMode, optional): Border handling strategy. Defaults toBorderMode.MIRROR.
Use case
- Softens impulse noise while preserving thin edges (midpoint between min/max).
Apply a percentile blur (order-statistic filter) to the image.
Parameters
radius(int): Non-negative blur radius defining the neighborhood window.percentile(float): Value in the range [0.0, 1.0] selecting which ordered pixel to keep.border(BorderMode, optional): Border handling strategy. Defaults toBorderMode.MIRROR.
Use case
- Fine control over ordered statistics (e.g.,
percentile=0.1suppresses bright outliers).
Examples
img = Image.load("noisy.png")
median = img.percentile_blur(2, 0.5)
max_filter = img.percentile_blur(1, 1.0, border=zignal.BorderMode.ZERO)
Apply an alpha-trimmed mean blur, discarding a fraction of low/high pixels.
Parameters
radius(int): Non-negative blur radius.trim_fraction(float): Fraction in [0, 0.5) removed from both tails.border(BorderMode, optional): Border handling strategy. Defaults toBorderMode.MIRROR.
Use case
- Robust alternative to averaging that discards extremes (hot pixels, specular highlights).
Apply Gaussian blur to the image.
Parameters
sigma(float): Standard deviation of the Gaussian kernel. Must be > 0.
Examples
img = Image.load("photo.png")
blurred = img.gaussian_blur(2.0)
blurred_soft = img.gaussian_blur(5.0) # More blur
Sharpen the image using unsharp masking (2 * self - blur_box).
Parameters
radius(int): Non-negative blur radius used to compute the unsharp mask.0returns an unmodified copy.
Examples
img = Image.load("photo.png")
crisp = img.sharpen(2)
identity = img.sharpen(0) # no-op copy
Invert the colors of the image.
Creates a negative/inverted version of the image where:
- Grayscale pixels: 255 - value
- RGB pixels: inverts each channel (255 - r, 255 - g, 255 - b)
- RGBA pixels: inverts RGB channels while preserving alpha
Examples
img = Image.load("photo.png")
inverted = img.invert()
# Works with all image types
gray = Image(100, 100, 128, dtype=zignal.Grayscale)
gray_inv = gray.invert() # pixels become 127
Automatically adjust image contrast by stretching the intensity range.
Analyzes the histogram and remaps pixel values so the darkest pixels become black (0) and brightest become white (255).
Parameters
cutoff(float, optional): Rate of pixels to ignore at extremes (0-0.5). Default: 0. For example, 0.02 ignores the darkest and brightest 2% of pixels, helping to remove outliers.
Returns
A new image with adjusted contrast.
Examples
img = Image.load("photo.png")
# Basic auto-contrast
enhanced = img.autocontrast()
# Ignore 2% outliers on each end
enhanced = img.autocontrast(cutoff=0.02)
Equalize the histogram of the image to improve contrast.
Redistributes pixel intensities to achieve a more uniform histogram, which typically enhances contrast in images with poor contrast or uneven lighting conditions. The technique maps the cumulative distribution function (CDF) of pixel values to create a more even spread of intensities across the full range.
For color images (RGB/RGBA), each channel is equalized independently.
Returns
Image: New image with equalized histogram
Example
import zignal
# Load an image with poor contrast
img = zignal.Image.load("low_contrast.jpg")
# Apply histogram equalization
equalized = img.equalize()
# Save the result
equalized.save("equalized.jpg")
# Compare with autocontrast
auto = img.autocontrast(cutoff=0.02)
Apply motion blur effect to the image.
Motion blur simulates camera or object movement during exposure. Three types of motion blur are supported:
MotionBlur.linear()- Linear motion blurMotionBlur.radial_zoom()- Radial zoom blurMotionBlur.radial_spin()- Radial spin blur
Examples
from zignal import Image, MotionBlur
import math
img = Image.load("photo.png")
# Linear motion blur examples
horizontal_blur = img.motion_blur(MotionBlur.linear(angle=0, distance=30)) # Camera panning
vertical_blur = img.motion_blur(MotionBlur.linear(angle=math.pi/2, distance=20)) # Camera shake
diagonal_blur = img.motion_blur(MotionBlur.linear(angle=math.pi/4, distance=25)) # Diagonal motion
# Radial zoom blur examples
center_zoom = img.motion_blur(MotionBlur.radial_zoom(center=(0.5, 0.5), strength=0.7)) # Center zoom burst
off_center_zoom = img.motion_blur(MotionBlur.radial_zoom(center=(0.33, 0.67), strength=0.5)) # Rule of thirds
subtle_zoom = img.motion_blur(MotionBlur.radial_zoom(strength=0.3)) # Subtle effect with defaults
# Radial spin blur examples
center_spin = img.motion_blur(MotionBlur.radial_spin(center=(0.5, 0.5), strength=0.5)) # Center rotation
swirl_effect = img.motion_blur(MotionBlur.radial_spin(center=(0.3, 0.3), strength=0.6)) # Off-center swirl
strong_spin = img.motion_blur(MotionBlur.radial_spin(strength=0.8)) # Strong spin with defaults
Notes
- Linear blur preserves image dimensions
- Radial effects use bilinear interpolation for smooth results
- Strength values closer to 1.0 produce stronger blur effects
Binarize the image using Otsu's method.
The input is converted to grayscale if needed. Returns a tuple containing the binary image (0 or 255 values) and the threshold chosen by the algorithm.
Returns
tuple[Image, int]: (binary image, threshold)
Examples
binary, threshold = img.threshold_otsu()
print(threshold)
Adaptive mean thresholding producing a binary image.
Pixels are compared to the mean of a local window (square of size 2*radius+1).
Values greater than mean - c become 255, others become 0.
Parameters
radius(int, optional): Neighborhood radius. Must be > 0. Default: 6.c(float, optional): Subtracted constant. Default: 5.0.
Returns
Image: Binary image with values 0 or 255.
Apply Sobel edge detection and return the gradient magnitude.
The result is a new grayscale image (dtype=zignal.Grayscale) where
each pixel encodes the edge strength at that location.
Examples
img = Image.load("photo.png")
edges = img.sobel()
Apply Shen-Castan edge detection to the image.
The Shen-Castan algorithm uses ISEF (Infinite Symmetric Exponential Filter) for edge detection with adaptive gradient computation and hysteresis thresholding. Returns a binary edge map where edges are 255 (white) and non-edges are 0 (black).
Parameters
smooth(float, optional): ISEF smoothing factor (0 < smooth < 1). Higher values preserve more detail. Default: 0.9window_size(int, optional): Odd window size for local gradient statistics (>= 3). Default: 7high_ratio(float, optional): Percentile for high threshold selection (0 < high_ratio < 1). Default: 0.99low_rel(float, optional): Low threshold as fraction of high threshold (0 < low_rel < 1). Default: 0.5hysteresis(bool, optional): Enable hysteresis edge linking. When True, weak edges connected to strong edges are preserved. Default: Trueuse_nms(bool, optional): Use non-maximum suppression for single-pixel edges. When True, produces thinner edges. Default: False
Returns
Image: Binary edge map (Grayscale image with values 0 or 255)
Examples
from zignal import Image
img = Image.load("photo.jpg")
# Use default settings
edges = img.shen_castan()
# Low-noise settings for clean images
clean_edges = img.shen_castan(smooth=0.95, high_ratio=0.98)
# High-noise settings for noisy images
denoised_edges = img.shen_castan(smooth=0.7, window_size=11)
# Single-pixel edges with NMS
thin_edges = img.shen_castan(use_nms=True)
Apply Canny edge detection to the image.
The Canny algorithm is a classic multi-stage edge detector that produces thin, well-localized edges with good noise suppression. It consists of five main steps:
- Gaussian smoothing to reduce noise
- Gradient computation using Sobel operators
- Non-maximum suppression to thin edges
- Double thresholding to classify strong and weak edges
- Edge tracking by hysteresis to link edges
Returns a binary edge map where edges are 255 (white) and non-edges are 0 (black).
Parameters
sigma(float, optional): Standard deviation for Gaussian blur. Default: 1.4. Typical values: 1.0-2.0. Higher values = more smoothing, fewer edges.low(float, optional): Lower threshold for hysteresis. Default: 50.high(float, optional): Upper threshold for hysteresis. Default: 150. Should be 2-3x larger thanlow.
Returns
A new grayscale image (dtype=zignal.Grayscale) with binary edge map.
Raises
ValueError: If sigma < 0, thresholds are negative, or low >= high
Examples
img = Image.load("photo.png")
# Use defaults (sigma=1.4, low=50, high=150)
edges = img.canny()
# Custom parameters - more aggressive edge detection (lower thresholds)
edges_sensitive = img.canny(sigma=1.0, low=30, high=90)
# Conservative edge detection (higher thresholds)
edges_conservative = img.canny(sigma=2.0, low=100, high=200)
Blend an overlay image onto this image using the specified blend mode.
Modifies this image in-place. Both images must have the same dimensions. The overlay image must have an alpha channel for proper blending.
Parameters
overlay(Image): Image to blend onto this imagemode(Blending, optional): Blending mode (default: NORMAL)
Raises
ValueError: If images have different dimensionsTypeError: If overlay is not an Image object
Examples
# Basic alpha blending
base = Image(100, 100, (255, 0, 0))
overlay = Image(100, 100, (0, 0, 255, 128)) # Semi-transparent blue
base.blend(overlay) # Default NORMAL mode
# Using different blend modes
base.blend(overlay, zignal.Blending.MULTIPLY)
base.blend(overlay, zignal.Blending.SCREEN)
base.blend(overlay, zignal.Blending.OVERLAY)
Dilate a binary image using a square structuring element.
Parameters
kernel_size(int, optional): Side length of the square element (odd, >= 1). Default: 3.iterations(int, optional): Number of passes. Default: 1.
Returns
Image: Dilated binary image.
Erode a binary image using a square structuring element.
Same parameters as dilate_binary.
Perform binary opening (erosion followed by dilation).
Useful for removing isolated noise while preserving overall shapes.
Matrix for numerical computations with f64 (float64) values.
This class provides a bridge between zignal's Matrix type and NumPy arrays, with zero-copy operations when possible.
Examples
import zignal
import numpy as np
# Create from list of lists
m = zignal.Matrix([[1, 2, 3], [4, 5, 6]])
# Create with dimensions using full()
m = zignal.Matrix.full(3, 4) # 3x4 matrix of zeros
m = zignal.Matrix.full(3, 4, fill_value=1.0) # filled with 1.0
# From numpy (zero-copy for float64 contiguous arrays)
arr = np.random.randn(10, 5)
m = zignal.Matrix.from_numpy(arr)
# To numpy (zero-copy)
arr = m.to_numpy()
Create a new Matrix from a list of lists.
Parameters
data(List[List[float]]): List of lists containing matrix data
Examples
# Create from list of lists
m = Matrix([[1, 2, 3], [4, 5, 6]]) # 2x3 matrix
m = Matrix([[1.0, 2.5], [3.7, 4.2]]) # 2x2 matrix
Create a Matrix filled with a specified value.
Parameters
rows(int): Number of rowscols(int): Number of columnsfill_value(float, optional): Value to fill the matrix with (default: 0.0)
Returns
Matrix: A new Matrix of the specified dimensions filled with fill_value
Examples
# Create 3x4 matrix of zeros
m = Matrix.full(3, 4)
# Create 3x4 matrix of ones
m = Matrix.full(3, 4, 1.0)
# Create 5x5 matrix filled with 3.14
m = Matrix.full(5, 5, 3.14)
Create a Matrix from a NumPy array (zero-copy when possible).
The array must be 2D with dtype float64 and be C-contiguous. If the array is not contiguous or not float64, an error is raised.
Parameters
array(NDArray[np.float64]): A 2D NumPy array with dtype float64
Returns
Matrix: A new Matrix that shares memory with the NumPy array
Examples
import numpy as np
arr = np.random.randn(10, 5) # float64 by default
m = Matrix.from_numpy(arr)
# Modifying arr will modify m and vice versa
Convert the matrix to a NumPy array (zero-copy).
Returns a float64 NumPy array that shares memory with the Matrix. Modifying the array will modify the Matrix.
Returns
NDArray[np.float64]: A 2D NumPy array with shape (rows, cols)
Examples
m = Matrix(3, 4, fill_value=1.0)
arr = m.to_numpy() # shape (3, 4), dtype float64
Transpose the matrix.
Returns
Matrix: A new transposed matrix where rows and columns are swapped
Examples
m = Matrix([[1, 2, 3], [4, 5, 6]])
t = m.transpose() # shape (3, 2)
Compute the matrix inverse.
Returns
Matrix: The inverse matrix such that A @ A.inverse() ≈ I
Raises
ValueError: If matrix is not square or is singular
Examples
m = Matrix([[2, 0], [0, 2]])
inv = m.inverse() # [[0.5, 0], [0, 0.5]]
Matrix multiplication (dot product).
Parameters
other(Matrix): Matrix to multiply with
Returns
Matrix: Result of matrix multiplication
Examples
a = Matrix([[1, 2], [3, 4]])
b = Matrix([[5, 6], [7, 8]])
c = a.dot(b) # or a @ b
Sum of all matrix elements.
Returns
float: The sum of all elements
Examples
m = Matrix([[1, 2], [3, 4]])
s = m.sum() # 10.0
Mean (average) of all matrix elements.
Returns
float: The mean of all elements
Sum of diagonal elements (trace).
Returns
float: The trace of the matrix
Raises
ValueError: If matrix is not square
Compute the determinant of the matrix.
Returns
float: The determinant value
Raises
ValueError: If matrix is not square
Frobenius norm (entrywise â„“2).
Returns
float: Frobenius norm value
Entrywise L1 norm (sum of absolute values).
Returns
float: L1 norm value
Entrywise infinity norm (maximum absolute value).
Returns
float: Infinity norm value
Entrywise â„“áµ– norm with runtime exponent.
Parameters
p(float, optional): Exponent (default 2).
Returns
float: Element norm value
Schatten â„“áµ– norm based on singular values.
Parameters
p(float, optional): Exponent (default 2, must be ≥ 1 when finite).
Returns
float: Schatten norm value
Induced operator norm with p ∈ {1, 2, ∞}.
Parameters
p(float, optional): Exponent (allowed values: 1, 2, +inf; default 2).
Returns
float: Induced norm value
Nuclear norm (sum of singular values).
Returns
float: Nuclear norm value
Spectral norm (largest singular value).
Returns
float: Spectral norm value
Compute standard deviation of all matrix elements.
Returns
float: The standard deviation
Raise all elements to power n (element-wise).
Parameters
n(float): The exponent
Returns
Matrix: Matrix with elements raised to power n
Extract a row as a column vector.
Parameters
idx(int): Row index
Returns
Matrix: Column vector containing the row
Extract a column as a column vector.
Parameters
idx(int): Column index
Returns
Matrix: Column vector
Extract a submatrix.
Parameters
row_start(int): Starting row indexcol_start(int): Starting column indexrow_count(int): Number of rowscol_count(int): Number of columns
Returns
Matrix: Submatrix
Create a matrix filled with random values in [0, 1).
Parameters
rows(int): Number of rowscols(int): Number of columnsseed(int, optional): Random seed for reproducibility
Returns
Matrix: Matrix filled with random float64 values
Examples
m = Matrix.random(10, 5) # Random 10x5 matrix
m = Matrix.random(10, 5, seed=42) # Reproducible random matrix
Compute the numerical rank of the matrix.
Uses QR decomposition with column pivoting to determine the rank.
Returns
int: The numerical rank
Compute the Moore-Penrose pseudoinverse.
Works for rectangular matrices and gracefully handles rank deficiency. Uses SVD-based algorithm.
Parameters
tolerance(float, optional): Threshold for small singular values
Returns
Matrix: The pseudoinverse matrix
Examples
# For rectangular matrix
m = Matrix([[1, 2], [3, 4], [5, 6]])
pinv = m.pinv()
# With custom tolerance
pinv = m.pinv(tolerance=1e-10)
Compute LU decomposition with partial pivoting.
Returns L, U matrices and permutation vector such that PA = LU.
Returns
dict: Dictionary with keys:
- 'l': Lower triangular matrix
- 'u': Upper triangular matrix
- 'p': Permutation vector (as Matrix)
- 'sign': Determinant sign (+1.0 or -1.0)
Raises
ValueError: If matrix is not square
Compute QR decomposition with column pivoting.
Returns Q, R matrices and additional information about the decomposition.
Returns
dict: Dictionary with keys:
- 'q': Orthogonal matrix (m×n)
- 'r': Upper triangular matrix (n×n)
- 'rank': Numerical rank (int)
- 'perm': Column permutation indices (list of int)
- 'col_norms': Final column norms (list of float)
Compute Singular Value Decomposition (SVD).
Computes A = U × Σ × V^T where U and V are orthogonal matrices and Σ is a diagonal matrix of singular values.
Parameters
full_matrices(bool, optional): If True, U is m×m; if False, U is m×n (default: True)compute_uv(bool, optional): If True, compute U and V; if False, only compute singular values (default: True)
Returns
dict: Dictionary with keys:
- 'u': Left singular vectors (Matrix or None)
- 's': Singular values as column vector (Matrix)
- 'v': Right singular vectors (Matrix or None)
- 'converged': Convergence status (0 = success, k = failed at k-th value)
Raises
ValueError: If rows < cols (matrix must be tall or square)
A rectangle defined by its left, top, right, and bottom coordinates.
Initialize a Rectangle with specified coordinates.
Creates a rectangle from its bounding coordinates. The rectangle is defined by four values: left (x-min), top (y-min), right (x-max), and bottom (y-max). The right and bottom bounds are exclusive.
Parameters
left(float): Left edge x-coordinate (inclusive)top(float): Top edge y-coordinate (inclusive)right(float): Right edge x-coordinate (exclusive)bottom(float): Bottom edge y-coordinate (exclusive)
Examples
# Create a rectangle from (10, 20) to (110, 70)
rect = Rectangle(10, 20, 110, 70)
print(rect.width) # 100.0 (110 - 10)
print(rect.height) # 50.0 (70 - 20)
print(rect.contains(109.9, 69.9)) # True
print(rect.contains(110, 70)) # False
# Create a square
square = Rectangle(0, 0, 50, 50)
print(square.width) # 50.0
Notes
- The constructor validates that right >= left and bottom >= top
- Use Rectangle.init_center() for center-based construction
- Coordinates follow image convention: origin at top-left, y increases downward
- Right and bottom bounds are exclusive
Create a Rectangle from center coordinates.
Parameters
x(float): Center x coordinatey(float): Center y coordinatewidth(float): Rectangle widthheight(float): Rectangle height
Examples
# Create a 100x50 rectangle centered at (50, 50)
rect = Rectangle.init_center(50, 50, 100, 50)
# This creates Rectangle(0, 25, 100, 75)
Check if the rectangle is ill-formed (empty).
A rectangle is considered empty if its left >= right or top >= bottom.
Examples
rect1 = Rectangle(0, 0, 100, 100)
print(rect1.is_empty()) # False
rect2 = Rectangle(100, 100, 100, 100)
print(rect2.is_empty()) # True
Calculate the area of the rectangle.
Examples
rect = Rectangle(0, 0, 100, 50)
print(rect.area()) # 5000.0
Check if a point is inside the rectangle.
Uses exclusive bounds for right and bottom edges.
Parameters
x(float): X coordinate to checky(float): Y coordinate to check
Examples
rect = Rectangle(0, 0, 100, 100)
print(rect.contains(50, 50)) # True - inside
print(rect.contains(100, 50)) # False - on right edge (exclusive)
print(rect.contains(99.9, 99.9)) # True - just inside
print(rect.contains(150, 50)) # False - outside
Get the center of the rectangle as (x, y).
Returns
tuple[float, float]: Center coordinates(x, y)
Create a new rectangle expanded by the given amount.
Parameters
amount(float): Amount to expand each border by
Examples
rect = Rectangle(50, 50, 100, 100)
grown = rect.grow(10)
# Creates Rectangle(40, 40, 110, 110)
Create a new rectangle shrunk by the given amount.
Parameters
amount(float): Amount to shrink each border by
Examples
rect = Rectangle(40, 40, 110, 110)
shrunk = rect.shrink(10)
# Creates Rectangle(50, 50, 100, 100)
Create a new rectangle translated by (dx, dy).
Parameters
dx(float): Horizontal translationdy(float): Vertical translation
Returns
Rectangle: A new rectangle shifted by the offsets
Return a new rectangle clipped to the given bounds.
Parameters
bounds(Rectangle | tuple[float, float, float, float]): Rectangle to clip against
Returns
Rectangle: The clipped rectangle (may be empty)
Calculate the intersection of this rectangle with another.
Parameters
other(Rectangle | tuple[float, float, float, float]): The other rectangle to intersect with
Examples
rect1 = Rectangle(0, 0, 100, 100)
rect2 = Rectangle(50, 50, 150, 150)
intersection = rect1.intersect(rect2)
# Returns Rectangle(50, 50, 100, 100)
# Can also use a tuple
intersection = rect1.intersect((50, 50, 150, 150))
# Returns Rectangle(50, 50, 100, 100)
rect3 = Rectangle(200, 200, 250, 250)
result = rect1.intersect(rect3) # Returns None (no overlap)
Calculate the Intersection over Union (IoU) with another rectangle.
Parameters
other(Rectangle | tuple[float, float, float, float]): The other rectangle to calculate IoU with
Returns
float: IoU value between 0.0 (no overlap) and 1.0 (identical rectangles)
Examples
rect1 = Rectangle(0, 0, 100, 100)
rect2 = Rectangle(50, 50, 150, 150)
iou = rect1.iou(rect2) # Returns ~0.143
# Can also use a tuple
iou = rect1.iou((0, 0, 100, 100)) # Returns 1.0 (identical)
# Non-overlapping rectangles
rect3 = Rectangle(200, 200, 250, 250)
iou = rect1.iou(rect3) # Returns 0.0
Check if this rectangle overlaps with another based on IoU and coverage thresholds.
Parameters
other(Rectangle | tuple[float, float, float, float]): The other rectangle to check overlap withiou_thresh(float, optional): IoU threshold for considering overlap. Default: 0.5coverage_thresh(float, optional): Coverage threshold for considering overlap. Default: 1.0
Returns
bool: True if rectangles overlap enough based on the thresholds
Description
Returns True if any of these conditions are met:
- IoU > iou_thresh
- intersection.area / self.area > coverage_thresh
- intersection.area / other.area > coverage_thresh
Examples
rect1 = Rectangle(0, 0, 100, 100)
rect2 = Rectangle(50, 50, 150, 150)
# Default thresholds
overlaps = rect1.overlaps(rect2) # Uses IoU > 0.5
# Custom IoU threshold
overlaps = rect1.overlaps(rect2, iou_thresh=0.1) # True
# Coverage threshold (useful for small rectangle inside large)
small = Rectangle(25, 25, 75, 75)
overlaps = rect1.overlaps(small, coverage_thresh=0.9) # True (small is 100% covered)
# Simple intersection (any positive overlap)
rect1.overlaps(rect2, iou_thresh=0.0, coverage_thresh=0.0)
# Full containment test
rect1.overlaps(small, iou_thresh=0.0, coverage_thresh=1.0)
# Can use tuple
overlaps = rect1.overlaps((50, 50, 150, 150), iou_thresh=0.1)
Check if this rectangle fully contains another rectangle.
Parameters
other(Rectangle | tuple[float, float, float, float]): Rectangle to test
Returns
bool: True ifotherlies completely inside this rectangle
Convex hull computation using Graham's scan algorithm.
Initialize a new ConvexHull instance.
Creates a new ConvexHull instance that can compute the convex hull of 2D point sets using Graham's scan algorithm. The algorithm has O(n log n) time complexity where n is the number of input points.
Examples
# Create a ConvexHull instance
hull = ConvexHull()
# Find convex hull of points
points = [(0, 0), (1, 1), (2, 2), (3, 1), (4, 0), (2, 4), (1, 3)]
result = hull.find(points)
# Returns: [(0.0, 0.0), (1.0, 3.0), (2.0, 4.0), (4.0, 0.0)]
Notes
- Returns vertices in clockwise order
- Returns None for degenerate cases (e.g., all points collinear)
- Requires at least 3 points for a valid hull
Find the convex hull of a set of 2D points.
Returns the vertices of the convex hull in clockwise order as a list of (x, y) tuples, or None if the hull is degenerate (e.g., all points are collinear).
Parameters
points(list[tuple[float, float]]): List of (x, y) coordinate pairs. At least 3 points are required.
Examples
hull = ConvexHull()
points = [(0, 0), (1, 1), (2, 2), (3, 1), (4, 0), (2, 4), (1, 3)]
result = hull.find(points)
# Returns: [(0.0, 0.0), (1.0, 3.0), (2.0, 4.0), (4.0, 0.0)]
Return the tightest axis-aligned rectangle enclosing the last hull.
The rectangle is expressed in image-style coordinates (left, top, right, bottom)
and matches the bounds of the currently cached convex hull. If no hull has
been computed yet or the last call was degenerate (e.g., all points were
collinear), this method returns None.
Returns
Rectangle | None: Bounding rectangle instance orNonewhen unavailable.
Similarity transform (rotation + uniform scale + translation). Raises ValueError when the point correspondences are rank deficient or the fit fails to converge.
Create similarity transform from point correspondences.
Affine transform (general 2D linear transform). Raises ValueError when correspondences are rank deficient or the fit fails to converge.
Create affine transform from point correspondences.
Projective transform (homography/perspective transform). Raises ValueError when correspondences are rank deficient or the fit fails to converge.
Create projective transform from point correspondences.
Canvas for drawing operations on images.
Create a Canvas for drawing operations on an Image.
A Canvas provides drawing methods to modify the pixels of an Image. The Canvas maintains a reference to the parent Image to prevent it from being garbage collected while drawing operations are in progress.
Parameters
image(Image): The Image object to draw on. Must be initialized with dimensions.
Examples
# Create an image and get its canvas
img = Image(100, 100, Rgb(255, 255, 255))
canvas = Canvas(img)
# Draw on the canvas
canvas.fill(Rgb(0, 0, 0))
canvas.draw_circle((50, 50), 20, Rgb(255, 0, 0))
Notes
- The Canvas holds a reference to the parent Image
- All drawing operations modify the original Image pixels
- Use Image.canvas() method as a convenient way to create a Canvas
Fill the entire canvas with a color.
Parameters
color(int, tuple or color object): Color to fill the canvas with. Can be:
Examples
img = Image.load("photo.png")
canvas = img.canvas()
canvas.fill(128) # Fill with gray
canvas.fill((255, 0, 0)) # Fill with red
canvas.fill(Rgb(0, 255, 0)) # Fill with green using Rgb object
Draw a line between two points.
Parameters
p1(tuple[float, float]): Starting point coordinates (x, y)p2(tuple[float, float]): Ending point coordinates (x, y)color(int, tuple or color object): Color of the line.width(int, optional): Line width in pixels (default: 1)mode(DrawMode, optional): Drawing mode (default:DrawMode.FAST)
Draw a rectangle outline.
Parameters
rect(Rectangle | tuple[float, float, float, float]): Rectangle object defining the boundscolor(int, tuple or color object): Color of the rectangle.width(int, optional): Line width in pixels (default: 1)mode(DrawMode, optional): Drawing mode (default:DrawMode.FAST)
Fill a rectangle area.
Parameters
rect(Rectangle | tuple[float, float, float, float]): Rectangle object defining the boundscolor(int, tuple or color object): Fill color.mode(DrawMode, optional): Drawing mode (default:DrawMode.FAST)
Draw a polygon outline.
Parameters
points(list[tuple[float, float]]): List of (x, y) coordinates forming the polygoncolor(int, tuple or color object): Color of the polygon.width(int, optional): Line width in pixels (default: 1)mode(DrawMode, optional): Drawing mode (default:DrawMode.FAST)
Fill a polygon area.
Parameters
points(list[tuple[float, float]]): List of (x, y) coordinates forming the polygoncolor(int, tuple or color object): Fill color.mode(DrawMode, optional): Drawing mode (default:DrawMode.FAST)
Composite another image onto this canvas.
Parameters
image(Image): Source image to draw.position(tuple[float, float]): Top-left destination position(x, y).source_rect(Rectangle | tuple[float, float, float, float] | None, optional): Optional source rectangle in source image coordinates. When omitted orNone, the entire image is used.blend_mode(Blending | None, optional): Blending mode applied when drawing RGBA sources. Defaults toBlending.NORMAL; useNoneorBlending.NONEfor direct copy.
Notes
- Alpha blending is handled automatically based on the source image dtype; non-RGBA images always copy pixels directly.
- Drawing is clipped to the canvas bounds.
Draw a circle outline.
Parameters
center(tuple[float, float]): Center coordinates (x, y)radius(float): Circle radiuscolor(int, tuple or color object): Color of the circle.width(int, optional): Line width in pixels (default: 1)mode(DrawMode, optional): Drawing mode (default:DrawMode.FAST)
Fill a circle area.
Parameters
center(tuple[float, float]): Center coordinates (x, y)radius(float): Circle radiuscolor(int, tuple or color object): Fill color.mode(DrawMode, optional): Drawing mode (default:DrawMode.FAST)
Draw an arc outline.
Parameters
center(tuple[float, float]): Center coordinates (x, y)radius(float): Arc radius in pixelsstart_angle(float): Starting angle in radians (0 = right, π/2 = down, π = left, 3π/2 = up)end_angle(float): Ending angle in radianscolor(int, tuple or color object): Color of the arc.width(int, optional): Line width in pixels (default: 1)mode(DrawMode, optional): Drawing mode (default:DrawMode.FAST)
Notes
- Angles are measured in radians, with 0 pointing right and increasing clockwise
- For a full circle, use start_angle=0 and end_angle=2Ï€
- The arc is drawn from start_angle to end_angle in the positive angular direction
Fill an arc (pie slice) area.
Parameters
center(tuple[float, float]): Center coordinates (x, y)radius(float): Arc radius in pixelsstart_angle(float): Starting angle in radians (0 = right, π/2 = down, π = left, 3π/2 = up)end_angle(float): Ending angle in radianscolor(int, tuple or color object): Fill color.mode(DrawMode, optional): Drawing mode (default:DrawMode.FAST)
Notes
- Creates a filled pie slice from the center to the arc edge
- Angles are measured in radians, with 0 pointing right and increasing clockwise
- For a full circle, use start_angle=0 and end_angle=2Ï€
Draw a quadratic Bézier curve.
Parameters
p0(tuple[float, float]): Start point (x, y)p1(tuple[float, float]): Control point (x, y)p2(tuple[float, float]): End point (x, y)color(int, tuple or color object): Color of the curve.width(int, optional): Line width in pixels (default: 1)mode(DrawMode, optional): Drawing mode (default:DrawMode.FAST)
Draw a cubic Bézier curve.
Parameters
p0(tuple[float, float]): Start point (x, y)p1(tuple[float, float]): First control point (x, y)p2(tuple[float, float]): Second control point (x, y)p3(tuple[float, float]): End point (x, y)color(int, tuple or color object): Color of the curve.width(int, optional): Line width in pixels (default: 1)mode(DrawMode, optional): Drawing mode (default:DrawMode.FAST)
Draw a smooth spline through polygon points.
Parameters
points(list[tuple[float, float]]): List of (x, y) coordinates to interpolate throughcolor(int, tuple or color object): Color of the spline.width(int, optional): Line width in pixels (default: 1)tension(float, optional): Spline tension (0.0 = angular, 0.5 = smooth, default: 0.5)mode(DrawMode, optional): Drawing mode (default:DrawMode.FAST)
Fill a smooth spline area through polygon points.
Parameters
points(list[tuple[float, float]]): List of (x, y) coordinates to interpolate throughcolor(int, tuple or color object): Fill color.tension(float, optional): Spline tension (0.0 = angular, 0.5 = smooth, default: 0.5)mode(DrawMode, optional): Drawing mode (default:DrawMode.FAST)
Draw text on the canvas.
Parameters
text(str): Text to drawposition(tuple[float, float]): Position coordinates (x, y)color(int, tuple or color object): Text color.font(BitmapFont, optional): Font object to use for rendering. IfNone, uses BitmapFont.font8x8()scale(float, optional): Text scale factor (default: 1.0)mode(DrawMode, optional): Drawing mode (default:DrawMode.FAST)
Bitmap font for text rendering. Supports BDF/PCF formats, including optional gzip-compressed files (.bdf.gz, .pcf.gz).
Load a bitmap font from file.
Supports BDF (Bitmap Distribution Format) and PCF (Portable Compiled Format) files, including
optionally gzip-compressed variants (e.g., .bdf.gz, .pcf.gz).
Parameters
path(str): Path to the font file
Examples
font = BitmapFont.load("unifont.bdf")
canvas.draw_text("Hello", (10, 10), font, (255, 255, 255))
Get the built-in default 8x8 bitmap font with all available characters.
This font includes ASCII, extended ASCII, Greek, and box drawing characters.
Examples
font = BitmapFont.font8x8()
canvas.draw_text("Hello World!", (10, 10), font, (255, 255, 255))
Grayscale image format (single channel, u8)
Principal Component Analysis (PCA) for dimensionality reduction.
PCA is a statistical technique that transforms data to a new coordinate system where the greatest variance lies on the first coordinate (first principal component), the second greatest variance on the second coordinate, and so on.
Examples
import zignal
import numpy as np
# Create PCA instance
pca = zignal.PCA()
# Prepare data using Matrix
data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
matrix = zignal.Matrix.from_numpy(data)
# Fit PCA, keeping 2 components
pca.fit(matrix, num_components=2)
# Project a single vector
coeffs = pca.project([2, 3, 4])
# Transform batch of data
transformed = pca.transform(matrix)
# Reconstruct from coefficients
reconstructed = pca.reconstruct(coeffs)
Fit the PCA model on training data.
Parameters
data(Matrix): Training samples matrix (n_samples × n_features)num_components(int, optional): Number of components to keep. If None, keeps min(n_samples-1, n_features)
Raises
- ValueError: If data has insufficient samples (< 2)
- ValueError: If num_components is 0
Examples
matrix = zignal.Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
pca.fit(matrix) # Keep all possible components
pca.fit(matrix, num_components=2) # Keep only 2 components
Project a single vector onto the PCA space.
Parameters
vector(list[float]): Input vector to project
Returns
list[float]: Coefficients in PCA space
Raises
- RuntimeError: If PCA has not been fitted
- ValueError: If vector dimension doesn't match fitted data
Examples
coeffs = pca.project([1.0, 2.0, 3.0])
Transform data matrix to PCA space.
Parameters
data(Matrix): Data matrix (n_samples × n_features)
Returns
Matrix: Transformed data (n_samples × n_components)
Raises
- RuntimeError: If PCA has not been fitted
- ValueError: If data dimensions don't match fitted data
Examples
transformed = pca.transform(matrix)
Reconstruct a vector from PCA coefficients.
Parameters
coefficients(List[float]): Coefficients in PCA space
Returns
List[float]: Reconstructed vector in original space
Raises
- RuntimeError: If PCA has not been fitted
- ValueError: If number of coefficients doesn't match number of components
Examples
reconstructed = pca.reconstruct([1.0, 2.0])
Feature Distribution Matching for image style transfer.
Initialize a new FeatureDistributionMatching instance.
Creates a new FDM instance that can be used to transfer color distributions between images. The instance maintains internal state for efficient batch processing of multiple images with the same target distribution.
Examples
# Create an FDM instance
fdm = FeatureDistributionMatching()
# Single image transformation
source = Image.load("portrait.png")
target = Image.load("sunset.png")
fdm.match(source, target) # source is modified in-place
source.save("portrait_sunset.png")
# Batch processing with same style
style = Image.load("style_reference.png")
fdm.set_target(style)
for filename in image_files:
img = Image.load(filename)
fdm.set_source(img)
fdm.update()
img.save(f"styled_{filename}")
Notes
- The algorithm matches mean and covariance of pixel distributions
- Target statistics are computed once and can be reused for multiple sources
- See: https://facebookresearch.github.io/dino/blog/
Set the target image whose distribution will be matched.
This method computes and stores the target distribution statistics (mean and covariance) for reuse across multiple source images. This is more efficient than recomputing the statistics for each image when applying the same style to multiple images.
Parameters
image(Image): Target image providing the color distribution to match. Must be RGB.
Examples
fdm = FeatureDistributionMatching()
target = Image.load("sunset.png")
fdm.set_target(target)
Set the source image to be transformed.
The source image will be modified in-place when update() is called.
Parameters
image(Image): Source image to be modified. Must be RGB.
Examples
fdm = FeatureDistributionMatching()
source = Image.load("portrait.png")
fdm.set_source(source)
Set both source and target images and apply the transformation.
This is a convenience method that combines set_source(), set_target(), and update() into a single call. The source image is modified in-place.
Parameters
source(Image): Source image to be modified (RGB)target(Image): Target image providing the color distribution to match (RGB)
Examples
fdm = FeatureDistributionMatching()
source = Image.load("portrait.png")
target = Image.load("sunset.png")
fdm.match(source, target) # source is now modified
source.save("portrait_sunset.png")
Apply the feature distribution matching transformation.
This method modifies the source image in-place to match the target distribution. Both source and target must be set before calling this method.
Raises
RuntimeError: If source or target has not been set
Examples
fdm = FeatureDistributionMatching()
fdm.set_target(target)
fdm.set_source(source)
fdm.update() # source is now modified
Batch processing
fdm.set_target(style_image)
for img in images:
fdm.set_source(img)
fdm.update() # Each img is modified in-place
Result of solving an assignment problem.
Contains the optimal assignments and total cost.
Attributes
assignments: List of column indices for each row (None if unassigned)total_cost: Total cost of the assignment
Iterator over image pixels yielding (row, col, pixel) in native format.
This iterator walks the image in row-major order (top-left to bottom-right). For views, iteration respects the view bounds and the underlying stride, so you only traverse the visible sub-rectangle without copying.
Examples
image = Image(2, 3, Rgb(255, 0, 0), format=zignal.Rgb)
for r, c, pixel in image:
print(f"image[{r}, {c}] = {pixel}")
Notes
Returned by
iter(Image)/Image.__iter__()Use
Image.to_numpy()when you need bulk numeric processing for best performance.
Online statistics accumulator using Welford's algorithm.
Maintains numerically stable estimates of mean, variance, skewness, and excess kurtosis in a single pass.
Online statistics accumulator using Welford's algorithm.
Maintains numerically stable estimates of mean, variance, skewness, and excess kurtosis in a single pass.
Add a single sample to the running statistics.
Parameters
value(float): Sample value to add.
Examples
stats = RunningStats()
stats.add(1.5)
Add multiple samples to the running statistics.
Parameters
values(Iterable[float]): Iterable of numeric samples.
Examples
stats = RunningStats()
stats.extend([1.0, 2.5, 3.7])
Reset all accumulated statistics.
Examples
stats = RunningStats()
stats.add(5.0)
stats.clear()
print(stats.count) # 0
Standardize a value using the accumulated statistics.
Returns (value - mean) / std_dev. If the standard deviation is zero
(e.g., fewer than two samples or zero variance), this returns 0.0.
Parameters
value(float): Value to scale.
Returns
float: Scaled value.
Rendering quality mode for drawing operations.
Attributes
FAST(int): Fast rendering without antialiasing (value: 0)SOFT(int): High-quality rendering with antialiasing (value: 1)
Notes
- FAST mode provides pixel-perfect rendering with sharp edges
- SOFT mode provides smooth, antialiased edges for better visual quality
- Default mode is FAST for performance
Blending modes for color composition.
Overview
These modes determine how colors are combined when blending. Each mode produces different visual effects useful for various image compositing operations.
Blend Modes
| Mode | Description | Best Use Case |
|---|---|---|
| NONE | No blending; overlay replaces base pixel | Direct copy |
| NORMAL | Standard alpha blending with transparency | Layering images |
| MULTIPLY | Darkens by multiplying colors (white has no effect) | Shadows, darkening |
| SCREEN | Lightens by inverting, multiplying, then inverting | Highlights, glow |
| OVERLAY | Combines multiply and screen based on base color | Contrast enhance |
| SOFT_LIGHT | Gentle contrast adjustment | Subtle lighting |
| HARD_LIGHT | Like overlay but uses overlay color to determine blend | Strong contrast |
| COLOR_DODGE | Brightens base color based on overlay | Bright highlights |
| COLOR_BURN | Darkens base color based on overlay | Deep shadows |
| DARKEN | Selects darker color per channel | Remove white |
| LIGHTEN | Selects lighter color per channel | Remove black |
| DIFFERENCE | Subtracts darker from lighter color | Invert/compare |
| EXCLUSION | Similar to difference but with lower contrast | Soft inversion |
Examples
base = zignal.Rgb(100, 100, 100)
overlay = zignal.Rgba(200, 50, 150, 128)
# Apply different blend modes
normal = base.blend(overlay, zignal.Blending.NORMAL)
multiply = base.blend(overlay, zignal.Blending.MULTIPLY)
screen = base.blend(overlay, zignal.Blending.SCREEN)
Notes
NONEperforms a direct copy and is the default for APIs that accept blending- All other blend modes respect alpha channel for proper compositing
- Result color type matches the base color type
- Overlay must be RGBA or convertible to RGBA
Interpolation methods for image resizing.
Performance and quality comparison:
| Method | Quality | Speed | Best Use Case | Overshoot |
|---|---|---|---|---|
| NEAREST_NEIGHBOR | ★☆☆☆☆ | ★★★★★ | Pixel art, masks | No |
| BILINEAR | ★★☆☆☆ | ★★★★☆ | Real-time, preview | No |
| BICUBIC | ★★★☆☆ | ★★★☆☆ | General purpose | Yes |
| CATMULL_ROM | ★★★★☆ | ★★★☆☆ | Natural images | No |
| MITCHELL | ★★★★☆ | ★★☆☆☆ | Balanced quality | Yes |
| LANCZOS | ★★★★★ | ★☆☆☆☆ | High-quality resize | Yes |
Note: "Overshoot" means the filter can create values outside the input range, which can cause ringing artifacts but may also enhance sharpness.
Border handling strategies used by convolution and order-statistic filters.
Optimization policy for assignment problems.
Determines whether to minimize or maximize the total cost.
RGB color in sRGB colorspace with components in range 0-255
Blend with overlay (tuple interpreted as RGBA) using the specified mode.
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
RGBA color with alpha channel, components in range 0-255
Blend with overlay (tuple interpreted as RGBA) using the specified mode.
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
HSL (Hue-Saturation-Lightness) color representation
Blend with overlay (tuple interpreted as RGBA) using the specified mode.
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
HSV (Hue-Saturation-Value) color representation
Blend with overlay (tuple interpreted as RGBA) using the specified mode.
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
CIELAB color space representation
Blend with overlay (tuple interpreted as RGBA) using the specified mode.
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
CIE LCH color space representation (cylindrical Lab)
Blend with overlay (tuple interpreted as RGBA) using the specified mode.
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
LMS color space representing Long, Medium, Short wavelength cone responses
Blend with overlay (tuple interpreted as RGBA) using the specified mode.
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
Oklab perceptual color space representation
Blend with overlay (tuple interpreted as RGBA) using the specified mode.
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
Oklch perceptual color space in cylindrical coordinates
Blend with overlay (tuple interpreted as RGBA) using the specified mode.
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
XYB color space used in JPEG XL image compression
Blend with overlay (tuple interpreted as RGBA) using the specified mode.
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
CIE 1931 XYZ color space representation
Blend with overlay (tuple interpreted as RGBA) using the specified mode.
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
YCbCr color space used in JPEG and video encoding
Blend with overlay (tuple interpreted as RGBA) using the specified mode.
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
Motion blur effect configuration.
Use the static factory methods to create motion blur configurations:
MotionBlur.linear(angle, distance)- Linear motion blurMotionBlur.radial_zoom(center, strength)- Radial zoom blurMotionBlur.radial_spin(center, strength)- Radial spin blur
Examples
import math
from zignal import Image, MotionBlur
img = Image.load("photo.jpg")
# Linear motion blur
horizontal = img.motion_blur(MotionBlur.linear(angle=0, distance=30))
vertical = img.motion_blur(MotionBlur.linear(angle=math.pi/2, distance=20))
# Radial zoom blur
zoom = img.motion_blur(MotionBlur.radial_zoom(center=(0.5, 0.5), strength=0.7))
zoom_default = img.motion_blur(MotionBlur.radial_zoom()) # Uses defaults
# Radial spin blur
spin = img.motion_blur(MotionBlur.radial_spin(center=(0.3, 0.7), strength=0.5))