Skip to main content

rosetta_squint_hash — Python port (extensions over imagehash)

A thin wrapper around the Python imagehash package (4.3.2+) that adds the cross-port-stable extensions implemented across the 5 ports of rosetta-squint-hash (Rust, Go, Java, JS/TS, Swift).

The v1 surface that's actually different from upstream imagehash is one function: whash_db4_robust. Everything else (phash, dhash, whash(mode='haar'), whash(mode='db4'), phash_simple, dhash_vertical, colorhash, crop_resistant_hash, hex round-trip) is re-exported from upstream unchanged.

Why this package exists

Python imagehash.whash(mode='db4') and our 5 ports' whashDb4 produce the same hash for ~93% of test fixtures. The other ~7% are pathological synthetic inputs (checkerboards, high-contrast geometric icons) where the algorithm computes a wavelet LL band whose values are mathematically exactly 0 — but float64 rounding produces tiny noise (~1e-17). The "is coef > median" comparison then depends on which side of zero the noise lands, which depends on the order of float additions, which depends on SIMD/FMA and compiler.

PyWavelets' C+SIMD inner loop, Rust's f64 accumulation, Java's double loops, Go's column-first traversal, JS's Number arithmetic, and Swift's Double accumulation can disagree by ~10 bits out of 256 on those inputs. Every implementation is "right" per IEEE 754 — the input is at a tie point.

whash_db4_robust resolves this by snapping |coef| < 1e-12 to exactly 0 before the median + threshold step. All implementations now agree (cross-port stable) at the cost of those specific inputs producing different hex than imagehash.whash(mode='db4').

For real photos this never triggers; both functions return identical output.

Install

pip install -e .              # from python/

Not yet on PyPI.

Version policy

The wrapper pins upstream dependencies to the exact versions our goldens were validated against:

Dep Pinned to Why
imagehash ==4.3.2 Algorithm output is what generates spec/goldens.json. New upstream release → potential drift → release of rosetta-squint-hash after re-validation.
Pillow ==10.4.* PIL's Lanczos resize, Image.crop rounding, ImageFilter.GaussianBlur box-radius formula, and grayscale conversion are all involved in our hash pipeline. A Pillow major-version bump can change any of these and silently break parity with the 5 other ports.
PyWavelets >=1.5,<2.0 db4 filter coefficients are mathematical constants; less drift risk, but bound the major version.
numpy >=1.26,<2.0 NumPy 2.0 changed some default dtypes and behaviors; pin to 1.x.

This is intentional. The upstream-tracker workflow (.github/workflows/upstream-tracker.yml) catches new upstream releases weekly and surfaces the goldens diff so a human can decide whether to bump and re-release.

If you need to share a Python environment with packages requiring different Pillow / imagehash versions, install with the unpinned extra:

pip install rosetta-squint-hash[unpinned]

Caveat: with [unpinned] the cross-port byte-exact guarantee is no longer enforced by the wrapper — verify your own output matches spec/goldens.json before relying on it. The 5 non-Python ports are unaffected (they don't link to Pillow at all).

Usage

import rosetta_squint_hash as rih
from PIL import Image

img = Image.open("photo.jpg")

# Drop-in upstream imagehash:
h_strict = rih.whash(img, mode="db4")            # matches imagehash exactly

# Our cross-port-stable variant:
h_robust = rih.whash_db4_robust(img, hash_size=8)

print(str(h_strict))                              # hex, identical to imagehash.whash output
print(str(h_robust))                              # hex, identical to Rust/Go/Java/JS/Swift output

# On a real photo, the two agree:
assert str(h_strict) == str(h_robust)

# On a checkerboard, they will differ:
checker = Image.open("spec/fixtures/checker-256.png")
print(rih.whash(checker, mode="db4"))             # SIMD-dependent
print(rih.whash_db4_robust(checker, hash_size=8)) # same on every platform

When to pick which

Use case Function
Matching against existing hashes stored from Python imagehash rih.whash(img, mode="db4")
Cross-language storage/lookup where every port (Python, Rust, Go, Java, JS, Swift) must produce the same hex for the same input rih.whash_db4_robust(img)
Hashing photographs (untrusted or trusted) Either; output is identical for non-pathological inputs
Hashing synthetic test inputs or geometric patterns rih.whash_db4_robust if cross-port matters

API

def whash_db4_robust(
    image: PIL.Image.Image,
    hash_size: int = 8,
    image_scale: int | None = None,
) -> imagehash.ImageHash:
    """Cross-port-stable variant of imagehash.whash(mode='db4').

    See docstring in rosetta_squint_hash/_impl.py for the full algorithm.
    """

WHASH_DB4_ROBUST_EPS: float = 1e-12  # the ε threshold; fixed across ports

All other names re-exported from imagehash are accessible as rosetta_squint_hash.<name>.

Testing

cd python
pip install -e ".[test]"
pytest

Tests verify whash_db4_robust output against spec/goldens.json — the same goldens the 5 non-Python ports test against. The Python reference produces the goldens, so this is a self-consistency check; the value comes from confirming the per-port implementations match.

License

BSD-2-Clause.

Metadata

Release files for rosetta-squint-hash 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rosetta-squint-hash 1.0.1
File Size Uploaded Provenance
rosetta_squint_hash-1.0.1.tar.gz 15.9 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for rosetta-squint-hash 1.0.1
File Interpreter ABI Platform
rosetta_squint_hash-1.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 26.8 kB

Release files / rosetta_squint_hash-1.0.1.tar.gz

Download URL rosetta_squint_hash-1.0.1.tar.gz
Size 15.9 kB
Tags Source
SHA-256 checksum
How to use checksums
2bd8237bf87730d39102fb05a0644cada5be3843d804ec290325ad39b4e28cd9
BLAKE2b-256 checksum
How to use checksums
20757a2aa8043b093d5f26b67306f40f51f5b9bfeca879524662c1c777559853
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Release files / rosetta_squint_hash-1.0.1-py3-none-any.whl

Download URL rosetta_squint_hash-1.0.1-py3-none-any.whl
Size 10.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
49eb53c088ef1340f97675f8b0df83726324a5589572ecb30a21050486d1baf9
BLAKE2b-256 checksum
How to use checksums
ee051b98db146ad480e2127798e416ba4d1c4533c15decc6f368d36d51cebde0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Security for rosetta-squint-hash 1.0.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page