Skip to content

SSIMULACRA2

currentMigrated from docs/metrics/SSIMULACRA2.mdx at 37c3317fd058; modified for Encode Wiki · CC BY-SA 4.0

SSIMULACRA2 (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.

Scores run from 100 (mathematically lossless) down without a lower limit; higher is better. The upstream README relates them to subjective quality like this:

ScoreUpstream descriptionTypical output of
100Mathematically lossless
90Visually lossless: not noticeable by an average observer in a flicker test at 1:1 from a normal viewing distancecjxl -d 0.5, libjpeg-turbo 4:4:4 q95
85Excellent: not noticeable in an in-place comparison at 1:1cjxl -d 1, libjpeg-turbo 4:4:4 q90
80Very high: not noticeable in a side-by-side comparison at 1:1cjxl -d 1.5, libjpeg-turbo 4:2:2 q85
70High: MOS around 4 (“good”); artifacts perceptible but not annoying, barely noticeable side by side at 1:1cjxl -d 2.5, libjpeg-turbo 4:2:0 q70
50Medium: MOS around 3 (“fair”); slightly annoying artifactscjxl -d 5, libjpeg-turbo 4:2:0 q35
30Low: MOS around 2 (“poor”); obvious and annoying artifactscjxl -d 9, libjpeg-turbo 4:2:0 q20
10Very low: MOS close to 1 (“bad”); very obvious and annoying artifactscjxl -d 14, libjpeg-turbo 4:2:0 q14
NegativeExtremely 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.

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:

Terminal window
ssimulacra2 original.png distorted.png

It 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.

For video, compute a score for every frame. The common tools are:

  • Vship: GPU implementation (CUDA, HIP or Vulkan). Its FFVship command 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:

Terminal window
FFVship --source reference.mkv --encoded distorted.mkv --json ssimu2.json

FFVship 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 vs
core = 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.

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.

  • 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.