SSIMULACRA2
37c3317fd058; modified for Encode Wiki · CC BY-SA 4.0SSIMULACRA2 (Structural SIMilarity Unveiling Local And Compression Related Artifacts) is a full-reference image metric by Jon Sneyers at Cloudinary, released in 2022 and updated to version 2.1 in 2023. It extends multi-scale SSIM with a perceptual colour space and separate penalties for added artifacts and lost detail, and it was tuned against large sets of viewer ratings.
It is the community’s main metric for comparing encoders and settings. It correlates with viewer scores better than PSNR, SSIM or VMAF on every dataset in its README, and it penalises both blur and ringing, so smoothing an encode doesn’t buy points. For the very-high-fidelity end, many people also check Butteraugli.
SSIMULACRA2 is an image metric: on video it scores each frame independently and knows nothing about motion. Pair it with XPSNR or CVVDP if temporal artifacts matter, and watch the result.
Score scale
Section titled “Score scale”Scores run from 100 (mathematically lossless) down without a lower limit; higher is better. The upstream README relates them to subjective quality like this:
| Score | Upstream description | Typical output of |
|---|---|---|
| 100 | Mathematically lossless | |
| 90 | Visually lossless: not noticeable by an average observer in a flicker test at 1:1 from a normal viewing distance | cjxl -d 0.5, libjpeg-turbo 4:4:4 q95 |
| 85 | Excellent: not noticeable in an in-place comparison at 1:1 | cjxl -d 1, libjpeg-turbo 4:4:4 q90 |
| 80 | Very high: not noticeable in a side-by-side comparison at 1:1 | cjxl -d 1.5, libjpeg-turbo 4:2:2 q85 |
| 70 | High: MOS around 4 (“good”); artifacts perceptible but not annoying, barely noticeable side by side at 1:1 | cjxl -d 2.5, libjpeg-turbo 4:2:0 q70 |
| 50 | Medium: MOS around 3 (“fair”); slightly annoying artifacts | cjxl -d 5, libjpeg-turbo 4:2:0 q35 |
| 30 | Low: MOS around 2 (“poor”); obvious and annoying artifacts | cjxl -d 9, libjpeg-turbo 4:2:0 q20 |
| 10 | Very low: MOS close to 1 (“bad”); very obvious and annoying artifacts | cjxl -d 14, libjpeg-turbo 4:2:0 q14 |
| Negative | Extremely low quality, very strong distortion |
The scale was calibrated on still images viewed at 1:1. For video it is a useful orientation, but compare encodes of the same clip with each other rather than aiming at a fixed number.
Score images
Section titled “Score images”Cloudinary’s reference implementation ships as the ssimulacra2 tool in the libjxl release archives for Linux and Windows. Many distribution packages of libjxl leave it out. Give it the original first:
ssimulacra2 original.png distorted.pngIt prints a single score. fssimu2 is a faster, lower-memory derivative that also reads JPEG, WebP, AVIF and Y4M; its scores differ slightly from the reference implementation, so don’t mix the two in one comparison.
Score video
Section titled “Score video”For video, compute a score for every frame. The common tools are:
- Vship: GPU implementation (CUDA, HIP or Vulkan). Its
FFVshipcommand decodes the files itself; the VapourSynth plugin works inside scripts. - vszip: CPU VapourSynth plugin, installed with
pip install vapoursynth-vszip. - Av1an uses either plugin for its SSIMULACRA2 target-quality mode.
With FFVship, SSIMULACRA2 is the default metric, and --json saves the per-frame scores:
FFVship --source reference.mkv --encoded distorted.mkv --json ssimu2.jsonFFVship prints the mean, standard deviation, median, 5th and 95th percentiles, minimum and maximum. In a VapourSynth script run with python (not vspipe), the same summary looks like this:
import vapoursynth as vscore = vs.core
ref = core.bs.VideoSource("reference.mkv")dist = core.bs.VideoSource("distorted.mkv")
clip = core.vship.SSIMULACRA2(ref, dist)scores = sorted(f.props["_SSIMULACRA2"] for f in clip.frames())n = len(scores)print(f"mean {sum(scores) / n:.2f} 5th percentile {scores[n // 20]:.2f} worst {scores[0]:.2f}")For vszip, call core.vszip.SSIMULACRA2(ref, dist) instead and read the SSIMULACRA2 property (no underscore).
Both inputs must have the same dimensions and frame count. FFVship resizes a mismatched encode on its own; in VapourSynth, scale it yourself, e.g. dist = dist.resize.Bicubic(ref.width, ref.height). The tools convert YUV to RGB using the clip’s matrix, transfer and primaries properties, so missing or wrong colour tags give wrong scores; FFVship guesses missing tags, and --verbose shows what it used.
Summarise per-frame scores
Section titled “Summarise per-frame scores”A mean hides a short ugly section, and that section is what viewers notice. Report at least:
- the mean, for the overall level;
- a low percentile such as the 5th, for how bad the worse parts get;
- the worst frames, by number, so you can look at them.
A large standard deviation, or a low percentile far below the mean, points to specific scenes worth inspecting at normal speed and frame by frame. See Compare encoding quality for the whole workflow.
How it works
Section titled “How it works”- Both images are converted to linear light and then to the XYB colour space used by JPEG XL.
- Three error maps are computed for each of the X, Y and B channels: an SSIM map, a ringing/blockiness map (edges in the distorted image where the original is smooth) and a smoothing/blur map (the reverse).
- Each map is computed at six scales, from 1:1 to 1:32, downscaling in linear light.
- For each of the 54 maps, the mean (1-norm) and the 4-norm are taken; the 4-norm emphasises localised errors.
- A weighted sum of those 108 values, tuned on the CID22, TID2013, KADID-10k and KonFiG-IQA datasets, is mapped to the final score.