obartra/ssim

View on GitHub
src/bezkrovnySsim.ts

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
/**
 * Implements Bezkrovny's ssim-specific logic.
 *
 * Refactor of the TypeScript SSIM implementation by Bezkrovny, modified to match the api of ssim.js
 * and reduce duplication.
 *
 * The original work is available at: https://github.com/igor-bezkrovny/image-quantization which is
 * itself a port of the Java SSIM implementation available at https://github.com/rhys-e/structural-similarity
 * both under MIT license
 *
 * @namespace bezkrovnySsim
 */
import { average, variance, covariance } from './math'
import { sub } from './matlab'
import { Options, Matrix } from './types'

/**
 * Generates a SSIM map based on two input image matrices.
 *
 * Images must be a 2-Dimensional grayscale image
 *
 * This method produces a simliar output to `assets/ssim.m` (~1%) when running on Matlab. It's based
 * of Igor Bezkrovny's TypeScript implementation
 *
 * @method bezkrovnySsim
 * @param {Matrix} pixels1 - The reference matrix
 * @param {Matrix} pixels2 - The second matrix to compare against
 * @param {Options} options - The input options parameter
 * @returns {Matrix} ssim_map - A matrix containing the map of computed SSIMs
 * @public
 * @memberOf bezkrovnySsim
 */
export function bezkrovnySsim(
  pixels1: Matrix,
  pixels2: Matrix,
  options: Options
) {
  const { windowSize } = options
  const width = Math.ceil(pixels1.width / windowSize)
  const height = Math.ceil(pixels1.height / windowSize)
  const data = new Array(width * height)
  let counter = 0

  for (let y = 0; y < pixels1.height; y += windowSize) {
    for (let x = 0; x < pixels1.width; x += windowSize) {
      const windowWidth = Math.min(windowSize, pixels1.width - x)
      const windowHeight = Math.min(windowSize, pixels1.height - y)

      const values1 = sub(pixels1, x, windowHeight, y, windowWidth)
      const values2 = sub(pixels2, x, windowHeight, y, windowWidth)

      data[counter++] = windowSsim(values1, values2, options)
    }
  }
  return { data, width, height }
}

/**
 * Generates the per-window ssim value
 *
 * @method windowSsim
 * @param {Matrix} values1 - The matrix of the ssim window to compute for image 1
 * @param {Matrix} values2 - The matrix of the ssim window to compute for image 2
 * @param {Options} options - The input options parameter
 * @returns {Number} ssim - The ssim value at the current window
 * @private
 * @memberOf bezkrovnySsim
 */
function windowSsim(
  { data: values1 }: Matrix,
  { data: values2 }: Matrix,
  { bitDepth, k1, k2 }: Options
): number {
  const L = 2 ** bitDepth - 1
  const c1 = (k1 * L) ** 2
  const c2 = (k2 * L) ** 2
  const average1 = average(values1)
  const average2 = average(values2)
  const σSqx = variance(values1, average1)
  const σSqy = variance(values2, average2)
  const σxy = covariance(values1, values2, average1, average2)

  const numerator = (2 * average1 * average2 + c1) * (2 * σxy + c2)
  const denom1 = average1 ** 2 + average2 ** 2 + c1
  const denom2 = σSqx + σSqy + c2

  return numerator / (denom1 * denom2)
}