Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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 metafile comparison has no extra dependency (WMF is not supported yet).

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:
    • Apply EXIF orientation, then crop to actual content (ignore blank padding/margins; ink is detected from alpha or from a flat border colour).
    • 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 only) are compared by hashing drawing records, skipping the header (resize bounds), skipping non-EMF+ metadata comments, but including EMF+ comment payloads where Office stores GDI+ drawing data — so the same chart re-exported with new timestamps still matches, while an edit to the drawing (including EMF+ content) does not. The file must be a complete EMF (through EMR_EOF); truncated files are rejected.
  4. Anything uncertain returns "different." Undecodable bytes, a raster-vs-metafile mismatch, missing bytes, unsupported WMF, or an incomplete/corrupt EMF — all 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 and are not supported yet (images_match returns an explicit “WMF is not supported” reason). Only .emf metafiles are compared. Open an issue if you need WMF.
  • 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.

About

No description or website provided.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages