Skip to main content

imagefp

Decide whether two images are the same picture — even when they were re-encoded, alpha-flattened onto a background, cropped/padded differently, or resized — without ever falsely claiming a match it isn't sure of.

Built for pipelines that re-save or re-export documents (PowerPoint, Word, Excel, PDF) where the same source image commonly reappears in a different container format, with different compression, or with its transparency flattened away. Generic perceptual-hash libraries don't handle the alpha-flattening or EMF-metadata-churn cases; this one does.

Install

pip install imagefp

Raster comparison (.png, .jpg, .gif, .bmp, .tiff, .webp, ...) requires Pillow, which is installed automatically as a dependency. EMF/WMF metafile comparison has no extra dependency.

Quick start

from imagefp import images_match

with open("logo.png", "rb") as f:
    data_a = f.read()
with open("logo_export.jpg", "rb") as f:
    data_b = f.read()

match, reason = images_match(data_a, "logo.png", data_b, "logo_export.jpg")
print(match, reason)
# True "same picture -- shape 0/30, colour 3/18, aspect 0.000/0.11"

images_match is the one function most callers need. It takes the raw bytes and the original file/part name (so it knows whether it's looking at a raster or a vector metafile) for each image, and returns:

(match: bool, reason: str)

reason is always populated, win or lose, so you can log why two images were or weren't considered the same.

How it decides "same picture"

  1. Classify each file as raster (.png, .jpg, .webp, ...) or metafile (.emf, .wmf) by extension.
  2. Rasters are compared with a perceptual fingerprint:
    • Crop to actual content (ignore blank padding/margins).
    • Composite any transparency onto white, the same way PowerPoint does when it re-saves an image — so a transparent logo and its flattened export land in the same colour space.
    • Downsample to a 16×16 grid (colour thumbnail + a separate "ink mask" of where there's real content vs. background).
    • Compare in three gates, cheapest first: aspect ratio, then ink-mask shape, then colour — measured only over cells where both images actually have ink, so background/padding differences can't skew it.
  3. Metafiles (EMF) are compared by hashing their drawing records, deliberately skipping the header (which changes on resize) and comment records (where per-export metadata/timestamps live) — so the same chart re-exported a week later still matches, while an actual edit does not.
  4. Anything uncertain returns "different." Undecodable bytes, a raster-vs-metafile mismatch, missing bytes, or an unparsable metafile — all of these are reported as not a match, never as an uncertain match.

What it does not do

  • It cannot detect a match after a resize that rewrites an EMF's actual drawing coordinates (as opposed to just its header bounds) — that would require rendering the metafile to pixels, which is out of scope.
  • .wmf files are legacy 16-bit metafiles with a different record format and are currently always reported as unreadable (emf_signature returns None for them). If you need WMF support, open an issue.
  • This is a "is this the same picture" tool, not a general reverse-image search or similarity ranker — it returns a boolean, not a similarity score for ranking many candidates (though the reason string exposes the underlying distances if you want to build that yourself).

API reference

Function Purpose
images_match(data_a, name_a, data_b, name_b, *, shape=30.0, thumb=18.0, aspect=0.11) The main entry point. Returns (match, reason).
kind(name) "raster", "metafile", or "other", by file extension.
describe(data) Build a Descriptor (thumbnail + ink mask + aspect) for one raster image.
same_image(a, b, ...) Run the three-gate comparison on two Descriptors directly.
emf_signature(data) SHA-256-derived signature of an EMF's drawing records, or None.
thumb_distance, shape_distance, colour_distance The individual distance metrics, if you want to tune thresholds yourself.

All public names are also importable directly from the imagefp package, e.g. from imagefp import Descriptor, kind, emf_signature.

Tuning thresholds

The three gates each take a threshold, exposed as keyword arguments on images_match:

images_match(data_a, name_a, data_b, name_b,
             shape=30.0,   # max ink-mask distance
             thumb=18.0,   # max masked colour distance
             aspect=0.11)  # max |log(aspect ratio difference)|

Lower values are stricter (fewer false positives, more false negatives); higher values are looser. The defaults were tuned for documents re-exported by PowerPoint/Office tooling — you may want to widen thumb slightly for very lossy JPEG re-compression, or tighten aspect if your corpus has many same-content images at deliberately different aspect ratios (e.g. a banner vs. a thumbnail of unrelated art that happens to share a palette).

Development

git clone https://github.com/tejanshsachdeva/imagefp.git
cd imagefp
pip install -e ".[dev]"
pytest

License

MIT — see LICENSE.

Release files for imagefp 0.5.0

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

Source distribution (sdist)

Source distribution for imagefp 0.5.0
File Size Uploaded
imagefp-0.5.0.tar.gz 13.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for imagefp 0.5.0
File Interpreter ABI Platform
imagefp-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 22.9 kB

Release files / imagefp-0.5.0.tar.gz

Download URL imagefp-0.5.0.tar.gz
Size 13.2 kB
Tags Source
SHA-256 checksum
How to use checksums
0b964d4d9d812c69b605d61715159154b748bc84a2ebddcf0ab4ab77398ca244
BLAKE2b-256 checksum
How to use checksums
ab75cb73811523205cdc31e8565d322b6b515517219d96b0d9d964849bc80bb5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.

Transparency log

Release files / imagefp-0.5.0-py3-none-any.whl

Download URL imagefp-0.5.0-py3-none-any.whl
Size 9.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
13591445bcdf46e02850c344d6f657eecd5fd3ff5afaef615ccb16b6300bcdb7
BLAKE2b-256 checksum
How to use checksums
e2da19a353b7d5285f397982a7f4a03b116cfaada1fe421cf6c960ffa5239223
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.

Transparency log

Release history Release notifications | RSS feed

0.6.0

2 release files

This release

0.5.0 This release

2 release files

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