metrics.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. const metrics = {
  2. alpha: { // combine sigma and bigTheta
  3. option: "Geometric Difference (α)",
  4. displayName: String.raw`\alpha`,
  5. displayBody: p => String.raw`
  6. \sqrt{\sigma\left(${p}\right)^{2 - \sqrt{L\left(\vec{q}\right)}} \Theta\left(${p}\right)^{\sqrt{L\left(\vec{q}\right)}}}
  7. `,
  8. evaluate: () => 0, // calculated below
  9. },
  10. sigma: { // RMS
  11. option: "RMS Deviation (σ)",
  12. displayName: String.raw`\sigma`,
  13. displayBody: p => String.raw`
  14. \sqrt{I\left(${p}\right) - 2\vec{q}\cdot\vec{\mu}\left(${p}\right) + \left|\left|\vec{q}\right|\right|^2}
  15. `,
  16. evaluate: (data, target) => Math.sqrt(data.inertia - 2 * vectorDot(data.mu.vector, target.vector) + target.sqMag),
  17. },
  18. bigTheta: { // 1 - arith mean of cosine similarity
  19. option: "Cosine Difference (Θ)",
  20. displayName: String.raw`\Theta`,
  21. displayBody: p => String.raw`
  22. 1 - \hat{q}\cdot\vec{\nu}\left(${p}\right)
  23. `,
  24. evaluate: (data, target) => 1 - vectorDot(data.nu, target.unit),
  25. },
  26. theta: { // angle of mean
  27. option: "Angular Difference (θ)",
  28. displayName: String.raw`\theta`,
  29. displayBody: p => String.raw`
  30. \cos^{-1}\left( \hat{q}\cdot\hat{\mu}\left(${p}\right) \right)
  31. `,
  32. evaluate: (data, target) => rad2deg * Math.acos(vectorDot(data.mu.unit, target.unit)),
  33. },
  34. phi: { // hue angle
  35. option: "Hue Azimuth (ϕ)",
  36. displayName: String.raw`\phi`,
  37. displayBody: (p, space) => String.raw`
  38. \angle \left(\text{oproj}_{\vec{${space === "jab" ? "J" : "L"}}}{\vec{q}}, \text{oproj}_{\vec{${space === "jab" ? "J" : "L"}}}{\vec{\mu}\left(${p}\right)} \right)
  39. `,
  40. evaluate: (data, target) => {
  41. const raw = Math.abs(data.mu.hue - target.hue);
  42. return Math.min(raw, 360 - raw);
  43. },
  44. },
  45. delta: { // euclidean
  46. option: "Euclidean (δ)",
  47. displayName: String.raw`\delta`,
  48. displayBody: p => String.raw`
  49. \left|\left| \vec{q} - \vec{\mu}\left(${p}\right) \right|\right|
  50. `,
  51. evaluate: (data, target) => vectorMag(data.mu.vector.map((x, i) => x - target.vector[i])),
  52. },
  53. manhattan: { // manhattan distance
  54. option: "Manhattan (M)",
  55. displayName: "M",
  56. displayBody: p => String.raw`
  57. \sum_{i} \left| \vec{\mu}\left(${p}\right)_i - \vec{q}_i \right|
  58. `,
  59. evaluate: (data, target) => data.mu.vector.map((x, i) => Math.abs(x - target.vector[i])).reduce((x, y) => x + y),
  60. },
  61. ch: { // chebyshev
  62. option: "Chebyshev (Ч)",
  63. displayName: "Ч",
  64. displayBody: p => String.raw`
  65. \max_{i} \left| \vec{\mu}\left(${p}\right)_i - \vec{q}_i \right|
  66. `,
  67. evaluate: (data, target) => Math.max(...data.mu.vector.map((x, i) => Math.abs(x - target.vector[i]))),
  68. },
  69. lightnessDiff: {
  70. option: "Lightness (ℓ)",
  71. displayName: String.raw`\ell`,
  72. displayBody: (p, space) => String.raw`
  73. \left| \text{comp}_{\vec{${space === "jab" ? "J" : "L"}}}{\vec{q}} - \text{comp}_{\vec{${space === "jab" ? "J" : "L"}}}{\vec{\mu}\left(${p}\right)} \right|
  74. `,
  75. evaluate: (data, target) => Math.abs(data.mu.lightness - target.lightness),
  76. },
  77. inertia: {
  78. option: "Inertia (I)",
  79. displayName: "I",
  80. displayBody: p => String.raw`
  81. \frac{1}{\left|${p}\right|} \sum_{p\in ${p}}{\left|\left|\vec{p}\right|\right|^2}
  82. `,
  83. evaluate: data => data.inertia,
  84. },
  85. muNuAngle: {
  86. option: "Mu-Nu Angle (V)",
  87. displayName: "V",
  88. displayBody: p => String.raw`\angle \left( \vec{\mu}\left(${p}\right), \vec{\nu}\left(${p}\right) \right)`,
  89. evaluate: data => data.muNuAngle,
  90. },
  91. size: {
  92. option: "Size (N)",
  93. displayName: "N",
  94. displayBody: p => String.raw`\left|${p}\right|`,
  95. evaluate: data => data.size,
  96. },
  97. lightness: {
  98. option: "Mean Lightness (L)",
  99. displayName: "L",
  100. displayBody: (p, space) => String.raw`
  101. \text{comp}_{\vec{${space === "jab" ? "J" : "L"}}}{\vec{\mu}\left(${p}\right)}
  102. `,
  103. evaluate: data => data.mu.lightness,
  104. },
  105. chroma: {
  106. option: "Mean Chroma (C)",
  107. displayName: "C",
  108. displayBody: p => String.raw`\text{chroma}\left(\vec{\mu}\left(${p}\right)\right)`,
  109. evaluate: data => data.mu.chroma,
  110. },
  111. intensity: {
  112. option: "Visual Intensity (γ)",
  113. displayName: String.raw`\gamma`,
  114. displayBody: p => String.raw`\sqrt[3]{C\left(${p}\right) L\left(${p}\right)^2}`,
  115. evaluate: data => Math.cbrt(data.mu.chroma * data.mu.lightness * data.mu.lightness)
  116. },
  117. };
  118. const applyMetrics = (data, target) => {
  119. const scores = Object.fromEntries(
  120. Object.entries(metrics)
  121. .map(([name, metric]) => [name, metric.evaluate(data, target)])
  122. );
  123. // rearranges to geometric mean of sigma and bigTheta
  124. const power = Math.sqrt(target.lightness)
  125. scores.alpha = Math.sqrt(Math.pow(scores.sigma, 2 - power) * Math.pow(scores.bigTheta, power));
  126. return scores;
  127. };