Rust-accelerated pixelmatch and SSIM for PNG bytes
Project description
pixelhog
Fast visual regression primitives for Python, implemented in Rust.
pixelhog compares screenshots in two complementary ways:
diff: exact pixel-level differences (with anti-alias handling), optional diff image outputssim: perceptual similarity score in[0.0, 1.0]
It also provides spatial clustering (where on the page did things change), early-exit checks,
and WebP thumbnail generation — all accessible through a stateful Comparison object that
decodes images once and exposes methods on demand.
Install (local dev)
uv venv .venv --python 3.12
source .venv/bin/activate
uv pip install -U pip maturin
maturin develop --release
Quickstart
from pixelhog import Comparison, thumbnail
cmp = Comparison(baseline_png, current_png)
count = cmp.diff_count() # pixel mismatch count
score = cmp.ssim() # perceptual similarity
png = cmp.diff_image() # diff visualization (PNG bytes)
thumb = cmp.current_thumbnail(width=200) # lossless WebP thumbnail
# Spatial clustering — where did things change?
result = cmp.clusters(dilation=8, merge_gap=60)
for cluster in result.clusters:
print(cluster.bbox.x, cluster.bbox.y, cluster.bbox.width, cluster.bbox.height)
# Early exit — fail fast if too many diffs
capped = cmp.diff_count_capped(max_diffs=1000)
# Standalone thumbnail (lossless WebP, Lanczos3 downscale, top-crop)
thumb = thumbnail(current_png, width=200, height=150)
API at a glance
Comparison
| Method | Returns | Notes |
|---|---|---|
Comparison(baseline_png, current_png) |
Comparison |
Decode pair once, call methods on demand |
Comparison.from_rgba(...) |
Comparison |
Pre-decoded RGBA buffers |
Comparison.batch(pairs) |
list[Comparison] |
Parallel decode |
.diff_count(threshold, include_aa) |
int |
Pixel mismatch count |
.diff_count_capped(max_diffs, ...) |
int |
Early-exit count |
.ssim() |
float |
Structural similarity |
.clusters(dilation, merge_gap, ...) |
ClustersResult |
Spatial regions of change |
.diff_image(...) |
bytes (PNG) |
Diff visualization |
.current_thumbnail(width, height, ...) |
bytes (WebP) |
Thumbnail of current image |
.baseline_thumbnail(width, height, ...) |
bytes (WebP) |
Thumbnail of baseline image |
.size_mismatch |
bool |
Whether images had different dimensions |
Utilities and batch
| Function | Input | Output | Use when |
|---|---|---|---|
thumbnail |
PNG bytes | bytes (WebP) |
Single-image thumbnail (no pair needed) |
diff_batch |
list[(baseline, current)] |
list[DiffResult] |
Parallel diff across many pairs |
diff_count_batch |
list[(baseline, current)] |
list[DiffCountResult] |
Parallel count-only |
ssim_batch |
list[(baseline, current)] |
list[float] |
Parallel SSIM |
compare_batch |
list[(baseline, current)] |
list[CompareResult] |
Parallel combined metrics |
Behavior
Comparisondecodes PNG bytes once at construction; methods compute on demand.Comparison.from_rgba(...)accepts pre-decoded RGBA buffers (zero-copy).- Smaller images are padded to the larger dimensions with transparent pixels.
- SSIM uses 11×11 uniform windows with reflect padding; falls back to global for tiny images.
- Clustering uses morphological dilation + two-pass CCL with optional aligned-bbox merge.
Correctness and tests
The test suite is designed to validate both algorithm fidelity and practical product behavior.
- Rust unit/integration tests cover:
- identical/completely different/partial-diff images
- threshold behavior
- different-size padding behavior
- SSIM behavior (identical, slight change, large change, small-image fallback)
- Canonical pixelmatch fixture tests use the official Mapbox test set:
- 8 fixture pairs with exact expected mismatch counts
- expected diff image comparison against golden outputs
- decoded RGBA byte equality checks to ensure pixel-perfect output matching
- Python integration tests cover:
- high-level API contracts and error behavior
- tall-page and subtle-change scenarios
- cross-validation against a pure-Python reference implementation
- pixel diff counts must match exactly
- SSIM must stay within tolerance
Run the full correctness suite:
# Rust core only
cargo test -p pixelhog
# Full suite including Python integration tests
cargo test
uv run --python 3.12 --with maturin --with pytest --with pillow bash -lc \
"maturin develop --release && pytest -q"
Benchmarks
The repo includes both Criterion benches and pipeline breakdown tools.
cargo benchruns Criterion API benchmarks (PNG-bytes entry points).- Breakdown binaries in
examples/measure where time goes:breakdown.rs: decode vs core diff vs encode vs API callssim_breakdown.rs: decode/pad vs core SSIM vs API callcombined_estimate.rs: separate calls vs combined single-decode flow
Run:
cargo bench -p pixelhog
cargo run -p pixelhog --release --example breakdown
cargo run -p pixelhog --release --example ssim_breakdown
cargo run -p pixelhog --release --example combined_estimate
For screenshot-style workloads, the practical guidance is:
diff_countis cheaper thandiffwhen you do not need an artifact.compare(..., return_diff=False)avoids duplicate decode work when you need both diff-count and SSIM.
Development
# Rust tests (includes canonical Mapbox fixture tests)
cargo test
# Python extension + tests
uv run --python 3.12 --with maturin --with pytest --with pillow bash -lc \
"maturin develop --release && pytest -q"
# Lint/format/type-check
uv run --python 3.12 --with ruff ruff format --check .
uv run --python 3.12 --with ruff ruff check .
uv run --python 3.12 --with ty --with pytest --with pillow ty check . --python .venv
License
This repository is MIT licensed. See LICENSE.
Algorithm attribution for pixelmatch is documented in THIRD_PARTY_NOTICES.md.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pixelhog-1.2.0.tar.gz.
File metadata
- Download URL: pixelhog-1.2.0.tar.gz
- Upload date:
- Size: 32.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
919b9c3f90e89307ae1de816d7860cd3fa27a59e5e2da020f02953f30ebb56fd
|
|
| MD5 |
da7fe63be1c8aeaa89a904d8349fa21e
|
|
| BLAKE2b-256 |
1f5e23e3cf43002b8e68e55da4b650b108909e6e38569170fc9c2611ddaf5dcf
|
File details
Details for the file pixelhog-1.2.0-cp312-abi3-win_amd64.whl.
File metadata
- Download URL: pixelhog-1.2.0-cp312-abi3-win_amd64.whl
- Upload date:
- Size: 610.6 kB
- Tags: CPython 3.12+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3de6ddcea4cad183052a20f3d07d38ca23ff587bc7b6ffafdff1f98f914ca06b
|
|
| MD5 |
3df951aaab596c1485c13a6fba43053f
|
|
| BLAKE2b-256 |
09d2c057e7c1a708ec91abd9071a068332adf5d92d064b49537c0fdf9164cfed
|
File details
Details for the file pixelhog-1.2.0-cp312-abi3-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: pixelhog-1.2.0-cp312-abi3-musllinux_1_2_x86_64.whl
- Upload date:
- Size: 916.4 kB
- Tags: CPython 3.12+, musllinux: musl 1.2+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7ac96f4f9ee6c2505eb89412f6532e8cc9719ac730d1c156e30b22be878285cf
|
|
| MD5 |
356df565bfa455c0e1c97b48ac887d97
|
|
| BLAKE2b-256 |
27c0a5b1d61a18fcd060afda879fd95abb9f510d131e585cf477816c1f178ce4
|
File details
Details for the file pixelhog-1.2.0-cp312-abi3-musllinux_1_2_aarch64.whl.
File metadata
- Download URL: pixelhog-1.2.0-cp312-abi3-musllinux_1_2_aarch64.whl
- Upload date:
- Size: 860.0 kB
- Tags: CPython 3.12+, musllinux: musl 1.2+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
05239693d9e5f844af52a082d082a8e191608f379257ce35bf5c197b52231970
|
|
| MD5 |
8be211f546c73393e227a39c77c00a30
|
|
| BLAKE2b-256 |
7307da9179b7f0c9b118b9ae81a9a6d1849a2acbbdc86b9f6450ad05f0bdd8d2
|
File details
Details for the file pixelhog-1.2.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: pixelhog-1.2.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 724.2 kB
- Tags: CPython 3.12+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
365834d17326109c63cf2da5204cb92ce84518798f9a5d08502331fe83dacb4d
|
|
| MD5 |
6eee63cf58a0035952b6ae7748be49f0
|
|
| BLAKE2b-256 |
797ea04a4914a17774929d8fd1e0be0da71c1c310db14a0a37fbc33437a301f4
|
File details
Details for the file pixelhog-1.2.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: pixelhog-1.2.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 683.7 kB
- Tags: CPython 3.12+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2a2d70c505f17295b85a77c60c7fec8cee97a3c95100232cca2f86a5a590a579
|
|
| MD5 |
8c9ea5a24b159903f05bbd05bb4320d6
|
|
| BLAKE2b-256 |
da07d142e38d7dc60a467b97ef8f1a92d61cb1909bb8ffb0e9c27bec55e61bb0
|
File details
Details for the file pixelhog-1.2.0-cp312-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: pixelhog-1.2.0-cp312-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 648.8 kB
- Tags: CPython 3.12+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f0488d22181b166871a149458cefc84d5c6bac74766898a35053dcb7c163f802
|
|
| MD5 |
6e3c1084e3fe255b4fd84337348fcb55
|
|
| BLAKE2b-256 |
1ae09822760802dd572b837fb381fcba8274509d76532b151f0a49ed2ca250e2
|
File details
Details for the file pixelhog-1.2.0-cp312-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: pixelhog-1.2.0-cp312-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 660.8 kB
- Tags: CPython 3.12+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
82b33e6cddc730dd08314aea688d0f3ac50f1f52439c5655378838ce41bf3710
|
|
| MD5 |
ac34dffa097c3edf9c06504f8abfcb8d
|
|
| BLAKE2b-256 |
a13b860c2e4cfe1a26fdfd5c19afb4ba649710a83a9a5d0bfcdd4111068aba3b
|