Fork me on GitHub

Source: img_metrics.js

  1. /* Image Metrics
  2. * ===========================================================
  3. * derived from image-mse.js (https://github.com/darosh/image-mse-js)
  4. * MIT License
  5. * Copyright (c) 2023 Joachim de Fourestier (Joe DF)
  6. */
  7. /* exported NRMSE */
  8. /**
  9. * The NRMSE (normalized root mean square error) - reference-based image metric.
  10. * @namespace NRMSE
  11. */
  12. const NRMSE = {
  13. /** The maximum value of a pixel component. 255 for 8 bit depth */
  14. pixelMaxValue: 255,
  15. /** The allowed size difference when comparing images.
  16. * The leftover extra pixels of the larger image will be ignored. */
  17. defaultTolerance: 0.01,
  18. /**
  19. * Compares two images.
  20. * @param {*} image1 Preferably the ground truth / reference (convention, shouldnt matter otherwise)
  21. * @param {*} image2 An image to compare
  22. * @param {*} tolerance The allowed size ratio difference, default is {@link NRMSE.defaultTolerance}.
  23. * @returns An object with the calculate metric values of MSE, PSNR, RMSE, NRMSE,
  24. * Inverted NRMSE, NMSE, and Inverted NMSE.
  25. */
  26. compare: function(image1, image2, tolerance) {
  27. 'use strict';
  28. // size check and tolerance
  29. if (image1.data.length != image2.data.length) {
  30. let dl1 = image1.data.length, dl2 = image2.data.length;
  31. var errmsg = "The given images have different data length ("
  32. +image1.width+"x"+image1.height+" vs "+image2.width+"x"+image2.height
  33. +") or sizes ("+dl1+" vs "+dl2+").";
  34. // get the tolerance or use default if not provided
  35. tolerance = (typeof tolerance !== 'undefined') ? tolerance : this.defaultTolerance;
  36. let relDiff = Math.abs(dl1 - dl2) / Math.max(dl1, dl2);
  37. if (relDiff <= tolerance) {
  38. // eslint-disable-next-line no-magic-numbers
  39. console.warn(errmsg + " Tolerance = "+tolerance+" relDiff = "+relDiff.toFixed(6));
  40. } else {
  41. throw errmsg;
  42. }
  43. }
  44. const n_channels = 4; // assume grayscale RGBA flat array
  45. // max pixel value for the bit depth. At 8 bit-depth, this is 255.
  46. const p_max = this.pixelMaxValue;
  47. // Do sum of squared difference
  48. var sum = 0, len = image1.data.length;
  49. for (var i = 0; i < len; i += n_channels) {
  50. let diff = image1.data[i] - image2.data[i];
  51. // check and allow for tolerance of diff image sizes
  52. if (isNaN(diff)) {
  53. if (i >= image2.data.length) {
  54. console.warn("Calculation loop ended early as i = "+i);
  55. break;
  56. } else {
  57. throw "Error: untolerated or invalid calculation!";
  58. }
  59. }
  60. sum += Math.pow(diff, 2);
  61. }
  62. // number of pixels, each having multiple "channels",
  63. // or color components (e.g., R, G, B, A)...
  64. var pc = len / n_channels;
  65. // final step ("average error over all entries/pixels"),
  66. // to obtain MSE (Mean Squared Error)
  67. var mse = sum / pc;
  68. // Maximum possible mse value
  69. // var max_mse = (Math.pow(p_max - 0, 2) * pc) / pc;
  70. // which can be reduced to:
  71. const max_mse = Math.pow(p_max, 2);
  72. // Normalized MSE (NMSE)
  73. var nmse = mse / max_mse;
  74. // Compute RMSE (Root MSE)
  75. var rmse = Math.sqrt(mse);
  76. // Normalized RMSE (NRMSE) or RMS-percentage-E (RMSPE)
  77. // many ways to normalize:
  78. // https://cirpwiki.info/wiki/Statistics#Normalization
  79. // https://stats.stackexchange.com/a/413257/37910
  80. // We just normalize by the max value possible for our RMSE
  81. // which is equal to Math.sqrt(max_mse);
  82. // which can be reduce to just: p_max
  83. var nrmse = rmse / p_max;
  84. return {
  85. mse: mse,
  86. psnr: this.psnr(mse),
  87. rmse: rmse,
  88. nrmse: nrmse,
  89. // "inverted" ("iNRMSE") since we want 1 = good, 0 = bad match
  90. inrmse: 1 - nrmse,
  91. nmse: nmse,
  92. // inverted NMSE ("iNMSE")
  93. inmse: 1 - nmse,
  94. };
  95. },
  96. /**
  97. * Calculates PSNR (Peak Signal-to-Noise Ratio).
  98. * @param {*} mse a Mean Squared Error (MSE) value
  99. * @param {*} max the maximum value of a pixel component, default is {@link NRMSE.pixelMaxValue}
  100. * @returns the calculated value
  101. */
  102. psnr: function(mse, max) {
  103. if (max === void 0) { max = this.pixelMaxValue; }
  104. // eslint-disable-next-line no-magic-numbers
  105. return 10 * this.log10((max * max) / mse);
  106. },
  107. /** Utility function that performs a Log base 10 calculation. */
  108. log10: function(value) {
  109. return Math.log(value) / Math.LN10;
  110. }
  111. };

↑ Top