Perceptual Hashing for Map Tile Comparison

Perceptual hashing is the most over-recommended technique in visual regression and one of the most useful once its role is correct. It reduces an image to a short fingerprint whose distance from another fingerprint tracks whether the two are broadly the same picture, and it does so in microseconds. On a map frame that property is genuinely valuable — and it is also, precisely, a description of what a map test does not care about, because every fault worth catching lives in detail the hash is designed to throw away. This page is about using it for what it is good at.

This is a task within Diff Algorithm Tuning for Cartography, under Web Map Visual Testing Fundamentals & Toolchains. It sits in the layered comparison order that page describes, above byte equality and below the pixel count.

Prerequisites

Where a hash sits among the other measures

Where a perceptual hash sits relative to the other comparison measures Four comparison measures are placed on a scale from cheapest and coarsest to most expensive and most sensitive. Byte equality is free and answers only whether anything changed at all. A perceptual hash is very cheap, reduces each image to a fixed fingerprint, and answers whether the images are broadly the same picture — which makes it a fast pre-filter rather than a gate. The changed-pixel count is moderately priced and localises change. Windowed SSIM is the most expensive and detects structural drift a pixel count misses. A caption states the role the hash actually earns: rejecting the obviously identical and flagging the obviously different before the expensive measures run. A hash is a pre-filter, not a verdict byte equality free · did anything change at all resolves most captures outright on a deterministic pipeline perceptual hash microseconds · is it the same picture fixed-length fingerprint, compared by Hamming distance changed-pixel count one pass · where did it change localises the change and sizes the largest cluster windowed SSIM expensive · structural drift a count misses

The layered order matters as much as the individual measures. On a deterministic pipeline the great majority of captures are byte-identical to their baseline, so the first line resolves them for free. The hash then costs microseconds on what remains and can settle the extreme cases at both ends. Only what survives both reaches the passes that cost real time.

What the hash keeps and what it throws away

What a 64-bit hash keeps and what it discards on a map frame A map frame is reduced to a 64-bit fingerprint through three steps, with what survives each step noted. Downscaling to an eight by eight grid keeps the broad arrangement of light and dark areas — the shape of the coastline, the position of a large park, the overall balance of the frame — and discards everything smaller than a twentieth of the frame. A discrete cosine transform keeps low-frequency structure and discards high-frequency detail, which is where every label, road casing and anti-aliased edge lives. Thresholding against the median produces one bit per cell. A caption states the consequence directly: a dropped label changes no bits, so a hash can never be the gate on a cartographic frame. Everything a map test cares about lives in what the hash discards 1 · downscale to 8×8 keeps: broad light/dark arrangement discards: anything under a twentieth of the frame 2 · DCT, low frequencies keeps: large-scale structure discards: labels, casings, every anti-aliased edge 3 · threshold at the median keeps: one bit per cell, 64 total discards: all remaining magnitude information a dropped label changes zero bits — which settles what a hash can be used for

Follow a specific fault through the pipeline to see why this is decisive. A dropped street label occupies perhaps eighty by twelve pixels in a 1280 by 800 frame — about a tenth of one percent of the area. Downscaled to an eight by eight grid, it contributes a fraction of the average brightness of one cell. After the DCT keeps only low frequencies, it contributes nothing measurable. The resulting fingerprint is bit-identical to the baseline’s.

That is not a tuning problem. Increasing the hash size to 256 bits shifts the boundary a little and does not change the character: the technique’s entire purpose is invariance to exactly this class of small, local change, which is what makes it robust for finding a re-encoded photograph and useless for finding a missing label.

The three jobs worth using it for

The three jobs a hash does well on a map suite Three uses are listed with what each replaces. As a cache key, the hash lets a run skip the expensive comparison entirely when the candidate matches the stored fingerprint, replacing a full pixel pass with a lookup. As a catastrophe detector, a large Hamming distance identifies a blank frame, a wrong camera or a failed style load before anyone looks at a diff, replacing a confusing threshold failure with a clear one. As a deduplication key across a matrix, identical fingerprints identify captures that are the same picture on several engines, replacing a manual audit of which cells could be collapsed. A caption notes that none of the three is a verdict on cartographic correctness. Three jobs it does well, none of them the gate cache key skip the pixel pass when the fingerprint matches the stored one catastrophe detector a blank frame, a wrong camera or a failed style, named clearly matrix deduplication key which cells are the same picture, and so candidates to collapse

As a cache key. Store the fingerprint alongside each baseline. When a candidate’s fingerprint matches, the comparison can still run — but the result is overwhelmingly likely to be a pass, so this is a useful way to order work: hash everything first, then run the expensive comparison on the non-matching set first, so a failing run reports its failures early rather than after twenty minutes of passes.

const phash = computeHash(candidate);
const suspicious = phash !== baseline.phash;   // process these first

As a catastrophe detector. A Hamming distance above roughly a quarter of the bit length means the two images are not the same picture at all. On a map suite that almost always means one of four things: the frame is blank because the canvas was captured before anything drew, the camera is at the wrong place because a fixture did not apply, the style failed to load, or the capture caught an error page. Reporting that distinctly — “capture does not resemble its baseline” rather than “changed-pixel ratio 62%” — sends the investigation to the right place immediately.

if (hamming(phash, baseline.phash) > 16) {
  throw new Error('capture does not resemble its baseline — check camera, style and readiness');
}

As a matrix deduplication key. Captures of the same scenario on different engines that share a fingerprint are candidates for the cell collapse described in Maintaining Per-Engine Baselines for Leaflet and OpenLayers. The hash cannot confirm the collapse — that needs byte equality over repeated runs — but it can find the candidates cheaply across a large matrix, which is otherwise a tedious audit.

Verification

Confirm the procedure worked before wiring it into a blocking gate:

Troubleshooting

Symptom Likely cause Fix
The hash flags captures that look identical The frame contains a large low-frequency element that moved slightly — commonly a hillshade or a full-frame gradient Raise the catastrophe threshold; the hash is doing its job, and this is not the measure to make sensitive
The hash never flags anything, including a blank frame The fingerprint is being computed over the wrong region, often the whole page rather than the map canvas Compute it over the same region the comparison uses, or the two measures are describing different images
Two engines produce different fingerprints for an apparently identical raster scene One of them is applying a colour profile the other is not, which shifts every cell’s brightness Force sRGB in both environments before treating the difference as meaningful

Frequently asked questions

Should the hash ever fail a build on its own?

Only for the catastrophe case, and it is worth doing there because the failure it produces is much clearer than the one the pixel measures produce for the same situation. A blank capture reported as “does not resemble its baseline” saves the reviewer from opening a diff that is entirely magenta. For anything short of that, the hash should inform the ordering and the reporting, never the verdict.

Which hash variant is best for map frames?

The DCT-based variant is the usual choice and its behaviour on map frames is the analysis above. The average-hash variant is slightly cheaper and slightly more sensitive to overall brightness, which on a map means it reacts to a basemap theme change more readily — occasionally useful. Neither becomes suitable as a gate, so the choice between them is minor.

Does hashing help with the tile-level comparison rather than the frame?

More than at the frame level, because a single 256-pixel tile is small enough that a dropped label occupies a meaningful fraction of it. Even there the hash is a filter rather than a verdict — but a tile-level fingerprint index is a genuinely efficient way to find which tiles in a large pyramid changed between two builds of a basemap, which is a different problem from testing an application.

How does it interact with the layered comparison order?

It slots between byte equality and the changed-pixel count, as shown above. The important discipline is that a hash match never short-circuits the pixel pass on a capture that is being gated — it only reorders work and detects catastrophes. A team that uses a hash match to skip comparison entirely has replaced their gate with a much weaker one and will not notice for months.