metrics.js 5.0 KB

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