Skip to main content

ImDiff: Compare Directories of Images

ImDiff compares either two image files or two directory trees that contain image files with matching relative paths. It is intended for workflows where generated images need visual review as part of test development, golden-file updates, or artifact triage.

The project exposes the same comparison engine through two interfaces:

  • python -m imdiff LEFT RIGHT opens a Tk-based review window when the GUI stack is available, or warns and falls back to summary mode when it is not.
  • python -m imdiff --summary LEFT RIGHT prints a non-interactive summary and exits with a non-zero status when differences are found without importing any GUI-only modules.

When both arguments are directories, ImDiff walks the union of both directory trees, pairs files by relative path, and classifies each entry as:

  • identical: byte-identical files or text files with no unified diff output
  • similar: images whose normalized RMSE is below the project threshold
  • different: images or text files whose contents differ materially
  • different-size: image pairs with different dimensions
  • missing or new: a file exists on only one side
  • failed-to-load: a file exists but Pillow could not decode it as an image

The interactive directory view combines a file tree, image panes, and file operations so a reviewer can inspect left, right, and diff images, switch zoom policies, and copy or delete files directly from the comparison session.

This utility requires that the Tk Python packages be installed on the system. Hint for those running Ubuntu:

apt install python3-pil python3-pil.imagetk python3-tk

The application also depends on numpy and ttkbootstrap for image math and themed Tk widgets.

Usage

Compare two image files:

python -m imdiff path/to/expected.png path/to/actual.png

Compare two directories and print a CI-friendly summary:

python -m imdiff --summary tests/baseline-images tests/generated-images

By default the normalized RMSE is measured over the whole image, which tolerates the many tiny GPU/driver rendering variations that show up across large images. To instead catch small but concentrated differences, select a sliding-window size with --window; the reported score becomes the worst-scoring window of that size:

python -m imdiff --window medium tests/baseline-images tests/generated-images

The available window sizes are global (the default, whole image), small (32x32), medium (64x64), large (128x128), and xlarge (256x256). You can also pass an explicit pixel size as WIDTH,HEIGHT, for example --window 1024,1024, when none of the presets fit. A window larger than the image is clamped to the image size. The flag applies to both the interactive windows and --summary mode.

When the GUI cannot be imported or Tk cannot start, the default interactive command warns on stderr and continues in the same summary mode used by --summary. This makes headless CI environments safe without changing the CLI command line.

Programmatic comparison

The same comparison core is available to other Python code, such as test harnesses that compare a captured screenshot against a stored reference:

from imdiff import ImageComparator, RmseWindow

comparator = ImageComparator(reference_png, actual_png, RmseWindow.Medium)

status = comparator.diff_info  # 'identical', 'missing', 'different-size', ...
if isinstance(status, float):  # a same-size pair: status is the normalized RMSE
    assert status <= 0.03, f'normalized RMSE {status:g} exceeded threshold'

comparator.diff                # grayscale difference image (PIL.Image)
comparator.high_contrast_diff  # histogram-stretched difference image

diff_info returns a symbolic status string for presence, size, and load problems, or the normalized RMSE (range [0, 1]) when both images decode at the same size. The metric honors the window passed to the constructor: Global scores the whole image, while any other RmseWindow reports the largest score found across every window position of that size. The window may also be an explicit (width, height) tuple instead of a preset. A window larger than the image is clamped to the image size. Decoded images and derived results are cached on first use and recomputed after copy_left_to_right, copy_right_to_left, delete_files, or an explicit clear.

The normalized_rmse(left_data, right_data, window) function is also exported for callers that already hold equally sized RGBA arrays.

Repository Layout

  • imdiff/image_comparator.py is the comparison core: the RmseWindow sizes, the normalized_rmse metric, and the lazy ImageComparator model shared by the CLI and GUI.
  • imdiff/list_files.py pairs files across directory trees.
  • imdiff/cli/ contains the command-line entry points and summary printing.
  • imdiff/gui/ contains the Tk windows, canvases, menus, and directory browser.
  • developing/architecture.md describes the component relationships in more detail.

Metadata

Release files for imdiff 2.1.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 imdiff 2.1.0
File Size Uploaded
imdiff-2.1.0.tar.gz 53.6 kB Details

Built distribution (wheel)

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

Total release size: 105.4 kB

Release files / imdiff-2.1.0.tar.gz

Download URL imdiff-2.1.0.tar.gz
Size 53.6 kB
Tags Source
SHA-256 checksum
How to use checksums
bbf0b96c1abb48891f38ba01a1f098a16a970740dbdda4f5b8169370c4bcc7d2
BLAKE2b-256 checksum
How to use checksums
22388a915d8c241bace6850267e19ce24690a7f1d062256b2b92fb1287be241d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.12

Release files / imdiff-2.1.0-py3-none-any.whl

Download URL imdiff-2.1.0-py3-none-any.whl
Size 51.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6fa4b4ae9a5ef0e2b881ff1282a232217fd7f0383f3de9603f066c83b78d4dfe
BLAKE2b-256 checksum
How to use checksums
1092a33e662a62b6d143756005d1b073badc180c76247875d13ea2480e37f845
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.12

Release history Release notifications | RSS feed

This release

2.1.0 This release

2 release files

2.0.0

2 release files

0.4.6

2 release files

0.4.5

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.8

2 release files

0.2.7

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.0

2 release files

0.1.0

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