blazediff-interpret
Structured region analysis for image diffs. Given two images and a set of changed regions, it says what changed in each one β not just where.
Installation
# Cargo.toml
[dependencies]
blazediff-interpret = "5.4.0"The crate name is blazediff-interpret; the library imports as blazediff_interpret.
Why itβs a separate crate
The classifier is deliberately independent of whatever found the regions. Three producers feed it:
| Producer | ChangeSource | How it finds regions |
|---|---|---|
blazediff | Diff | connected components over a pixel-diff mask |
blazediff-ssim | ScoreMap | thresholding a local SSIM score map |
| your code | Regions | DOM rectangles, a JS-side diff, a crop list β anything |
All three call the same function and get identical treatment; only the description of where differs. blazediff and blazediff-ssim are independent of each other, so a classifier living in either would be unreachable from the other. It sits below both, on blazediff-shared.
Usage
use blazediff_interpret::{interpret, ChangeSource};
// From a pixel diff β what `blazediff` does.
let result = interpret(&expected, &actual, ChangeSource::Diff {
output: &diff_image.data,
diff_count: diff.diff_count,
diff_percentage: diff.diff_percentage,
})?;
// From a similarity map β what `blazediff-ssim` does.
let result = interpret(&expected, &actual, ChangeSource::ScoreMap {
map: &outcome.map,
width: outcome.map_width,
height: outcome.map_height,
floor: 0.99,
})?;
// From boxes you already have.
let result = interpret(&expected, &actual, ChangeSource::Regions(&boxes))?;
println!("{}", result.summary);
for region in &result.regions {
println!("{:?} at {} ({:.2}%)", region.change_type, region.position, region.percentage);
}Coarse regions are fine
A producer only has to know roughly where something changed. Before any statistic is computed, each supplied box is refined against the source pixels β every pixel whose YIQ delta falls below the noise floor is dropped β so shape, colour and gradient analysis stay per-pixel no matter how blocky the input was:
// An 8x8 change, described exactly and then quantized to a 16px grid.
let exact = interpret(&a, &b, ChangeSource::Regions(&[BoundingBox { x: 16, y: 16, width: 8, height: 8 }]))?;
let coarse = interpret(&a, &b, ChangeSource::Regions(&[BoundingBox { x: 16, y: 16, width: 16, height: 16 }]))?;
assert_eq!(coarse.diff_count, exact.diff_count); // both 64That is what makes an SSIM window map a usable region source: its grid is coarse, but the statistics derived from it are not.
diff_count therefore means the same thing on every path β actually-changed pixels, never windows. On a real fixture pair, SSIM-located regions report 792 changed pixels where the exact pixel diff reports 776.
API
| Item | Purpose |
|---|---|
interpret | the entry point: a ChangeSource in, a full InterpretResult out |
ChangeSource | Diff (a pixel diffβs output + counts), ScoreMap (a similarity map), or Regions |
regions_from_score_map | threshold a lower-resolution score map into image-space regions |
classify_region / classify_regions | classify against a mask you already hold |
detect_regions | connected components over a boolean mask |
extract_change_mask | recover a mask from an RGBA diff visualization |
detect_shifts | the shift-relabeling pass, for producers holding an exact mask |
classify_severity, build_summary | the pooling steps, for custom pipelines |
Regions arriving from a caller are validated: a box outside the image is an InterpretError::RegionOutOfBounds, not an out-of-bounds panic. That matters now that regions cross the wasm and N-API boundaries.
From JavaScript
One package wraps all of it β @blazediff/interpret-native:
import { interpret, interpretRegions } from "@blazediff/interpret-native";
// Regions from a pixel diff (default), or from a similarity map.
const exact = await interpret("expected.png", "actual.png");
const loose = await interpret("expected.png", "actual.png", undefined, { source: "ms-ssim" });
// Regions you already know about.
const given = await interpretRegions("expected.png", "actual.png", [
{ x: 16, y: 16, width: 32, height: 32 },
]);What it classifies
Each region gets a change type, a shape, a position, a confidence, and the statistics behind them β colour delta, gradient/edge correlation, fill ratios, and the signals the classifier used. See INTERPRET.mdΒ for the full algorithm.