metrics.js 6.0 KB

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