Pipeline¶
The Pipeline class is the core builder for image and array processing pipelines.
Overview¶
from polars_cv import Pipeline
pipe = (
Pipeline()
.source("image_bytes")
.resize(height=224, width=224)
.grayscale()
)
API Reference¶
Pipeline ¶
Bases: _OpsMixin
Modular pipeline builder for image and array operations.
A pipeline defines a sequence of operations that can be applied to a Polars
expression using the .cv.pipe() accessor. The pipeline is executed when
.sink() is called on the resulting expression.
Parameters that carry a value — sizes, offsets, factors, thresholds, fill
values, kernel coefficients, and non-structural enums such as filter or
interpolation — accept either a literal or a Polars expression, resolved
per row at execution time.
Parameters that fix the output shape, rank, or dtype at planning time
are literal-only, so the lazy schema cannot desync from the produced data:
reduction axis, perceptual_hash(hash_size), reshape/transpose
/flip axis lists, rotate(expand), cast(dtype),
normalize(method/out_dtype) and histogram(closed/output).
Passing an expression to one of those raises TypeError at build time.
Example
>>> from polars_cv import Pipeline
>>> import polars as pl
>>>
>>> # Define a reusable pipeline (without a sink)
>>> preprocess = (
... Pipeline()
... .source("image_bytes")
... .resize(height=224, width=224)
... .grayscale()
... )
>>>
>>> # Apply to a DataFrame and choose the output format at the sink
>>> df = pl.DataFrame({"image": [img_bytes]})
>>> result = df.with_columns(
... processed=pl.col("image").cv.pipe(preprocess).sink("numpy")
... )
Pipelines support typed domain tracking for transitions between images, geometry, and numeric results: - buffer: Image/array data (default) - contour: Polygon geometry - scalar: Single numeric values - vector: Multiple numeric values (e.g., bounding boxes)
source ¶
source(
format: str = "auto",
*,
allowed_roots: Sequence[str] | None = None,
cloud_options: CloudOptions
| dict[str, Any]
| None = None,
decode_max_size: int | None = None,
dtype: str | None = None,
on_error: str | None = None,
require_contiguous: bool | None = None,
) -> Pipeline
Define the input source format.
The default "auto" infers the decode path from the column's Polars
dtype at runtime. Pass an explicit format to override the inference (or
when the column dtype cannot be routed, such as a plain numeric column).
Image sources ("image_bytes" and "file_path") auto-detect the
encoding and preserve its dtype: PNG/JPEG decode to u8, 16-bit PNG to u16,
and TIFF may produce u8, u16, f32 or f64. Decoded images are always 3D
[H, W, C]. Until then the dtype is "auto": a list/array
sink needs it known at planning time, from dtype= here, a cast(),
or an operation that fixes it.
A "contour" source decodes to the contour domain; rasterize it with
:meth:rasterize.
Each keyword applies to some formats and not others, and defaults to
None (the format's own default). One that does not apply to the chosen
format is rejected, naming the formats it applies to.
decode_max_size asserts the pipeline needs at most this many pixels on
the decoded long side, so JPEG decoding uses IDCT scaling (1/8, 1/4 or
1/2) to skip work. The long side never drops below
min(decode_max_size, original), so a downstream resize to that size
never upscales; other encodings decode at full size. A scaled decode
followed by a resize is not bit-identical to a full decode and the same
resize, hence the explicit opt-in.
allowed_roots restricts which locations a path column may read from.
An entry that parses as a remote URI ("s3://bucket/public/") is
matched as a URI prefix, anything else ("/srv/images") as a local
directory. Local paths are canonicalized first, so .. and symlinks
cannot escape, and matching is component-wise ("/srv/images" does not
admit "/srv/images-private"). A path matching no entry is refused, and
the refusal is subject to on_error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
format
|
str
|
How to decode the input column. |
'auto'
|
allowed_roots
|
Sequence[str] | None
|
|
None
|
cloud_options
|
CloudOptions | dict[str, Any] | None
|
|
None
|
decode_max_size
|
int | None
|
|
None
|
dtype
|
str | None
|
|
None
|
on_error
|
str | None
|
|
None
|
require_contiguous
|
bool | None
|
|
None
|
Example
>>> # Decode PNG/JPEG bytes from a column
>>> pipe = Pipeline().source("image_bytes").resize(height=224, width=224)
>>>
>>> # Read from file paths or URLs, sandboxed
>>> pipe = Pipeline().source("file_path", allowed_roots=["/srv/images"])
>>>
>>> # Assert dtype for a list sink (cast if needed at runtime)
>>> pipe = Pipeline().source("image_bytes", dtype="f32")
>>>
>>> # Gracefully handle corrupt images as null
>>> pipe = Pipeline().source("image_bytes", on_error="null")
>>>
>>> # Rasterize a contour column to a mask
>>> pipe = Pipeline().source("contour").rasterize(width=64, height=64)
add_constant ¶
Add a constant to every value (x + value).
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
FloatOrExpr
|
Constant addend (literal or per-row expression). |
required |
adjust_contrast ¶
Adjust image contrast: (pixel - mean) * factor + mean.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
factor
|
FloatOrExpr
|
Contrast factor. 1.0 = no change, >1 = more contrast, <1 = less. |
required |
Example
pipe = Pipeline().source("image_bytes").adjust_contrast(factor=1.5)
adjust_gamma ¶
Apply gamma (power-law) correction: normalize to [0,1], raise to gamma,
denormalize.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gamma
|
FloatOrExpr
|
Gamma value. <1 = brighter, >1 = darker, 1.0 = no change. |
required |
Example
pipe = Pipeline().source("image_bytes").adjust_gamma(gamma=0.5)
apply_lut ¶
Map each value through a lookup table.
Each u8 or u16 value is a row index into table. Rows of one entry map
every element in place, on any channel count (as cv2.LUT with one
table); rows of K > 1 entries take a single-channel image to K
channels (a palette). The entries are stored as out_dtype. A value with no
row is an error for its row: clamp first, keeping the dtype
(clamp(0, len(table) - 1, out_dtype="u8")), to send the excess to the
last row.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
Sequence[Sequence[FloatOrExpr]]
|
The rows, indexed by value: 1 to 65536 of them, each of the same
length (1, or the palette's channel count). Each entry must fit
|
required |
out_dtype
|
str
|
The output element dtype the entries are stored as. |
'u8'
|
Example
heat = Pipeline().source("image_bytes").grayscale().apply_lut([[v, 0, 255 - v] for v in range(256)])
blur ¶
Apply Gaussian blur.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sigma
|
FloatOrExpr
|
Standard deviation for Gaussian kernel. |
required |
canny ¶
Canny edge detection, as cv2.Canny(image, low, high) computes it:
3x3 Sobel gradients (L1 magnitude), non-maximum suppression and
double-threshold hysteresis. There is no pre-blur — add .blur()
first for a smoothed edge map, as with OpenCV. A colour image uses, per
pixel, the channel with the strongest gradient; alpha is ignored. Output
is a U8 binary edge map (0 or 255).
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
low_threshold
|
FloatOrExpr
|
Lower hysteresis threshold. |
50.0
|
high_threshold
|
FloatOrExpr
|
Upper hysteresis threshold. |
150.0
|
Example
edges = Pipeline().source("image_bytes").blur(sigma=1.4).canny(low_threshold=50, high_threshold=150)
cast ¶
Cast to a different data type.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dtype
|
str
|
Target data type (e.g., "f32", "u8"). |
required |
channel_select ¶
Extract one channel of a multi-channel image: [H, W, 1] from a
[H, W, C] input (the shape grayscale and rasterize give),
or [H, W] with keepdims=False. A [H, W] input is returned
as it is.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
IntOrExpr
|
Channel index to extract (0-based). Accepts a Polars expression for per-row dynamic selection. |
required |
keepdims
|
bool
|
Keep the channel axis, as size 1 (default), or drop it. |
True
|
Example
pipe = Pipeline().source("image_bytes").channel_select(index=0) # Red channel
channel_swap ¶
Reorder channels in a multi-channel image.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
order
|
Sequence[IntOrExpr]
|
New channel ordering, e.g. [2, 1, 0] for RGB-to-BGR. Each index may be a literal or a Polars expression, so the permutation can vary per row. The list length is the channel count and must be literal. |
required |
Example
pipe = Pipeline().source("image_bytes").channel_swap(order=[2, 1, 0])
clamp_max ¶
Cap values at value (min(x, value)); one-sided clamp.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
FloatOrExpr
|
Upper bound (literal or per-row expression). |
required |
clamp_min ¶
Floor values at value (max(x, value)); one-sided clamp.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
FloatOrExpr
|
Lower bound (literal or per-row expression). |
required |
area ¶
Compute the area of the contour using the Shoelace formula.
The area of the region the contour describes: the exterior minus the union of its hole rings, in either winding direction. Overlapping or nested hole rings are not double-subtracted. One value per contour for a contour set.
Domain: contour → vector
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
signed
|
BoolOrExpr
|
If True, return signed area (negative for CW winding). |
False
|
boundary ¶
A region's rings as open lines: the exterior, then each hole, each returning to its first point. The result is a set (holes add rings); an open contour, already a line, is refused.
Domain: contour → contour
bounding_box ¶
Compute the axis-aligned bounding box of the contour.
Returns [x, y, width, height].
Domain: contour → vector
centroid ¶
Compute the centroid (center of mass) of the contour.
Measured on the same region as area() — the exterior minus the union
of the hole rings — so overlapping or nested holes are not subtracted
twice.
Returns [x, y].
Domain: contour → vector
close ¶
Join an open line's ends into the region it bounds. A line that
returns to its first point (as boundary() draws one) drops the
repeat, so close() undoes boundary() on a region without holes.
A closed contour is refused.
Domain: contour → contour
convex_hull ¶
Compute the convex hull of the contour.
Domain: contour → contour
densify ¶
Add points so no edge is longer than max_segment_length: each edge is
split into ceil(length / max_segment_length) equal parts, and every
existing point is kept. A region's closing edges are split too.
Domain: contour → contour
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_segment_length
|
FloatOrExpr
|
The longest edge allowed, a finite length > 0 (literal or expression). |
required |
largest ¶
Keep the k largest contours of the set, by area: largest first,
equal areas in their input order. A set of fewer keeps them all.
Domain: contour → contour
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
k
|
IntOrExpr
|
How many contours to keep, >= 1 (literal or expression). |
1
|
offset ¶
Grow the set's regions by distance (negative: shrink them): every
point within distance of their union, or inside it and at least
-distance from its edge.
The set is read as one union, so growing can merge regions and
shrinking can split them or leave none: the result is always a set.
Convex corners turn by join; concave ones stay sharp. Round arcs are
polygons within 1/64 pixel of the true arc, or about distance / 2000
past a distance of 32 pixels. An open contour is refused.
Domain: contour → contour
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
distance
|
FloatOrExpr
|
How far to move the edge, in pixels: positive grows, negative shrinks (literal or expression). |
required |
join
|
str | Expr
|
|
'round'
|
perimeter ¶
Compute the perimeter (arc length) of the contour.
Domain: contour → vector
scale_contour ¶
Scale the contour about origin.
Domain: contour → contour
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sx
|
FloatOrExpr
|
X scale factor. |
required |
sy
|
FloatOrExpr
|
Y scale factor. |
required |
origin
|
str | Expr
|
Point to scale about — |
'centroid'
|
simplify ¶
Simplify the contour using the Douglas-Peucker algorithm.
Domain: contour → contour
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tolerance
|
FloatOrExpr
|
Maximum distance from the original contour. |
required |
smooth ¶
Round off corners by Chaikin's corner cutting: each pass replaces every
edge by its points a quarter and three quarters along, doubling the
point count. A region's rings are cut all the way round; an open line
keeps its two ends, unless it returns to its first point (as
boundary() draws one), when it is cut through it as a loop.
Domain: contour → contour
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
iterations
|
IntOrExpr
|
Passes to make; 0 returns the contour as it is (literal or expression). |
1
|
stroke ¶
stroke(
*,
width: FloatOrExpr = 1.0,
cap: str | Expr = "round",
join: str | Expr = "round",
) -> Pipeline
The region the set's paths cover when stroked, as disjoint regions:
every point within width / 2 of a path, its ends shaped by cap
and its corners by join. Every member is read as its outline: an
open line, capped at its ends (one returning to its first point, as
boundary() draws one, is a closed path), and a region's rings,
each a closed path with no ends. A ring of fewer than two points has
no edge and covers nothing; a one-point line is a dot (its caps). The result is always a set, filled
by rasterize(): stroke(width=2).rasterize(...) draws lines and
outlines. Round arcs are polygons within 1/64 pixel of the true arc,
or about width / 4000 past a width of 64 pixels.
Domain: contour → contour
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width
|
FloatOrExpr
|
The stroke's full width in pixels, a finite width > 0 (literal or expression). |
1.0
|
cap
|
str | Expr
|
How an open line ends: |
'round'
|
join
|
str | Expr
|
How a path turns a corner: |
'round'
|
translate ¶
Translate the contour by an offset.
Domain: contour → contour
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dx
|
FloatOrExpr
|
X offset (horizontal translation). |
required |
dy
|
FloatOrExpr
|
Y offset (vertical translation). |
required |
union_all ¶
The union of the set's regions, as disjoint regions: overlapping members merge and a hole another member covers is filled. The result is always a set. An open contour is refused.
Domain: contour → contour
convolve2d ¶
convolve2d(
kernel: Sequence[FloatOrExpr],
*,
normalize: BoolOrExpr = False,
border: str | Expr = "replicate",
) -> Pipeline
Apply generic 2D convolution with an arbitrary kernel.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kernel
|
Sequence[FloatOrExpr]
|
Flattened square kernel, row-major: its length is the square of an odd side (9 for 3×3, 25 for 5×5, ...), which is the kernel's size. Each coefficient may be a literal float or a Polars expression, so a batch can convolve with a different kernel per row; the length is structural. |
required |
normalize
|
BoolOrExpr
|
If True, divide output by the sum of absolute kernel values. |
False
|
border
|
str | Expr
|
Border handling mode ( |
'replicate'
|
coordinate_grid ¶
The pixel coordinates of the image: an f32 [H, W, 2] buffer holding
each pixel's centre (x + 0.5, y + 0.5) — the point rasterize
samples — or ((x + 0.5) / W, (y + 0.5) / H) when normalized.
Reads only the input's height and width, never its values, so it takes any dtype and channel count. The coordinates are in the frame of the buffer it follows: after a crop they start at the crop's corner. Combine them with ordinary ops for fields over positions (gradients, radial falloffs, vignettes).
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
normalized
|
BoolOrExpr
|
Divide x by the width and y by the height, so each lies in (0, 1). Accepts a Polars expression for a per-row choice. |
False
|
Example
dx2 = Pipeline().source("image_bytes").coordinate_grid().add_constant(-32.0).square()
crop ¶
crop(
*,
top: IntOrExpr = 0,
left: IntOrExpr = 0,
height: IntOrExpr | None = None,
width: IntOrExpr | None = None,
) -> Pipeline
Extract a rectangular region.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
top
|
IntOrExpr
|
Top offset. |
0
|
left
|
IntOrExpr
|
Left offset. |
0
|
height
|
IntOrExpr | None
|
Crop height (None = to end). |
None
|
width
|
IntOrExpr | None
|
Crop width (None = to end). |
None
|
convert_color ¶
Convert between color spaces.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
from_space
|
str
|
Source color space (rgb, bgr, hsv, lab, ycbcr, gray). |
required |
to_space
|
str
|
Target color space (rgb, bgr, hsv, lab, ycbcr, gray). |
required |
Example
pipe = Pipeline().source("image_bytes").convert_color(from_space="rgb", to_space="hsv")
dilate ¶
dilate(
*,
ksize: IntOrExpr = 3,
iterations: IntOrExpr = 1,
footprint: str | Expr = "square",
) -> Pipeline
Morphological dilation: the local maximum over a footprint (a
ksize × ksize square, or a disk), edges replicated. Requires
single-channel input (e.g. after .grayscale() or .threshold()).
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ksize
|
IntOrExpr
|
Size of the structuring element (the square's side, the disk's diameter). Must be odd and >= 1. Accepts a Polars expression for per-row dynamic values. |
3
|
iterations
|
IntOrExpr
|
Number of times the operation is applied. Accepts a Polars expression for per-row dynamic values. |
1
|
footprint
|
str | Expr
|
The structuring element: |
'square'
|
Example
mask = Pipeline().source("image_bytes").grayscale().threshold(128).dilate(ksize=3)
distance_transform ¶
Exact Euclidean distance transform of a mask: an f32 image of the same shape. Requires single-channel input.
A pixel is foreground when it is non-zero (NaN included). Unsigned, each
foreground pixel holds the distance from its centre to the nearest
background pixel's centre and the background holds 0, as
cv2.distanceTransform(DIST_L2, DIST_MASK_PRECISE) and
scipy.ndimage.distance_transform_edt compute. Signed, every pixel
holds its distance to the boundary between the two classes, the pixel
edges extract_contours traces: negative inside, positive outside
(as .point.signed_distance_to_contour), ±(d - 0.5) for the
centre distance d to the nearest pixel of the other class. When a
class has no pixel at all, the distance to it is infinite.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
signed
|
BoolOrExpr
|
Signed distance to the mask's boundary (negative inside) instead of each foreground pixel's distance to the background. Accepts a Polars expression for a per-row choice. |
False
|
Example
sdf = Pipeline().source("image_bytes").grayscale().threshold(128).distance_transform(signed=True)
equalize_histogram ¶
Apply histogram equalization for contrast enhancement: map each pixel through the normalized CDF, per channel. Output is U8.
Domain: buffer → buffer
Example
eq = Pipeline().source("image_bytes").grayscale().equalize_histogram()
erode ¶
erode(
*,
ksize: IntOrExpr = 3,
iterations: IntOrExpr = 1,
footprint: str | Expr = "square",
) -> Pipeline
Morphological erosion: the local minimum over a footprint (a
ksize × ksize square, or a disk), edges replicated. Requires
single-channel input (e.g. after .grayscale() or .threshold()).
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ksize
|
IntOrExpr
|
Size of the structuring element (the square's side, the disk's diameter). Must be odd and >= 1. Accepts a Polars expression for per-row dynamic values. |
3
|
iterations
|
IntOrExpr
|
Number of times the operation is applied. Accepts a Polars expression for per-row dynamic values. |
1
|
footprint
|
str | Expr
|
The structuring element: |
'square'
|
Example
mask = Pipeline().source("image_bytes").grayscale().threshold(128).erode(ksize=3)
expand_dims ¶
Insert an axis of size 1: [H, W] to [H, W, 1] with axis=-1. The
same elements in the same order, with no copy.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
axis
|
int
|
Where the new axis goes in the output, counted from the end when negative. |
required |
Example
pipe = Pipeline().source("array").expand_dims(-1)
extract_contours ¶
extract_contours(
*,
mode: str | Expr = "external",
method: str | Expr = "simple",
min_area: FloatOrExpr | None = None,
min_area_fraction: FloatOrExpr | None = None,
) -> Pipeline
Extract contours from binary mask.
The traced outline runs along the edges of the boundary pixels (pixel
(x, y) is the square [x, x+1] x [y, y+1]), so it bounds exactly
the pixels of its region: a blob filling w x h pixels comes back
bounding w x h, a single pixel as its unit square, and every region's
area is its pixel count. Rasterizing the result gives back the mask.
Regions are 8-connected: pixels touching only at a corner are one region, whose outline passes through that corner twice.
Borders come back as a flat list with no hierarchy. mode="all" yields
the exterior plus one border for each enclosed background region — holes
that touch or nest enclose one region between them — and reassembling a
holed contour from those is the caller's job. mode="external" keeps
only the outermost, discarding hole borders.
Domain: buffer → contour
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mode
|
str | Expr
|
"external" (outer borders only) or "all" (hole borders too, as a flat list). |
'external'
|
method
|
str | Expr
|
"simple" (remove redundant), "none" (all points), "approx". |
'simple'
|
min_area
|
FloatOrExpr | None
|
Filter small contours: the minimum area in pixels. Accepts a Polars expression for per-row dynamic thresholds. |
None
|
min_area_fraction
|
FloatOrExpr | None
|
Filter small contours relative to the image: the minimum
area as a fraction of its height x width, in (0, 1], resolved per image
as it runs (a speck threshold that scales with resolution). Applies
together with |
None
|
extract_shape ¶
Extract the buffer's shape: one Float64 per dimension, in order
([height, width, channels] for an image), as a vector.
Domain: buffer → vector
Returns:
| Type | Description |
|---|---|
Pipeline
|
|
flip ¶
Flip along specified axes.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
axes
|
Sequence[int]
|
Axes to flip. |
required |
grayscale ¶
Convert to grayscale (luminance 0.299R + 0.587G + 0.114B).
Domain: buffer → buffer
histogram ¶
histogram(
*,
bins: IntOrExpr | Sequence[float] = 256,
range: Sequence[FloatOrExpr] | None = None,
closed: str = "left",
output: str = "buckets",
) -> Pipeline
Compute pixel value histogram.
Bins follow numpy.histogram: a value outside the range or the edges
is in no bin (not counted; "quantized" gives it the index one past the
last bin), as is NaN. Infinite outer edges are open bounds, e.g.
bins=[-inf, 0, 10, inf] keeps every value.
Domain: buffer → vector; with output="quantized": buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bins
|
IntOrExpr | Sequence[float]
|
Number of bins (default 256), a Polars expression for per-row dynamic bin count, or an explicit list of non-decreasing bin edges (the outer ones may be infinite). |
256
|
range
|
Sequence[FloatOrExpr] | None
|
(min, max) tuple, finite. Auto-detected if None, widened to hold every value. |
None
|
closed
|
str
|
"left" or "right" interval inclusiveness (default "left"). |
'left'
|
output
|
str
|
"buckets" (list of structs), "counts" (bin counts), "normalized" (sum to 1.0), "quantized" (pixel indices), "edges" (bin edges). |
'buckets'
|
Example
Pipeline().source("image_bytes").grayscale().histogram(bins=8)
invert ¶
Invert values, keeping the dtype: MAX + MIN - x for an integer
(255 - x for u8, -1 - x for a signed dtype), 1 - x for a float.
Domain: buffer → buffer
label_reduce ¶
label_reduce(
contours: Expr,
*,
reduction: str | Expr = "max",
region_mode: str | Expr = "interior",
) -> Pipeline
Score contour regions against the current buffer values.
This is the buffer-space variant of label reduction. It accepts contours via a Polars expression and returns one score per contour.
Pixels are sampled at their centres. A contour with no area (a point or a line) or whose region catches no pixel centre (a sub-pixel contour) is scored on the pixels its outline passes through, in every region mode, rather than as 0.0.
Domain: buffer → vector
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
contours
|
Expr
|
Contour-set expression ( |
required |
reduction
|
str | Expr
|
Reduction over contour region values ( |
'max'
|
region_mode
|
str | Expr
|
Region selection mode. |
'interior'
|
letterbox ¶
letterbox(
*,
height: IntOrExpr,
width: IntOrExpr,
value: FloatOrExpr = 0.0,
filter: str | Expr = "lanczos3",
) -> Pipeline
Resize image maintaining aspect ratio and pad to exact target size: fit within the target, then pad with centered positioning.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
height
|
IntOrExpr
|
Target height (literal or expression). |
required |
width
|
IntOrExpr
|
Target width (literal or expression). |
required |
value
|
FloatOrExpr
|
Fill value for padding (default 0, typically black). Accepts a Polars expression for per-row dynamic values. |
0.0
|
filter
|
str | Expr
|
Resampling filter for the resize step (default "lanczos3"). |
'lanczos3'
|
Example
pipe = Pipeline().source("image_bytes").letterbox(height=224, width=224)
morphology_gradient ¶
Morphological gradient (dilate - erode): an edge outline. Requires single-channel input.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ksize
|
IntOrExpr
|
Size of the structuring element. Must be odd and >= 1. Accepts a Polars expression for per-row dynamic values. |
3
|
footprint
|
str | Expr
|
The structuring element: |
'square'
|
Example
edges = Pipeline().source("image_bytes").grayscale().threshold(128).morphology_gradient(ksize=3)
normalize ¶
normalize(
*,
method: str = "minmax",
mean: Sequence[FloatOrExpr] | None = None,
std: Sequence[FloatOrExpr] | None = None,
out_dtype: str | None = None,
) -> Pipeline
Normalize values to a standard range.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method
|
str
|
Normalization method: "minmax" scales values to [0, 1] using per-
element min/max; "zscore" standardizes to mean=0, std=1 using per-
element statistics; "preset" applies ImageNet-style channel-wise
normalization, |
'minmax'
|
mean
|
Sequence[FloatOrExpr] | None
|
Per-channel mean values; required for, and only valid with,
method="preset" (e.g. ImageNet |
None
|
std
|
Sequence[FloatOrExpr] | None
|
Per-channel standard deviation values; required for, and only valid
with, method="preset" (e.g. ImageNet |
None
|
out_dtype
|
str | None
|
Output dtype (default f32). Normalization computes in f32 and the
result is cast to this dtype at execution. For half precision use the
sink dtype instead ( |
None
|
Example
Pipeline().source().normalize(method="minmax") Pipeline().source().normalize( ... method="preset", ... mean=[0.485, 0.456, 0.406], ... std=[0.229, 0.224, 0.225], ... )
pad ¶
pad(
*,
top: IntOrExpr = 0,
bottom: IntOrExpr = 0,
left: IntOrExpr = 0,
right: IntOrExpr = 0,
value: FloatOrExpr = 0.0,
mode: str | Expr = "constant",
) -> Pipeline
Add padding to the image.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
top
|
IntOrExpr
|
Padding on top edge. |
0
|
bottom
|
IntOrExpr
|
Padding on bottom edge. |
0
|
left
|
IntOrExpr
|
Padding on left edge. |
0
|
right
|
IntOrExpr
|
Padding on right edge. |
0
|
value
|
FloatOrExpr
|
Fill value for "constant" mode (default 0). Accepts a Polars expression for per-row dynamic values. |
0.0
|
mode
|
str | Expr
|
Padding mode - "constant", "edge", "reflect", "symmetric". |
'constant'
|
Example
pipe = Pipeline().source("image_bytes").pad(top=10, bottom=10) pipe = Pipeline().source("image_bytes").pad(left=20, right=20, value=128)
pad_to_size ¶
pad_to_size(
*,
height: IntOrExpr,
width: IntOrExpr,
position: str | Expr = "center",
value: FloatOrExpr = 0.0,
) -> Pipeline
Pad image to exact target size (computed at runtime). A larger image is not cropped - resize first if needed.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
height
|
IntOrExpr
|
Target height. |
required |
width
|
IntOrExpr
|
Target width. |
required |
position
|
str | Expr
|
Where to place original content: "center" (default), "top-left" or "bottom-right". |
'center'
|
value
|
FloatOrExpr
|
Fill value for padding (default 0). Accepts a Polars expression for per-row dynamic values. |
0.0
|
Example
pipe = Pipeline().source("image_bytes").pad_to_size(height=100, width=200)
perceptual_hash ¶
Compute a perceptual hash fingerprint.
Domain: buffer → vector
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
algorithm
|
str
|
"perceptual" (pHash), "average" (aHash), "difference" (dHash). |
'perceptual'
|
hash_size
|
int
|
Number of bits in the hash (must be power of 2). It fixes the output vector length, so it is literal-only. |
64
|
Example
Pipeline().source("image_bytes").perceptual_hash()
reduce_argmax ¶
Index of the maximum value along an axis.
Unlike other reductions it always requires an axis: a global index is ambiguous for a multi-dimensional array. The result has the reduced shape and an i64 dtype.
Domain: buffer → buffer, vector → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
axis
|
int
|
Axis along which to find the index. |
required |
reduce_argmin ¶
Index of the minimum value along an axis.
Unlike other reductions it always requires an axis: a global index is ambiguous for a multi-dimensional array. The result has the reduced shape and an i64 dtype.
Domain: buffer → buffer, vector → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
axis
|
int
|
Axis along which to find the index. |
required |
reduce_max ¶
Reduce buffer by computing the maximum value.
buffer → scalar, vector → scalar; with axis: buffer → buffer, vector
→ buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
axis
|
int | None
|
Axis to reduce along. None for global reduction. It fixes the output rank, so it is literal-only. |
None
|
reduce_mean ¶
Compute arithmetic mean.
buffer → scalar, vector → scalar; with axis: buffer → buffer, vector
→ buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
axis
|
int | None
|
Axis to reduce along. None for global reduction. It fixes the output rank, so it is literal-only. |
None
|
reduce_min ¶
Reduce buffer by computing the minimum value.
buffer → scalar, vector → scalar; with axis: buffer → buffer, vector
→ buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
axis
|
int | None
|
Axis to reduce along. None for global reduction. It fixes the output rank, so it is literal-only. |
None
|
reduce_percentile ¶
Compute the q-th percentile of all values (linear interpolation, as numpy's default).
Domain: buffer → scalar, vector → scalar
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
q
|
FloatOrExpr
|
Percentile to compute, in [0, 100]. Accepts a Polars expression for per- row dynamic values. |
required |
reduce_popcount ¶
Count set bits (1s) in the buffer.
Domain: buffer → scalar, vector → scalar
reduce_std ¶
Reduce buffer by computing the standard deviation.
buffer → scalar, vector → scalar; with axis: buffer → buffer, vector
→ buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
axis
|
int | None
|
Axis to reduce along. None for global reduction. |
None
|
ddof
|
IntOrExpr
|
Delta degrees of freedom. 0 for population std (default), 1 for sample std. Accepts a Polars expression for per-row dynamic values. |
0
|
Example
pipe = Pipeline().source("image_bytes").reduce_std(ddof=1)
reduce_sum ¶
Sum all elements in the buffer.
Domain: buffer → scalar, vector → scalar
relu ¶
Apply ReLU activation (max(0, x)): negative values become zero.
Domain: buffer → buffer
reshape ¶
Reshape array to new dimensions.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shape
|
Sequence[IntOrExpr]
|
New shape. The number of entries fixes the output rank; each entry may be a Polars expression. |
required |
resize ¶
Resize image to specified dimensions.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
height
|
IntOrExpr
|
Target height. |
required |
width
|
IntOrExpr
|
Target width. |
required |
filter
|
str | Expr
|
Interpolation: "nearest", "bilinear", "lanczos3" (default). |
'lanczos3'
|
Example
Pipeline().source("image_bytes").resize(height=224, width=224)
resize_max ¶
Resize image so the maximum dimension equals target, preserving aspect ratio (200x100 with max_size=50 gives 50x25).
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_size
|
IntOrExpr
|
Target for the maximum dimension (literal or expression). |
required |
filter
|
str | Expr
|
Resize filter ("nearest", "bilinear", "lanczos3"). |
'lanczos3'
|
Example
pipe = Pipeline().source("image_bytes").resize_max(224)
resize_min ¶
Resize image so the minimum dimension equals target, preserving aspect ratio (200x100 with min_size=50 gives 100x50).
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
min_size
|
IntOrExpr
|
Target for the minimum dimension (literal or expression). |
required |
filter
|
str | Expr
|
Resize filter ("nearest", "bilinear", "lanczos3"). |
'lanczos3'
|
Example
pipe = Pipeline().source("image_bytes").resize_min(224)
resize_to_height ¶
Resize image to target height, preserving aspect ratio (width is computed at runtime).
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
height
|
IntOrExpr
|
Target height (literal or expression). |
required |
filter
|
str | Expr
|
Resize filter ("nearest", "bilinear", "lanczos3"). |
'lanczos3'
|
Example
pipe = Pipeline().source("image_bytes").resize_to_height(224)
resize_to_width ¶
Resize image to target width, preserving aspect ratio (height is computed at runtime).
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width
|
IntOrExpr
|
Target width (literal or expression). |
required |
filter
|
str | Expr
|
Resize filter ("nearest", "bilinear", "lanczos3"). |
'lanczos3'
|
Example
pipe = Pipeline().source("image_bytes").resize_to_width(224)
rotate ¶
rotate(
angle: FloatOrExpr,
*,
expand: bool = False,
interpolation: str | Expr = "bilinear",
border_value: FloatOrExpr = 0.0,
) -> Pipeline
Rotate image by specified angle.
For angles of 90, 180, or 270 degrees, this uses zero-copy view operations
(interpolation and border_value do not apply: nothing is resampled
and no out-of-bounds region is exposed). For arbitrary angles, the rotation
is performed via an affine transformation using the specified
interpolation and border value. For combined rotation + scale or explicit
output sizing, use :meth:rotate_and_scale or :meth:warp_affine.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angle
|
FloatOrExpr
|
Rotation angle in degrees (positive = clockwise). Can be a literal float or Polars expression. |
required |
expand
|
bool
|
If True, expand output dimensions to fit rotated image. If False (default), keep original dimensions (corners may be cropped). |
False
|
interpolation
|
str | Expr
|
Interpolation method for arbitrary angles -- |
'bilinear'
|
border_value
|
FloatOrExpr
|
Fill value for out-of-bounds pixels (default 0). Not applicable to 90/180/270 degree rotations. |
0.0
|
rotate_and_scale ¶
rotate_and_scale(
*,
angle: FloatOrExpr,
center: Sequence[FloatOrExpr],
output_size: Sequence[IntOrExpr],
scale: FloatOrExpr = 1.0,
) -> Pipeline
Combined rotation and scaling around a center point.
Warps with OpenCV's getRotationMatrix2D(center, -angle, scale)
matrix, built per row from the values the row resolves: the matrix
is computed in one place (the engine), so a literal and the same
value as an expression give identical output.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angle
|
FloatOrExpr
|
Rotation angle in degrees (positive = clockwise). Accepts a Polars expression for a per-row angle. |
required |
center
|
Sequence[FloatOrExpr]
|
|
required |
output_size
|
Sequence[IntOrExpr]
|
|
required |
scale
|
FloatOrExpr
|
Scale factor (default 1.0). Accepts an expression. |
1.0
|
Example
>>> pipe = Pipeline().source("image_bytes").rotate_and_scale(
... angle=45.0, scale=1.2, center=(112, 112), output_size=(224, 224)
... )
>>> # Per-row angle from a column
>>> pipe = Pipeline().source("image_bytes").rotate_and_scale(
... angle=pl.col("theta"), center=(112, 112), output_size=(224, 224)
... )
round ¶
Round to nearest, ties to even (matches Polars/numpy).
Domain: buffer → buffer
squeeze ¶
Remove an axis of size 1: [H, W, 1] to [H, W] with axis=-1. The
same elements in the same order, with no copy. An axis of another size
is refused.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
axis
|
int
|
The axis to remove, counted from the end when negative. |
required |
Example
pipe = Pipeline().source("array").squeeze(-1)
subtract_constant ¶
Subtract a constant from every value (x - value).
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
FloatOrExpr
|
Constant subtrahend (literal or per-row expression). |
required |
threshold ¶
Apply binary threshold: a U8 mask, 255 where the element exceeds value
and 0 elsewhere (for u8 input typically 0-255; for [0, 1] floats e.g. 0.5).
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
FloatOrExpr
|
Threshold value (int or float, or Polars expression). |
required |
transpose ¶
Transpose dimensions.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
axes
|
Sequence[int]
|
New order of axes: a permutation of every input axis. |
required |
warp_affine ¶
warp_affine(
*,
matrix: Sequence[FloatOrExpr],
output_size: Sequence[IntOrExpr],
interpolation: str | Expr = "bilinear",
border_value: FloatOrExpr = 0.0,
) -> Pipeline
Apply a 2x3 affine transformation matrix.
The matrix [a, b, tx, c, d, ty] is a forward mapping from
source to destination (same convention as OpenCV warpAffine):
The kernel inverts this matrix internally for interpolation.
Domain: buffer → buffer
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
matrix
|
Sequence[FloatOrExpr]
|
Six-element sequence representing the 2x3 affine matrix |
required |
output_size
|
Sequence[IntOrExpr]
|
|
required |
interpolation
|
str | Expr
|
Interpolation method -- |
'bilinear'
|
border_value
|
FloatOrExpr
|
Pixel value for out-of-bounds regions (default 0). |
0.0
|
Example
>>> # Translate image by (50, 30)
>>> pipe = Pipeline().source("image_bytes").warp_affine(
... matrix=[1.0, 0.0, 50.0, 0.0, 1.0, 30.0],
... output_size=(224, 224),
... )
>>>
>>> # Per-sample random affine: each row uses its own matrix columns
>>> pipe = Pipeline().source("image_bytes").warp_affine(
... matrix=[pl.col("a"), pl.col("b"), pl.col("tx"),
... pl.col("c"), pl.col("d"), pl.col("ty")],
... output_size=(224, 224),
... )
on_error ¶
Set the per-row error policy for the executed pipeline graph.
Controls what happens when producing a single row fails (source decode, operation execution, or output encoding):
"raise"(default): the first failing row fails the whole expression with its error."null": failing rows yield null for all of the graph's outputs; other rows are unaffected."null_with_message": as"null", plus the output becomes a struct with a reserved_errorstring field carrying the failure message for bad rows (null for good rows). Single-output pipelines become a two-field struct (_output+_error).
This is a graph-level setting: when pipelines are composed
(merge_pipe, binary ops), all composed pipelines must agree on
the policy.
Note: the per-source source(..., on_error="null") setting remains
independent — it nulls only the outputs that depend on a failing
source decode, while this policy covers any error producing the row.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
policy
|
str
|
One of |
required |
Returns:
| Type | Description |
|---|---|
'Pipeline'
|
New Pipeline with the error policy set. |
Example
pipe = Pipeline().source("image_bytes").resize(height=224, width=224).on_error("null")
on_null_param ¶
Set what a null in a per-row expression parameter means.
Parameters that take a Polars expression are read from ordinary columns, which may contain nulls:
"raise"(default): a null parameter fails the whole expression."null": rows whose parameter is null yield null, exactly as a null input image already does. Other rows are unaffected.
Under "null" only the outputs that actually depend on the affected
operation go null — unlike on_error("null"), which nulls every
output of a failing row. The two settings are independent: this one
does not weaken error reporting for decode, encode or genuine
operation failures.
To substitute a fallback value instead of nulling, fill the null in
the expression itself — pl.col("scale").fill_null(1.0) — which is
the idiomatic Polars way and needs nothing from this API.
This is a graph-level setting. Only a non-default policy is collected
from the composed pipelines, so an explicit "raise" is
indistinguishable from leaving it unset: composing a "null"
pipeline with a "raise" one gives the whole graph "null",
rather than being rejected as a conflict. (With only two values there
is no combination that can conflict; on_error, which has two
non-default values, does reject genuine disagreement.)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
policy
|
str
|
One of |
required |
Returns:
| Type | Description |
|---|---|
'Pipeline'
|
New Pipeline with the null-parameter policy set. |
Example
pipe = ( ... Pipeline() ... .source("image_bytes") ... .resize(height=pl.col("h"), width=pl.col("w")) ... .on_null_param("null") ... )
current_domain ¶
Get the current data domain of the pipeline.
Returns:
| Type | Description |
|---|---|
str
|
Current domain: "buffer", "contour", "scalar", or "vector". |
output_dtype ¶
Get the expected output dtype of the pipeline.
This is the dtype of the buffer after all operations have been applied.
Used for static type inference in list/array sinks. May be "auto"
if the dtype has not yet been determined (e.g. an image source with
no dtype-fixing operation applied).
Returns:
| Type | Description |
|---|---|
str
|
Output dtype string: |
thumbnail ¶
Decode only a downscaled thumbnail of an image source.
Explicit, chainable form of source(..., decode_max_size=...): it
asserts the pipeline needs at most max_size pixels on the decoded
image's long side, so JPEG decoding uses IDCT scaling (1/8, 1/4 or 1/2)
to skip work — a large CPU and memory win. The decoded long side never
drops below min(max_size, original), so a downstream resize down to
max_size never upscales. Non-JPEG formats (PNG, …) ignore the
assertion and decode at full size.
This is the cheap front of a decode-aware curation pass: decode a thumbnail, compute a cheap signal (perceptual hash, mean, blur/quality score), filter on it, and only full-decode the survivors in a second pass. A scaled decode followed by a resize is not bit-identical to a full decode then the same resize (different resampling path) — hence the explicit opt-in.
Must be called after source(...) on an image_bytes/file_path
source. It appends no op: it sets the source's decode_max_size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_size
|
int
|
Maximum pixels on the decoded long side. Positive int. |
required |
Returns:
| Type | Description |
|---|---|
'Pipeline'
|
A new Pipeline whose source carries the decode-scale assertion. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If there is no source, the source is not an image
source, or |
assert_shape ¶
assert_shape(
*,
dims: "Sequence[int | None] | None" = None,
height: IntOrExpr | None = None,
width: IntOrExpr | None = None,
channels: IntOrExpr | None = None,
) -> "Pipeline"
Declare a shape the planner cannot work out for itself.
Use this when the source does not reveal its shape at plan time — a
list column's rank and sizes are only known once the data arrives,
so a fixed-shape .sink("array") has nothing to publish without a
declaration. For a source the planner can read (image bytes, a file
path, a fixed-size Array column), what it reveals is already
planned and an assertion is at best redundant.
An assertion states a fact; it does not change one. A declaration that contradicts what the pipeline already knows — or that names a dimension the output rank does not have — is rejected here, at the line that wrote it. Execution checks it against every row where it was written, so a row whose data disagrees fails naming the assertion, and everything after it may rely on the declared shape.
Two spellings:
dims=[8, 8, 3]— positional and complete, of any rank. Entry i is the size of dimension i;Noneleaves one unknown. This also pins the output rank tolen(dims), which is what lets a list source reach anarraysink. An entry may be an expression: it is checked per row, and like any per-row size it publishes no shape.height=/width=/channels=— the[H, W, C]spelling of dimensions 0, 1 and 2, for the common image case. The hints are positional, so these names only describe an[H, W, C]buffer: after atranspose([2, 0, 1])dimension 0 is the channel axis, and calling itheightwould be a lie. A keyword naming a dimension the known rank does not have (channels=on a rank-2 buffer) is rejected; usedims=for anything that is not an image. Expressions are accepted and resolved per row, but a per-row size is not a plan-time fact, so it publishes no shape.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dims
|
'Sequence[int | None] | None'
|
Full positional shape. Mutually exclusive with the
|
None
|
height
|
IntOrExpr | None
|
Size of dimension 0 (literal or expression). |
None
|
width
|
IntOrExpr | None
|
Size of dimension 1 (literal or expression). |
None
|
channels
|
IntOrExpr | None
|
Size of dimension 2 (literal or expression). |
None
|
Returns:
| Type | Description |
|---|---|
'Pipeline'
|
A new pipeline carrying the declaration. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the declaration contradicts a known dimension or
rank, names a dimension the rank does not have, or mixes
|
flip_h ¶
Flip horizontally (along width axis).
Returns:
| Type | Description |
|---|---|
'Pipeline'
|
Self for chaining. |
flip_v ¶
Flip vertically (along height axis).
Returns:
| Type | Description |
|---|---|
'Pipeline'
|
Self for chaining. |
scale ¶
scale(
factor: FloatOrExpr,
out_dtype: str | None = None,
preserve_dtype: bool = False,
) -> "Pipeline"
Multiply all values by a factor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
factor
|
FloatOrExpr
|
Scale factor. |
required |
out_dtype
|
str | None
|
Dtype to leave the result in. Any :class: |
None
|
preserve_dtype
|
bool
|
If True, cast the result back to the input dtype,
e.g. u8 in → u8 out instead of the promoted f32. The same
mechanism as |
False
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If domain is not buffer, or the output dtype cannot be
honored (unknown dtype name, unknown input dtype under
|
clamp ¶
clamp(
min_val: FloatOrExpr,
max_val: FloatOrExpr,
out_dtype: str | None = None,
preserve_dtype: bool = False,
) -> "Pipeline"
Clamp values to a range.
This operation accepts any numeric input dtype and automatically handles type promotion. Integers are promoted to float32; floats are preserved.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
min_val
|
FloatOrExpr
|
Minimum value (literal or expression). |
required |
max_val
|
FloatOrExpr
|
Maximum value (literal or expression). |
required |
out_dtype
|
str | None
|
Dtype to leave the result in. Any :class: |
None
|
preserve_dtype
|
bool
|
If True, cast the result back to the input dtype,
e.g. u8 in → u8 out instead of the promoted f32. The same
mechanism as |
False
|
Returns:
| Type | Description |
|---|---|
'Pipeline'
|
Self for chaining. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If domain is not buffer, or the output dtype cannot be
honored (unknown dtype name, unknown input dtype under
|
adjust_brightness ¶
Adjust image brightness by scaling pixel values.
Convenience method equivalent to .scale(factor).clamp(min_val=0, max_val=255).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
factor
|
FloatOrExpr
|
Brightness factor. 1.0 = no change, >1 = brighter, <1 = darker. |
required |
preserve_dtype
|
bool
|
If True, cast the result back to the dtype the pipeline had before this op (round-then-saturate for integer targets), e.g. u8 in → u8 out instead of the promoted f32. Requires the pipeline's dtype to be known (not "auto"). |
False
|
Returns:
| Type | Description |
|---|---|
'Pipeline'
|
Self for chaining. |
to_hsv ¶
Convert from RGB to HSV color space.
Returns:
| Type | Description |
|---|---|
'Pipeline'
|
Self for chaining. |
to_lab ¶
Convert from RGB to CIE LAB color space.
Output dtype is promoted to f32 (L=[0,100], a/b~[-128,127]).
Returns:
| Type | Description |
|---|---|
'Pipeline'
|
Self for chaining. |
to_bgr ¶
Convert from RGB to BGR channel order.
Returns:
| Type | Description |
|---|---|
'Pipeline'
|
Self for chaining. |
to_ycbcr ¶
Convert from RGB to YCbCr color space.
Returns:
| Type | Description |
|---|---|
'Pipeline'
|
Self for chaining. |
sobel ¶
Sobel gradient operator.
Convenience method that delegates to :meth:convolve2d with standard
Sobel kernels.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
axis
|
str
|
Gradient direction — |
'x'
|
Returns:
| Type | Description |
|---|---|
'Pipeline'
|
Self for chaining. |
laplacian ¶
sharpen ¶
Sharpen using an unsharp-mask-style kernel.
The kernel sum is 1 (brightness-preserving) with strength controlling
how aggressively edges are enhanced. strength=0 produces the
identity; higher values increase edge emphasis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
strength
|
FloatOrExpr
|
Sharpening strength (default 1.0). Accepts a Polars
expression: the kernel coefficients are built from it
element-wise, and |
1.0
|
Returns:
| Type | Description |
|---|---|
'Pipeline'
|
Self for chaining. |
morphology_open ¶
morphology_open(
*,
ksize: IntOrExpr = OP_DEFAULTS["erode"]["ksize"],
footprint: str | Expr = OP_DEFAULTS["erode"][
"footprint"
],
) -> "Pipeline"
Morphological opening (erode then dilate).
Removes small bright spots while preserving larger structures.
Equivalent to .erode(ksize=ksize, footprint=footprint)
.dilate(ksize=ksize, footprint=footprint).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ksize
|
IntOrExpr
|
Size of the structuring element (the square's side, the disk's diameter). Must be odd and >= 1. Accepts a Polars expression for per-row dynamic values. |
OP_DEFAULTS['erode']['ksize']
|
footprint
|
str | Expr
|
The structuring element: |
OP_DEFAULTS['erode']['footprint']
|
Returns:
| Type | Description |
|---|---|
'Pipeline'
|
New Pipeline with opening applied. |
morphology_close ¶
morphology_close(
*,
ksize: IntOrExpr = OP_DEFAULTS["dilate"]["ksize"],
footprint: str | Expr = OP_DEFAULTS["dilate"][
"footprint"
],
) -> "Pipeline"
Morphological closing (dilate then erode).
Fills small dark holes while preserving larger structures.
Equivalent to .dilate(ksize=ksize, footprint=footprint)
.erode(ksize=ksize, footprint=footprint).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ksize
|
IntOrExpr
|
Size of the structuring element (the square's side, the disk's diameter). Must be odd and >= 1. Accepts a Polars expression for per-row dynamic values. |
OP_DEFAULTS['dilate']['ksize']
|
footprint
|
str | Expr
|
The structuring element: |
OP_DEFAULTS['dilate']['footprint']
|
Returns:
| Type | Description |
|---|---|
'Pipeline'
|
New Pipeline with closing applied. |
resize_scale ¶
resize_scale(
*,
scale: FloatOrExpr | None = None,
scale_x: FloatOrExpr | None = None,
scale_y: FloatOrExpr | None = None,
filter: str | Expr = OP_DEFAULTS["resize_scale"][
"filter"
],
) -> "Pipeline"
Resize image by scale factor.
Target dimensions are computed at runtime as: - new_width = input_width * scale_x - new_height = input_height * scale_y
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scale
|
FloatOrExpr | None
|
Uniform scale factor (applies to both x and y). |
None
|
scale_x
|
FloatOrExpr | None
|
X (width) scale factor. If None, uses scale. |
None
|
scale_y
|
FloatOrExpr | None
|
Y (height) scale factor. If None, uses scale. |
None
|
filter
|
str | Expr
|
Resize filter ("nearest", "bilinear", "lanczos3"). |
OP_DEFAULTS['resize_scale']['filter']
|
Returns:
| Type | Description |
|---|---|
'Pipeline'
|
Self for chaining. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If neither scale nor scale_x/scale_y specified. |
ValueError
|
If filter is invalid or current domain is not buffer. |
Example
>>> # Uniform 50% downscale
>>> pipe = Pipeline().source("image_bytes").resize_scale(scale=0.5)
>>>
>>> # Non-uniform: half width, double height
>>> pipe = Pipeline().source("image_bytes").resize_scale(scale_x=0.5, scale_y=2.0)
>>>
>>> # Dynamic scale from column
>>> pipe = Pipeline().source("image_bytes").resize_scale(scale=pl.col("zoom"))
shear ¶
shear(
*,
sx: FloatOrExpr = 0.0,
sy: FloatOrExpr = 0.0,
output_size: tuple[IntOrExpr, IntOrExpr],
) -> "Pipeline"
Apply a shear transformation.
Convenience wrapper that builds a shear matrix and delegates to
:meth:warp_affine.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sx
|
FloatOrExpr
|
Horizontal shear factor (literal or per-row Polars expression). |
0.0
|
sy
|
FloatOrExpr
|
Vertical shear factor (literal or per-row Polars expression). |
0.0
|
output_size
|
tuple[IntOrExpr, IntOrExpr]
|
|
required |
Returns:
| Type | Description |
|---|---|
'Pipeline'
|
Self for chaining. |
rasterize ¶
rasterize(
*,
width: IntOrExpr | None = None,
height: IntOrExpr | None = None,
shape: "LazyPipelineExpr | None" = None,
fill_value: IntOrExpr = OP_DEFAULTS["rasterize"][
"fill_value"
],
background: IntOrExpr = OP_DEFAULTS["rasterize"][
"background"
],
supersample: IntOrExpr = OP_DEFAULTS["rasterize"][
"supersample"
],
) -> "Pipeline"
Rasterize contours to a mask: each region filled.
A pixel is filled when its centre — (x + 0.5, y + 0.5) — lies inside
the contour, boundary included; holes are cut out by the same rule. This
is the convention contains_point and the area measures follow, so for
a shape whose vertices are integers on axis-aligned edges the mask holds
exactly area() pixels.
The contour domain carries a set — extract_contours() generally
yields more than one — and the mask is their union: each member's
exterior minus its own holes. One member's hole never erases another's
fill, and the result does not depend on the set's order. fill_value
and background may be inverted; the same region is painted either way.
supersample=n anti-aliases: each pixel samples its n x n
sub-pixel centres (x + (i + 0.5) / n, y + (j + 0.5) / n) and is
painted background + (fill_value - background) * k / n**2 for the
k inside, rounded half away from zero. n = 1 (the default) is the
centre rule above.
An open contour (is_closed = False, a line) has no region to fill
and is refused. stroke() turns lines (and regions' outlines) into
the regions they cover, which this then fills:
.stroke(width=2).rasterize(...).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width
|
IntOrExpr | None
|
Mask width. |
None
|
height
|
IntOrExpr | None
|
Mask height. |
None
|
shape
|
'LazyPipelineExpr | None'
|
Match dimensions from another pipeline. |
None
|
fill_value
|
IntOrExpr
|
Inside value (default 255). Accepts a Polars expression for per-row dynamic values. |
OP_DEFAULTS['rasterize']['fill_value']
|
background
|
IntOrExpr
|
Outside value (default 0). Accepts a Polars expression for per-row dynamic values. |
OP_DEFAULTS['rasterize']['background']
|
supersample
|
IntOrExpr
|
Samples per pixel along each axis, 1 to 16 (default 1). Accepts a Polars expression for per-row dynamic values. |
OP_DEFAULTS['rasterize']['supersample']
|
validate ¶
Validate that the pipeline is well-formed.
Raises:
| Type | Description |
|---|---|
ValueError
|
If pipeline is invalid. |
has_source ¶
Check if the pipeline has a source defined.
Returns:
| Type | Description |
|---|---|
bool
|
True if the pipeline has a source defined. |
to_graph ¶
Convert this linear pipeline to a graph representation.
This is the unified execution path - all pipelines are converted to graphs before execution. A Pipeline becomes a single node in the graph.
For multi-output with intermediate checkpoints, use LazyPipelineExpr composition with .pipe() and .alias() instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
column
|
Expr
|
The input column expression. |
required |
Returns:
| Type | Description |
|---|---|
'PipelineGraph'
|
PipelineGraph representation of this pipeline. |
Example
>>> from polars_cv import OptFlags
>>> pipe = Pipeline().source("image_bytes").resize(height=100, width=200)
>>> graph = pipe.to_graph(pl.col("image"))
>>> expr = graph.optimize(OptFlags.all()).to_expr()
to_expr() requires the optimization phase to have run — call
graph.optimize(flags) first (or use the higher-level
LazyPipelineExpr.sink, which does it for you).
explain ¶
Render the pipeline's op chain, logical or physical.
Mirrors Polars' .explain(optimized=...): the same pipeline can be
inspected before and after the plan-time optimization phase, so a user
sees the physical graph differ while the output stays identical.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
optimized
|
bool
|
When |
True
|
opt_flags
|
'OptFlags | bool | None'
|
Which passes to apply when |
None
|
Returns:
| Type | Description |
|---|---|
str
|
A one-line |