metrics.js 5.7 KB

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