Functions¶
Utility functions for working with polars-cv outputs.
NumPy Conversion¶
numpy_from_struct¶
Convert numpy/torch/ndarray sink output (struct) to NumPy array.
The numpy and torch sinks return a Polars Struct (NUMPY_OUTPUT_SCHEMA):
- data: Binary - raw bytes of the array
- dtype: String - NumPy dtype name (e.g., "float32")
- shape: List[UInt64] - shape of the array
- strides: List[Int64] - byte strides, so non-contiguous outputs keep their layout
- offset: UInt64 - byte offset into data
The ndarray sink returns the same struct tagged polars_cv.ndarray
(NdArrayType); numpy_from_struct reads both.
from polars_cv import numpy_from_struct
# From a Series element (dict)
arr = numpy_from_struct(result["tensor"][0])
print(f"Shape: {arr.shape}, dtype: {arr.dtype}")
# Or pass a Series directly (takes first element)
arr = numpy_from_struct(result["tensor"])
numpy_from_column¶
Read every row of a numpy/ndarray sink column as a NumPy array, without
copying. Each array is a read-only view of the column's own Arrow memory, with
the row's shape, dtype and byte strides, and keeps that memory alive after the
column is gone; null rows are None. Pass copy=True for owned, writable,
C-contiguous arrays.
from polars_cv import numpy_from_column
arrays = numpy_from_column(result["tensor"])
batch = [a for a in arrays if a is not None]
Prefer it to numpy_from_struct over .to_list() rows, which copies every
row into Python bytes first.
Extension Types¶
extension_types ¶
polars-cv's Arrow extension types.
An extension type tags a column with what it is — a point, a contour, a bounding box, an N-D array — on top of the plain struct that stores it. A consumer then identifies the column by its type instead of sniffing field names, and a tagged column keeps that identity through Parquet and IPC.
======================== ============================================
type storage
======================== ============================================
polars_cv.ndarray :data:NUMPY_OUTPUT_SCHEMA (the numpy/torch sink struct)
polars_cv.point :data:~polars_cv.geometry.schemas.POINT_SCHEMA
polars_cv.contour :data:~polars_cv.geometry.schemas.CONTOUR_SCHEMA
polars_cv.bbox :data:~polars_cv.geometry.schemas.BBOX_SCHEMA
======================== ============================================
Producing and consuming them:
.sink("ndarray")emits apolars_cv.ndarraycolumn;sink("numpy")keeps emitting the plain struct.- Any column with the right storage can be tagged with
.ext.to(PointType())and friends — a zero-copy relabel. - Every polars-cv expression accepts a tagged column wherever it accepts the plain struct. Geometry outputs are plain structs.
.ext.storage()recovers the plain struct. Polars' struct operations (.struct.field,unnest, casts) need it: they do not see through a tag.
An instance of one of these classes always has canonical storage. polars
rebuilds a registered type through :meth:~_PolarsCvType.ext_from_params, and
a column that carries one of these names over any other storage (or with
metadata) comes back as polars' generic :class:polars.Extension instead — so
isinstance(dtype, PointType) is a complete check, never a guess.
Where the facts live. The storage layouts belong to the Rust plugin
(geom_schema, output::numpy_output_dtype); ExtType in
src/ext_types.rs names the four types there and _lib.extension_types()
publishes them. They are restated here only so import polars_cv can register
the types without loading the compiled extension (registering late would let a
Parquet read decay a tagged column to polars' generic extension). The parity test
test_python_types_match_the_rust_declaration holds the two together.
.. warning::
Polars documents its extension-type API as unstable. Everything polars-cv
does with it goes through this module and src/ext_types.rs.
EXTENSION_TYPES
module-attribute
¶
EXTENSION_TYPES: tuple[type[_PolarsCvType], ...] = (
NdArrayType,
PointType,
ContourType,
BBoxType,
)
NdArrayType ¶
Bases: _PolarsCvType
polars_cv.ndarray: a strided N-D array, as the numpy/torch sink struct.
Emitted by .sink("ndarray"); read back with
:func:polars_cv.numpy_from_struct.
ext_from_params
classmethod
¶
Rebuild the type polars read, refusing to vouch for a malformed one.
Returns an instance of this class only for the canonical storage and no
metadata. Anything else under this name is returned as the generic
pl.Extension: raising here is not an option, because polars turns
an exception from this hook into a panic.
PointType ¶
Bases: _PolarsCvType
polars_cv.point: a 2-D {x, y} point.
ext_from_params
classmethod
¶
Rebuild the type polars read, refusing to vouch for a malformed one.
Returns an instance of this class only for the canonical storage and no
metadata. Anything else under this name is returned as the generic
pl.Extension: raising here is not an option, because polars turns
an exception from this hook into a panic.
ContourType ¶
Bases: _PolarsCvType
polars_cv.contour: a polygon {exterior, holes, is_closed}.
ext_from_params
classmethod
¶
Rebuild the type polars read, refusing to vouch for a malformed one.
Returns an instance of this class only for the canonical storage and no
metadata. Anything else under this name is returned as the generic
pl.Extension: raising here is not an option, because polars turns
an exception from this hook into a panic.
BBoxType ¶
Bases: _PolarsCvType
polars_cv.bbox: an axis-aligned box {x, y, width, height}.
ext_from_params
classmethod
¶
Rebuild the type polars read, refusing to vouch for a malformed one.
Returns an instance of this class only for the canonical storage and no
metadata. Anything else under this name is returned as the generic
pl.Extension: raising here is not an option, because polars turns
an exception from this hook into a panic.
register_extension_types ¶
Register every polars-cv type with polars. Idempotent.
Called once at import polars_cv. A name already registered to a class
that is not ours is refused: registering over it would change how the other
code's columns decode, and leaving it would change how ours do.
Mask Metrics¶
mask_iou¶
Compute IoU between two mask pipelines.
from polars_cv import mask_iou, Pipeline
import polars as pl
pred_pipe = Pipeline().source("image_bytes").grayscale().threshold(128)
gt_pipe = Pipeline().source("contour").rasterize(width=256, height=256)
result = df.with_columns(
iou=mask_iou(
pl.col("prediction").cv.pipe(pred_pipe),
pl.col("ground_truth").cv.pipe(gt_pipe),
)
)
mask_dice¶
Compute Dice coefficient between two mask pipelines.
from polars_cv import mask_dice
result = df.with_columns(
dice=mask_dice(
pl.col("prediction").cv.pipe(pred_pipe),
pl.col("ground_truth").cv.pipe(gt_pipe),
)
)
Hash Comparison¶
hamming_distance¶
Compute Hamming distance between two perceptual hashes.
from polars_cv import hamming_distance, Pipeline
import polars as pl
hash_pipe = Pipeline().source("image_bytes").perceptual_hash()
result = df.with_columns(
distance=hamming_distance(
pl.col("image_a").cv.pipe(hash_pipe),
pl.col("image_b").cv.pipe(hash_pipe),
)
)
hash_similarity¶
Compute similarity percentage between two perceptual hashes.
from polars_cv import hash_similarity
result = df.with_columns(
similarity=hash_similarity(
pl.col("image_a").cv.pipe(hash_pipe),
pl.col("image_b").cv.pipe(hash_pipe),
hash_bits=64, # Match your hash size
)
)
Display¶
show_images¶
Display images from a binary column in Jupyter notebooks. Falls back to a text summary outside notebook environments.
Supports PNG, JPEG, WebP, TIFF, BMP, GIF, VIEW protocol blobs, and numpy-sink struct columns. Format is auto-detected.
Types¶
ColorSpace¶
Color space identifiers for convert_color.
from polars_cv import ColorSpace
ColorSpace.RGB
ColorSpace.BGR
ColorSpace.HSV
ColorSpace.LAB
ColorSpace.YCBCR
ColorSpace.GRAY
CloudOptions¶
Configuration for cloud storage access. Named fields cover the common
credentials; storage_options forwards arbitrary options to the underlying
object_store backend using its native config keys (winning over named fields
on collision); gcs_bearer_token supplies a pre-obtained GCS OAuth access
token for credential types object_store cannot load natively (e.g. federated
external_account_authorized_user ADC).
from polars_cv import CloudOptions
options = CloudOptions(
aws_region="us-east-1",
aws_access_key_id="...",
aws_secret_access_key="...",
)
# GCS via an Application Default Credentials file:
options = CloudOptions(
storage_options={"google_application_credentials": "/path/adc.json"}
)
# GCS via a pre-minted OAuth access token (federated / brokered credentials):
options = CloudOptions(gcs_bearer_token=token)
pipe = Pipeline().source("file_path", cloud_options=options)
dtype_name_for¶
Name a Polars dtype the way source(dtype=) and cast() take it
(pl.Float32 → "f32"), for building a pipeline over a column whose dtype is
only known at run time.
from polars_cv import Pipeline, dtype_name_for
pipe = Pipeline().source("list", dtype=dtype_name_for(df.schema["values"].inner))
IMAGENET_MEAN / IMAGENET_STD¶
The ImageNet per-channel RGB mean [0.485, 0.456, 0.406] and standard
deviation [0.229, 0.224, 0.225], on a [0, 1] scale.
HashAlgorithm¶
Perceptual hash algorithm selection.
from polars_cv import HashAlgorithm
Pipeline().perceptual_hash(algorithm=HashAlgorithm.PERCEPTUAL)
Pipeline().perceptual_hash(algorithm=HashAlgorithm.AVERAGE)
Pipeline().perceptual_hash(algorithm=HashAlgorithm.DIFFERENCE)
Pipeline().perceptual_hash(algorithm=HashAlgorithm.BLOCKHASH)
Threading¶
How the plugin's thread pool and Polars' share the cores is described in Streaming & Scaling.
set_row_parallelism ¶
Turn polars-cv's own row parallelism on or off for this process.
On (the default), a plugin call spreads its rows over polars-cv's thread pool when that pool has idle threads. Off, every call runs its rows on the thread polars called it on, so the only parallelism is polars' own — the streaming engine's morsels, or one thread per call on the in-memory engine. Turn it off when the streaming engine already keeps every core busy, or on hosts where waking the pool's threads costs more than the rows (many hyperthreaded cores and cheap rows). Outputs are identical either way.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
Whether calls may split their rows. Must be a |
required |
Returns:
| Type | Description |
|---|---|
bool
|
The previous setting, so a caller can restore it. |
Examples:
row_parallelism ¶
Whether polars-cv splits a call's rows over its own thread pool.
Optimization¶
OptFlags
dataclass
¶
Bases: _OptFlagFields
Which optimization passes run, one boolean per pass (both tiers).
The fields are generated from the pass catalogue, one per pass. Construct
directly (OptFlags(scalar_fusion=False)), with the :meth:all/:meth:none
shorthands, or from the environment via :meth:from_env. Defaults are
all-on, matching the always-on behaviour that predated the explicit phase.
engine_opt ¶
The engine-tier toggles, keyed by their Rust OptConfig field name.
Serialized as the graph's opt object; the key set equals
:data:ENGINE_PASS_NAMES, which equals the Rust OptConfig fields.
from_env
classmethod
¶
Read the global default from :data:OPT_ENV_VAR.
An unset or blank value means all-on (the omitted-keyword default).
Otherwise the value is parsed by :meth:parse.
parse
classmethod
¶
Parse a flag string into :class:OptFlags.
Grammar (comma-separated, whitespace-insensitive):
all/noneas the first token sets the base; otherwise the base is all-off.- a bare pass name turns that pass on;
-nameturns it off.
Examples: "all", "none", "scalar_fusion" (only that one on),
"all,-scalar_fusion" (all but that one).
An unknown pass name raises ValueError rather than being silently
ignored — an unrecognised flag is a bug, not a no-op.
Build Information¶
build_info ¶
Report the versions of the things that can disagree.
The install is editable, so edits to the Python sources are live — but the
compiled extension is not. After a git pull that touches Rust,
_lib.abi3.so keeps its build-time version until maturin develop is
re-run, and new Python then runs against old Rust. When these three values
disagree, re-run maturin develop.
Returns:
| Type | Description |
|---|---|
dict[str, str | None]
|
Dict with:
- |
The two hashes are what actually detect staleness. The versions cannot: they are the release version, identical until the next bump, so they agree throughout the entire window in which Rust is edited without rebuilding.