Skip to content

Commit b870531

Browse files
authored
feat(ssim): add integration (americanexpress#220)
1 parent 0b8ec40 commit b870531

11 files changed

+338
-48
lines changed

README.md

+45-3
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,13 @@ See [the examples](./examples/README.md) for more detailed usage or read about a
100100

101101
`toMatchImageSnapshot()` takes an optional options object with the following properties:
102102

103-
* `customDiffConfig`: Custom config passed to [pixelmatch](https://github.com/mapbox/pixelmatch#pixelmatchimg1-img2-output-width-height-options) (See options section)
104-
* By default we have set the `threshold` to 0.01, you can increase that value by passing a customDiffConfig as demonstrated below.
105-
* Please note the `threshold` set in the `customDiffConfig` is the per pixel sensitivity threshold. For example with a source pixel colour of `#ffffff` (white) and a comparison pixel colour of `#fcfcfc` (really light grey) if you set the threshold to 0 then it would trigger a failure *on that pixel*. However if you were to use say 0.5 then it wouldn't, the colour difference would need to be much more extreme to trigger a failure on that pixel, say `#000000` (black)
103+
* `customDiffConfig`: Custom config passed to [pixelmatch](https://github.com/mapbox/pixelmatch#pixelmatchimg1-img2-output-width-height-options) (See options section) or [ssim.js](https://github.com/obartra/ssim/wiki/Usage#options)
104+
* Pixelmatch specific options
105+
* By default we have set the `threshold` to 0.01, you can increase that value by passing a customDiffConfig as demonstrated below.
106+
* Please note the `threshold` set in the `customDiffConfig` is the per pixel sensitivity threshold. For example with a source pixel colour of `#ffffff` (white) and a comparison pixel colour of `#fcfcfc` (really light grey) if you set the threshold to 0 then it would trigger a failure *on that pixel*. However if you were to use say 0.5 then it wouldn't, the colour difference would need to be much more extreme to trigger a failure on that pixel, say `#000000` (black)
107+
* SSIM specific options
108+
* By default we set `ssim` to 'bezkrovny'. It is the fastest option and best option most of the time. In cases where, higher precision is needed, this can be set to 'fast'. See [SSIM Performance Consideration](#ssim-performance-considerations) for a better understanding of how to use this feature.
109+
* `comparisonMethod`: (default: `pixelmatch`) (options `pixelmatch` or `ssim`) The method by which images are compared. `pixelmatch` does a pixel by pixel comparison, whereas `ssim` does a structural similarity comparison. `ssim` is a new experimental feature for jest-image-snapshot, but may become the default comparison method in the future. For a better understanding of how to use SSIM, see [Recommendations when using SSIM Comparison](#recommendations-when-using-ssim-comparison).
106110
* `customSnapshotsDir`: A custom absolute path of a directory to keep this snapshot in
107111
* `customDiffDir`: A custom absolute path of a directory to keep this diff in
108112
* `customSnapshotIdentifier`: A custom name to give this snapshot. If not provided one is computed automatically. When a function is provided it is called with an object containing `testPath`, `currentTestName`, `counter` and `defaultIdentifier` as its first argument. The function must return an identifier to use for the snapshot.
@@ -156,6 +160,44 @@ expect.extend({ toMatchImageSnapshot });
156160
### jest.retryTimes()
157161
Jest supports [automatic retries on test failures](https://jestjs.io/docs/en/jest-object#jestretrytimes). This can be useful for browser screenshot tests which tend to have more frequent false positives. Note that when using jest.retryTimes you'll have to use a unique customSnapshotIdentifier as that's the only way to reliably identify snapshots.
158162

163+
### Recommendations when using SSIM comparison
164+
Since SSIM calculates differences in structural similarity by building a moving 'window' over an images pixels, it does not particularly benefit from pixel count comparisons, especially when you factor in that it has a lot of floating point arithmetic in javascript. However, SSIM gains two key benefits over pixel by pixel comparison:
165+
- Reduced false positives (failing tests when the images look the same)
166+
- Higher sensitivity to actual changes in the image itself.
167+
168+
Documentation supporting these claims can be found in the many analyses comparing SSIM to Peak Signal to Noise Ratio (PSNR). See [Wang, Z.; Simoncelli, E. P. (September 2008). "Maximum differentiation (MAD) competition: a methodology for comparing computational models of perceptual quantities"](https://ece.uwaterloo.ca/~z70wang/publications/MAD.pdf) and Zhang, L.; Zhang, L.; Mou, X.; Zhang, D. (September 2012). A comprehensive evaluation of full reference image quality assessment algorithms. 2012 19th IEEE International Conference on Image Processing. pp. 1477–1480. [Wikipedia](http://en.wikipedia.org/wiki/Structural_Similarity) also provides many great reference sources describing the topic.
169+
170+
As such, most users can benefit from setting a 1% or 0.01 threshold for any SSIM comparison. The below code shows a one line modification of the 1% threshold example.
171+
172+
```javascript
173+
it('should fail if there is more than a 1% difference (ssim)', () => {
174+
...
175+
expect(image).toMatchImageSnapshot({
176+
comparisonMethod: 'ssim',
177+
failureThreshold: 0.01,
178+
failureThresholdType: 'percent'
179+
});
180+
});
181+
```
182+
### SSIM Performance Considerations
183+
The default SSIM comparison method used in the jest-image-snapshot implementation is 'bezkrovny' (as a `customDiffConfig` `{ssim: 'bezkrovny'}`).
184+
Bezkrovny is a special implementation of SSIM that is optimized for speed at a small, almost inconsequential change in accuracy. It gains this benefit by downsampling (or shrinking the original image) before performing the comparisons.
185+
This will provide the best combination of results and performance most of the time. When the need arises where higher accuracy is desired at the expense of time or a higher quality diff image is needed for debugging,
186+
this option can be changed to `{ssim: 'fast'}`. This uses the original SSIM algorithm described in Wang, et al. 2004 on "Image Quality Assessment: From Error Visibility to Structural Similarity" (https://github.com/obartra/ssim/blob/master/assets/ssim.pdf) optimized for javascript.
187+
188+
The following is an example configuration for using `{ssim: 'fast'}` with toMatchImageSnapshot().
189+
```javascript.
190+
{
191+
comparisonMethod: 'ssim',
192+
customDiffConfig: {
193+
ssim: 'fast',
194+
},
195+
failureThreshold: 0.01,
196+
failureThresholdType: 'percent'
197+
}
198+
```
199+
200+
159201
### Recipes
160202

161203
#### Upload diff images from failed tests

__tests__/__snapshots__/index.spec.js.snap

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ exports[`toMatchImageSnapshot passes diffImageToSnapshot everything it needs to
1616
Object {
1717
"allowSizeMismatch": false,
1818
"blur": 0,
19+
"comparisonMethod": "pixelmatch",
1920
"customDiffConfig": Object {},
2021
"diffDir": "path/to/__image_snapshots__/__diff_output__",
2122
"diffDirection": "horizontal",

__tests__/index.spec.js

+4
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,7 @@ describe('toMatchImageSnapshot', () => {
442442
const customDiffConfig = { perceptual: true };
443443
const customSnapshotIdentifier = ({ defaultIdentifier }) =>
444444
`custom-${defaultIdentifier}`;
445+
const comparisonMethod = 'ssim';
445446
const toMatchImageSnapshot = configureToMatchImageSnapshot({
446447
customDiffConfig,
447448
customSnapshotIdentifier,
@@ -453,6 +454,7 @@ describe('toMatchImageSnapshot', () => {
453454
failureThresholdType: 'percent',
454455
updatePassedSnapshot: true,
455456
blur: 1,
457+
comparisonMethod,
456458
});
457459
expect.extend({ toMatchImageSnapshot });
458460
const matcherAtTest = toMatchImageSnapshot.bind(mockTestContext);
@@ -473,6 +475,7 @@ describe('toMatchImageSnapshot', () => {
473475
updatePassedSnapshot: true,
474476
failureThreshold: 1,
475477
failureThresholdType: 'percent',
478+
comparisonMethod,
476479
});
477480
expect(Chalk).toHaveBeenCalledWith({
478481
enabled: false,
@@ -530,6 +533,7 @@ describe('toMatchImageSnapshot', () => {
530533
updatePassedSnapshot: false,
531534
failureThreshold: 0,
532535
failureThresholdType: 'pixel',
536+
comparisonMethod: 'pixelmatch',
533537
});
534538
expect(Chalk).toHaveBeenCalledWith({
535539
enabled: false,

0 commit comments

Comments
 (0)