obartra/ssim

View on GitHub
src/originalSsim.ts

Summary

Maintainability
A
3 hrs
Test Coverage
A
100%
/* eslint-disable max-statements */
// Exceeding max-statements to preserve the structure of the original Matlab script
import {
  add2d,
  divide2d,
  multiply2d,
  square2d,
  subtract2d,
  sum2d,
} from './math'
import { filter2, fspecial } from './matlab'
import { Options, Matrix } from './types'

/**
 * Generates a SSIM map based on two input image matrices. For images greater than 512 pixels, it
 * will downsample them.
 *
 * Images must be a 2-Dimensional grayscale image
 *
 * This method is a line-by-line port of `assets/ssim.m`. Some operations are more verbose here
 * since more logic is needed in JS to manipulate matrices than in Matlab
 *
 * Note that setting `options1.k1` and `options.k2` to 0 will generate the UQI (Universal Quality
 * Index), since it's a special case of SSIM. In general that's undesierable since `k1` and `k2`
 * contribute to the stabilization coeficients `c1` and `c2`.
 *
 * For a mathematically equivalent and more efficient implementation check `./ssim.js`.
 *
 * @method originalSsim
 * @param {Matrix} pixels1 - The reference matrix
 * @param {Matrix} pixels2 - The second matrix to compare against
 * @param {Object} options - The input options parameter
 * @returns {Matrix} ssim_map - A matrix containing the map of computed SSIMs
 * @public
 * @memberOf ssim
 * @since 0.0.2
 */
export function originalSsim(
  pixels1: Matrix,
  pixels2: Matrix,
  options: Options
): Matrix {
  let w = fspecial('gaussian', options.windowSize, 1.5)
  const L = 2 ** options.bitDepth - 1
  const c1 = (options.k1 * L) ** 2
  const c2 = (options.k2 * L) ** 2

  w = divide2d(w, sum2d(w))

  const μ1 = filter2(w, pixels1, 'valid')
  const μ2 = filter2(w, pixels2, 'valid')
  const μ1Sq = square2d(μ1)
  const μ2Sq = square2d(μ2)
  const μ12 = multiply2d(μ1, μ2)
  const pixels1Sq = square2d(pixels1)
  const pixels2Sq = square2d(pixels2)
  const σ1Sq = subtract2d(filter2(w, pixels1Sq, 'valid'), μ1Sq)
  const σ2Sq = subtract2d(filter2(w, pixels2Sq, 'valid'), μ2Sq)
  const σ12 = subtract2d(filter2(w, multiply2d(pixels1, pixels2), 'valid'), μ12)

  if (c1 > 0 && c2 > 0) {
    const num1 = add2d(multiply2d(μ12, 2), c1)
    const num2 = add2d(multiply2d(σ12, 2), c2)
    const denom1 = add2d(add2d(μ1Sq, μ2Sq), c1)
    const denom2 = add2d(add2d(σ1Sq, σ2Sq), c2)

    return divide2d(multiply2d(num1, num2), multiply2d(denom1, denom2))
  }

  const numerator1 = multiply2d(μ12, 2)
  const numerator2 = multiply2d(σ12, 2)
  const denominator1 = add2d(μ1Sq, μ2Sq)
  const denominator2 = add2d(σ1Sq, σ2Sq)

  return divide2d(
    multiply2d(numerator1, numerator2),
    multiply2d(denominator1, denominator2)
  )
}