In practice6 min read

Comparing two moments

How a pixel is judged changed, what the bounding box describes, and why a difference is not an error.

Comparison runs on raw frames. The diff command decodes its inputs at the boundary because files are what a human hands it, but the engine itself never sees a PNG or a JPEG. That is what lets the same primitive run on live captures at high frequency.

bash
eensh diff before.png after.png --json
response
{
  "comparison": {
    "mode": "exact", "pixel_threshold": 0, "area_threshold": 0.0,
    "changed": true,
    "changed_pixels": 5000,
    "total_pixels": 480000,
    "changed_fraction": 0.010416666666666666,
    "bounding_box": { "x": 200, "y": 150, "width": 100, "height": 50 }
  }
}

How a pixel is judged changed, stated exactly

These boundaries are specified precisely because a threshold whose edge is unspecified is worse than no threshold at all.

the definition
difference = max(|r1 - r2|, |g1 - g2|, |b1 - b2|)

pixel changed  iff  difference > pixel_threshold              (strict)
frame changed  iff  changed_pixels > 0
                    and changed_fraction >= area_threshold    (inclusive)

Note the asymmetry, which is deliberate: the pixel comparison is strict (greater than), while the area comparison is inclusive (greater than or equal). A threshold of 12 means a difference of 12 counts as unchanged, and a fraction exactly equal to the area threshold counts as changed.

Two separate answers: where pixels differ, and whether that counts

This is the distinction most likely to be missed, and it is the reason the changed crop can surprise you.

bounding_boxobject | null

The factual location of pixels above the pixel threshold. A statement about what is there.

changedboolean

A policy judgement, after the area threshold. An answer to “is this worth acting on”.

It is entirely normal to have a non-empty bounding box with changed: false:

a real, non-broken result
{
  "changed": false,
  "changed_pixels": 1,
  "total_pixels": 480000,
  "changed_fraction": 0.0000020833,
  "bounding_box": { "x": 10, "y": 10, "width": 1, "height": 1 }
}

One pixel moved. That is below any sensible area threshold, so the frame is not meaningfully changed — but a pixel did differ, and the box says where. Throwing that away would lose information the caller might want.

The crop is written whenever pixels differ, even if the frame is not "changed"

diff --changed-crop PATH writes a crop whenever a changed region was located — that is, whenever any pixel exceeded the pixel threshold. The area threshold is not consulted, because it expresses a policy judgement while the bounding box is a factual statement. The JSON still reports changed: false.

When nothing changed at all, no file is written. A placeholder image would be worse than an absent one, because a caller checking for the file would see a success where there was nothing to see.

Every option, with its default

OptionDefaultMeaning
--modeexactexact counts any channel difference; rgb honours --pixel-threshold.
--pixel-threshold0Largest per-channel difference ignored, 0–255.
--area-threshold0.0Smallest changed fraction considered meaningful, 0.0–1.0.
--changed-crop—Write a crop of the changed region, taken from the second image.

The defaults here are stricter than the observation defaults on purpose. diff asks “did anything differ?” and starts with no tolerance at all; wait-change asks “did anything meaningfully change?” and starts with a pixel threshold of 12.

A visual difference is not an error

A successful comparison exits 0 whether or not the images differ. The JSON carries the authoritative answer. Branching on the exit status of diff will conclude that every scene containing a change is broken.

Next: paying only for the pixels you need — regions and ROI.