diff --git "a/packages/niivueviewer/backend/gradio_niivueviewer/templates/component/index.js" "b/packages/niivueviewer/backend/gradio_niivueviewer/templates/component/index.js" new file mode 100644--- /dev/null +++ "b/packages/niivueviewer/backend/gradio_niivueviewer/templates/component/index.js" @@ -0,0 +1,37087 @@ +import * as y from "../../../../../assets/svelte/svelte_internal_client.js"; +import { onMount as mn, untrack as ir, tick as Ea, onDestroy as Fa } from "../../../../../assets/svelte/svelte_svelte.js"; +import "../../../../../assets/svelte/svelte_internal_flags_legacy.js"; +import { spring as sr } from "../../../../../assets/svelte/svelte_motion.js"; +import "../../../../../assets/svelte/svelte_transition.js"; +var Ma = 1e-6, Ce = typeof Float32Array < "u" ? Float32Array : Array; +function ms() { + var i = new Ce(9); + return Ce != Float32Array && (i[1] = 0, i[2] = 0, i[3] = 0, i[5] = 0, i[6] = 0, i[7] = 0), i[0] = 1, i[4] = 1, i[8] = 1, i; +} +function Ta(i, e) { + return i[0] = e[0], i[1] = e[1], i[2] = e[2], i[3] = e[4], i[4] = e[5], i[5] = e[6], i[6] = e[8], i[7] = e[9], i[8] = e[10], i; +} +function rt(i, e, t, s, r, a, n, o, l) { + var c = new Ce(9); + return c[0] = i, c[1] = e, c[2] = t, c[3] = s, c[4] = r, c[5] = a, c[6] = n, c[7] = o, c[8] = l, c; +} +function Sa(i, e) { + var t = e[0], s = e[1], r = e[2], a = e[3], n = e[4], o = e[5], l = e[6], c = e[7], h = e[8], u = h * n - o * c, d = -h * a + o * l, f = c * a - n * l, g = t * u + s * d + r * f; + return g ? (g = 1 / g, i[0] = u * g, i[1] = (-h * s + r * c) * g, i[2] = (o * s - r * n) * g, i[3] = d * g, i[4] = (h * t - r * l) * g, i[5] = (-o * t + r * a) * g, i[6] = f * g, i[7] = (-c * t + s * l) * g, i[8] = (n * t - s * a) * g, i) : null; +} +function gn(i, e, t) { + var s = e[0], r = e[1], a = e[2], n = e[3], o = e[4], l = e[5], c = e[6], h = e[7], u = e[8], d = t[0], f = t[1], g = t[2], m = t[3], p = t[4], v = t[5], A = t[6], x = t[7], w = t[8]; + return i[0] = d * s + f * n + g * c, i[1] = d * r + f * o + g * h, i[2] = d * a + f * l + g * u, i[3] = m * s + p * n + v * c, i[4] = m * r + p * o + v * h, i[5] = m * a + p * l + v * u, i[6] = A * s + x * n + w * c, i[7] = A * r + x * o + w * h, i[8] = A * a + x * l + w * u, i; +} +function ie() { + var i = new Ce(16); + return Ce != Float32Array && (i[1] = 0, i[2] = 0, i[3] = 0, i[4] = 0, i[6] = 0, i[7] = 0, i[8] = 0, i[9] = 0, i[11] = 0, i[12] = 0, i[13] = 0, i[14] = 0), i[0] = 1, i[5] = 1, i[10] = 1, i[15] = 1, i; +} +function xe(i) { + var e = new Ce(16); + return e[0] = i[0], e[1] = i[1], e[2] = i[2], e[3] = i[3], e[4] = i[4], e[5] = i[5], e[6] = i[6], e[7] = i[7], e[8] = i[8], e[9] = i[9], e[10] = i[10], e[11] = i[11], e[12] = i[12], e[13] = i[13], e[14] = i[14], e[15] = i[15], e; +} +function Ia(i, e) { + return i[0] = e[0], i[1] = e[1], i[2] = e[2], i[3] = e[3], i[4] = e[4], i[5] = e[5], i[6] = e[6], i[7] = e[7], i[8] = e[8], i[9] = e[9], i[10] = e[10], i[11] = e[11], i[12] = e[12], i[13] = e[13], i[14] = e[14], i[15] = e[15], i; +} +function Le(i, e, t, s, r, a, n, o, l, c, h, u, d, f, g, m) { + var p = new Ce(16); + return p[0] = i, p[1] = e, p[2] = t, p[3] = s, p[4] = r, p[5] = a, p[6] = n, p[7] = o, p[8] = l, p[9] = c, p[10] = h, p[11] = u, p[12] = d, p[13] = f, p[14] = g, p[15] = m, p; +} +function pn(i) { + return i[0] = 1, i[1] = 0, i[2] = 0, i[3] = 0, i[4] = 0, i[5] = 1, i[6] = 0, i[7] = 0, i[8] = 0, i[9] = 0, i[10] = 1, i[11] = 0, i[12] = 0, i[13] = 0, i[14] = 0, i[15] = 1, i; +} +function Ne(i, e) { + if (i === e) { + var t = e[1], s = e[2], r = e[3], a = e[6], n = e[7], o = e[11]; + i[1] = e[4], i[2] = e[8], i[3] = e[12], i[4] = t, i[6] = e[9], i[7] = e[13], i[8] = s, i[9] = a, i[11] = e[14], i[12] = r, i[13] = n, i[14] = o; + } else + i[0] = e[0], i[1] = e[4], i[2] = e[8], i[3] = e[12], i[4] = e[1], i[5] = e[5], i[6] = e[9], i[7] = e[13], i[8] = e[2], i[9] = e[6], i[10] = e[10], i[11] = e[14], i[12] = e[3], i[13] = e[7], i[14] = e[11], i[15] = e[15]; + return i; +} +function Re(i, e) { + var t = e[0], s = e[1], r = e[2], a = e[3], n = e[4], o = e[5], l = e[6], c = e[7], h = e[8], u = e[9], d = e[10], f = e[11], g = e[12], m = e[13], p = e[14], v = e[15], A = t * o - s * n, x = t * l - r * n, w = t * c - a * n, D = s * l - r * o, b = s * c - a * o, C = r * c - a * l, E = h * m - u * g, F = h * p - d * g, M = h * v - f * g, S = u * p - d * m, T = u * v - f * m, k = d * v - f * p, B = A * k - x * T + w * S + D * M - b * F + C * E; + return B ? (B = 1 / B, i[0] = (o * k - l * T + c * S) * B, i[1] = (r * T - s * k - a * S) * B, i[2] = (m * C - p * b + v * D) * B, i[3] = (d * b - u * C - f * D) * B, i[4] = (l * M - n * k - c * F) * B, i[5] = (t * k - r * M + a * F) * B, i[6] = (p * w - g * C - v * x) * B, i[7] = (h * C - d * w + f * x) * B, i[8] = (n * T - o * M + c * E) * B, i[9] = (s * M - t * T - a * E) * B, i[10] = (g * b - m * w + v * A) * B, i[11] = (u * w - h * b - f * A) * B, i[12] = (o * F - n * S - l * E) * B, i[13] = (t * S - s * F + r * E) * B, i[14] = (m * x - g * D - p * A) * B, i[15] = (h * D - u * x + d * A) * B, i) : null; +} +function mt(i, e, t) { + var s = e[0], r = e[1], a = e[2], n = e[3], o = e[4], l = e[5], c = e[6], h = e[7], u = e[8], d = e[9], f = e[10], g = e[11], m = e[12], p = e[13], v = e[14], A = e[15], x = t[0], w = t[1], D = t[2], b = t[3]; + return i[0] = x * s + w * o + D * u + b * m, i[1] = x * r + w * l + D * d + b * p, i[2] = x * a + w * c + D * f + b * v, i[3] = x * n + w * h + D * g + b * A, x = t[4], w = t[5], D = t[6], b = t[7], i[4] = x * s + w * o + D * u + b * m, i[5] = x * r + w * l + D * d + b * p, i[6] = x * a + w * c + D * f + b * v, i[7] = x * n + w * h + D * g + b * A, x = t[8], w = t[9], D = t[10], b = t[11], i[8] = x * s + w * o + D * u + b * m, i[9] = x * r + w * l + D * d + b * p, i[10] = x * a + w * c + D * f + b * v, i[11] = x * n + w * h + D * g + b * A, x = t[12], w = t[13], D = t[14], b = t[15], i[12] = x * s + w * o + D * u + b * m, i[13] = x * r + w * l + D * d + b * p, i[14] = x * a + w * c + D * f + b * v, i[15] = x * n + w * h + D * g + b * A, i; +} +function St(i, e, t) { + var s = t[0], r = t[1], a = t[2], n, o, l, c, h, u, d, f, g, m, p, v; + return e === i ? (i[12] = e[0] * s + e[4] * r + e[8] * a + e[12], i[13] = e[1] * s + e[5] * r + e[9] * a + e[13], i[14] = e[2] * s + e[6] * r + e[10] * a + e[14], i[15] = e[3] * s + e[7] * r + e[11] * a + e[15]) : (n = e[0], o = e[1], l = e[2], c = e[3], h = e[4], u = e[5], d = e[6], f = e[7], g = e[8], m = e[9], p = e[10], v = e[11], i[0] = n, i[1] = o, i[2] = l, i[3] = c, i[4] = h, i[5] = u, i[6] = d, i[7] = f, i[8] = g, i[9] = m, i[10] = p, i[11] = v, i[12] = n * s + h * r + g * a + e[12], i[13] = o * s + u * r + m * a + e[13], i[14] = l * s + d * r + p * a + e[14], i[15] = c * s + f * r + v * a + e[15]), i; +} +function rr(i, e, t) { + var s = t[0], r = t[1], a = t[2]; + return i[0] = e[0] * s, i[1] = e[1] * s, i[2] = e[2] * s, i[3] = e[3] * s, i[4] = e[4] * r, i[5] = e[5] * r, i[6] = e[6] * r, i[7] = e[7] * r, i[8] = e[8] * a, i[9] = e[9] * a, i[10] = e[10] * a, i[11] = e[11] * a, i[12] = e[12], i[13] = e[13], i[14] = e[14], i[15] = e[15], i; +} +function li(i, e, t) { + var s = Math.sin(t), r = Math.cos(t), a = e[4], n = e[5], o = e[6], l = e[7], c = e[8], h = e[9], u = e[10], d = e[11]; + return e !== i && (i[0] = e[0], i[1] = e[1], i[2] = e[2], i[3] = e[3], i[12] = e[12], i[13] = e[13], i[14] = e[14], i[15] = e[15]), i[4] = a * r + c * s, i[5] = n * r + h * s, i[6] = o * r + u * s, i[7] = l * r + d * s, i[8] = c * r - a * s, i[9] = h * r - n * s, i[10] = u * r - o * s, i[11] = d * r - l * s, i; +} +function ci(i, e, t) { + var s = Math.sin(t), r = Math.cos(t), a = e[0], n = e[1], o = e[2], l = e[3], c = e[4], h = e[5], u = e[6], d = e[7]; + return e !== i && (i[8] = e[8], i[9] = e[9], i[10] = e[10], i[11] = e[11], i[12] = e[12], i[13] = e[13], i[14] = e[14], i[15] = e[15]), i[0] = a * r + c * s, i[1] = n * r + h * s, i[2] = o * r + u * s, i[3] = l * r + d * s, i[4] = c * r - a * s, i[5] = h * r - n * s, i[6] = u * r - o * s, i[7] = d * r - l * s, i; +} +function Ba(i, e, t) { + var s = t[0], r = t[1], a = t[2], n = Math.sqrt(s * s + r * r + a * a), o, l, c; + return n < Ma ? null : (n = 1 / n, s *= n, r *= n, a *= n, o = Math.sin(e), l = Math.cos(e), c = 1 - l, i[0] = s * s * c + l, i[1] = r * s * c + a * o, i[2] = a * s * c - r * o, i[3] = 0, i[4] = s * r * c - a * o, i[5] = r * r * c + l, i[6] = a * r * c + s * o, i[7] = 0, i[8] = s * a * c + r * o, i[9] = r * a * c - s * o, i[10] = a * a * c + l, i[11] = 0, i[12] = 0, i[13] = 0, i[14] = 0, i[15] = 1, i); +} +function ka(i, e, t, s, r, a, n) { + var o = 1 / (e - t), l = 1 / (s - r), c = 1 / (a - n); + return i[0] = -2 * o, i[1] = 0, i[2] = 0, i[3] = 0, i[4] = 0, i[5] = -2 * l, i[6] = 0, i[7] = 0, i[8] = 0, i[9] = 0, i[10] = 2 * c, i[11] = 0, i[12] = (e + t) * o, i[13] = (r + s) * l, i[14] = (n + a) * c, i[15] = 1, i; +} +var hi = ka; +function Ra(i, e, t) { + return i[0] = e[0] * t, i[1] = e[1] * t, i[2] = e[2] * t, i[3] = e[3] * t, i[4] = e[4] * t, i[5] = e[5] * t, i[6] = e[6] * t, i[7] = e[7] * t, i[8] = e[8] * t, i[9] = e[9] * t, i[10] = e[10] * t, i[11] = e[11] * t, i[12] = e[12] * t, i[13] = e[13] * t, i[14] = e[14] * t, i[15] = e[15] * t, i; +} +var An = mt; +function le() { + var i = new Ce(3); + return Ce != Float32Array && (i[0] = 0, i[1] = 0, i[2] = 0), i; +} +function Ye(i) { + var e = new Ce(3); + return e[0] = i[0], e[1] = i[1], e[2] = i[2], e; +} +function ot(i) { + var e = i[0], t = i[1], s = i[2]; + return Math.sqrt(e * e + t * t + s * s); +} +function G(i, e, t) { + var s = new Ce(3); + return s[0] = i, s[1] = e, s[2] = t, s; +} +function nr(i, e) { + return i[0] = e[0], i[1] = e[1], i[2] = e[2], i; +} +function At(i, e, t) { + return i[0] = e[0] + t[0], i[1] = e[1] + t[1], i[2] = e[2] + t[2], i; +} +function de(i, e, t) { + return i[0] = e[0] - t[0], i[1] = e[1] - t[1], i[2] = e[2] - t[2], i; +} +function Cs(i, e, t) { + return i[0] = Math.min(e[0], t[0]), i[1] = Math.min(e[1], t[1]), i[2] = Math.min(e[2], t[2]), i; +} +function Ds(i, e, t) { + return i[0] = Math.max(e[0], t[0]), i[1] = Math.max(e[1], t[1]), i[2] = Math.max(e[2], t[2]), i; +} +function Va(i, e, t) { + return i[0] = e[0] * t, i[1] = e[1] * t, i[2] = e[2] * t, i; +} +function Ua(i, e) { + var t = e[0] - i[0], s = e[1] - i[1], r = e[2] - i[2]; + return Math.sqrt(t * t + s * s + r * r); +} +function Na(i, e) { + return i[0] = -e[0], i[1] = -e[1], i[2] = -e[2], i; +} +function Pe(i, e) { + var t = e[0], s = e[1], r = e[2], a = t * t + s * s + r * r; + return a > 0 && (a = 1 / Math.sqrt(a)), i[0] = e[0] * a, i[1] = e[1] * a, i[2] = e[2] * a, i; +} +function Pa(i, e) { + return i[0] * e[0] + i[1] * e[1] + i[2] * e[2]; +} +function Ci(i, e, t) { + var s = e[0], r = e[1], a = e[2], n = t[0], o = t[1], l = t[2]; + return i[0] = r * l - a * o, i[1] = a * n - s * l, i[2] = s * o - r * n, i; +} +function La(i, e, t, s) { + var r = e[0], a = e[1], n = e[2]; + return i[0] = r + s * (t[0] - r), i[1] = a + s * (t[1] - a), i[2] = n + s * (t[2] - n), i; +} +function ar(i, e, t) { + var s = e[0], r = e[1], a = e[2]; + return i[0] = s * t[0] + r * t[3] + a * t[6], i[1] = s * t[1] + r * t[4] + a * t[7], i[2] = s * t[2] + r * t[5] + a * t[8], i; +} +function Ki(i, e) { + var t = i[0], s = i[1], r = i[2], a = e[0], n = e[1], o = e[2], l = Math.sqrt((t * t + s * s + r * r) * (a * a + n * n + o * o)), c = l && Pa(i, e) / l; + return Math.acos(Math.min(Math.max(c, -1), 1)); +} +var or = de, ei = ot; +(function() { + var i = le(); + return function(e, t, s, r, a, n) { + var o, l; + for (t || (t = 3), s || (s = 0), r ? l = Math.min(r * t + s, e.length) : l = e.length, o = s; o < l; o += t) + i[0] = e[o], i[1] = e[o + 1], i[2] = e[o + 2], a(i, i, n), e[o] = i[0], e[o + 1] = i[1], e[o + 2] = i[2]; + return e; + }; +})(); +function Je() { + var i = new Ce(4); + return Ce != Float32Array && (i[0] = 0, i[1] = 0, i[2] = 0, i[3] = 0), i; +} +function nt(i) { + var e = new Ce(4); + return e[0] = i[0], e[1] = i[1], e[2] = i[2], e[3] = i[3], e; +} +function pe(i, e, t, s) { + var r = new Ce(4); + return r[0] = i, r[1] = e, r[2] = t, r[3] = s, r; +} +function lr(i, e, t) { + return i[0] = e[0] + t[0], i[1] = e[1] + t[1], i[2] = e[2] + t[2], i[3] = e[3] + t[3], i; +} +function Oa(i, e, t) { + return i[0] = e[0] - t[0], i[1] = e[1] - t[1], i[2] = e[2] - t[2], i[3] = e[3] - t[3], i; +} +function za(i, e, t) { + return i[0] = e[0] * t, i[1] = e[1] * t, i[2] = e[2] * t, i[3] = e[3] * t, i; +} +function Me(i, e, t) { + var s = e[0], r = e[1], a = e[2], n = e[3]; + return i[0] = t[0] * s + t[4] * r + t[8] * a + t[12] * n, i[1] = t[1] * s + t[5] * r + t[9] * a + t[13] * n, i[2] = t[2] * s + t[6] * r + t[10] * a + t[14] * n, i[3] = t[3] * s + t[7] * r + t[11] * a + t[15] * n, i; +} +var Ga = Oa; +(function() { + var i = Je(); + return function(e, t, s, r, a, n) { + var o, l; + for (t || (t = 4), s || (s = 0), r ? l = Math.min(r * t + s, e.length) : l = e.length, o = s; o < l; o += t) + i[0] = e[o], i[1] = e[o + 1], i[2] = e[o + 2], i[3] = e[o + 3], a(i, i, n), e[o] = i[0], e[o + 1] = i[1], e[o + 2] = i[2], e[o + 3] = i[3]; + return e; + }; +})(); +function Ya() { + var i = new Ce(2); + return Ce != Float32Array && (i[0] = 0, i[1] = 0), i; +} +function cr(i, e) { + var t = new Ce(2); + return t[0] = i, t[1] = e, t; +} +function _a(i, e, t) { + return i[0] = e[0] * t, i[1] = e[1] * t, i; +} +function hr(i) { + var e = i[0], t = i[1]; + return Math.sqrt(e * e + t * t); +} +function qa(i, e) { + var t = e[0], s = e[1], r = t * t + s * s; + return r > 0 && (r = 1 / Math.sqrt(r)), i[0] = e[0] * r, i[1] = e[1] * r, i; +} +(function() { + var i = Ya(); + return function(e, t, s, r, a, n) { + var o, l; + for (t || (t = 2), s || (s = 0), r ? l = Math.min(r * t + s, e.length) : l = e.length, o = s; o < l; o += t) + i[0] = e[o], i[1] = e[o + 1], a(i, i, n), e[o] = i[0], e[o + 1] = i[1]; + return e; + }; +})(); +var pt = 256, Di = [], ui; +for (; pt--; ) Di[pt] = (pt + 256).toString(16).substring(1); +function gs() { + var i = 0, e, t = ""; + if (!ui || pt + 16 > 256) { + for (ui = Array(i = 256); i--; ) ui[i] = 256 * Math.random() | 0; + i = pt = 0; + } + for (; i < 16; i++) + e = ui[pt + i], i == 6 ? t += Di[e & 15 | 64] : i == 8 ? t += Di[e & 63 | 128] : t += Di[e], i & 1 && i > 1 && i < 11 && (t += "-"); + return pt++, t; +} +var ur = typeof globalThis < "u" ? globalThis : typeof window < "u" ? window : typeof global < "u" ? global : typeof self < "u" ? self : {}; +function Ha(i) { + return i && i.__esModule && Object.prototype.hasOwnProperty.call(i, "default") ? i.default : i; +} +var Xi, dr; +function Wa() { + return dr || (dr = 1, Xi = function(e, t) { + var s = e.length; + if (e === t) return !0; + if (s !== t.length) return !1; + for (var r = 0; r < s; r++) + if (e[r] !== t[r]) + return !1; + return !0; + }), Xi; +} +var Ka = Wa(); +const Xa = /* @__PURE__ */ Ha(Ka), vn = -1, Li = 0, jt = 1, ki = 2, Es = 3, Fs = 4, Ms = 5, Ts = 6, xn = 7, wn = 8, fr = typeof self == "object" ? self : globalThis, ja = (i, e) => { + const t = (r, a) => (i.set(a, r), r), s = (r) => { + if (i.has(r)) + return i.get(r); + const [a, n] = e[r]; + switch (a) { + case Li: + case vn: + return t(n, r); + case jt: { + const o = t([], r); + for (const l of n) + o.push(s(l)); + return o; + } + case ki: { + const o = t({}, r); + for (const [l, c] of n) + o[s(l)] = s(c); + return o; + } + case Es: + return t(new Date(n), r); + case Fs: { + const { source: o, flags: l } = n; + return t(new RegExp(o, l), r); + } + case Ms: { + const o = t(/* @__PURE__ */ new Map(), r); + for (const [l, c] of n) + o.set(s(l), s(c)); + return o; + } + case Ts: { + const o = t(/* @__PURE__ */ new Set(), r); + for (const l of n) + o.add(s(l)); + return o; + } + case xn: { + const { name: o, message: l } = n; + return t(new fr[o](l), r); + } + case wn: + return t(BigInt(n), r); + case "BigInt": + return t(Object(BigInt(n)), r); + case "ArrayBuffer": + return t(new Uint8Array(n).buffer, n); + case "DataView": { + const { buffer: o } = new Uint8Array(n); + return t(new DataView(o), n); + } + } + return t(new fr[a](n), r); + }; + return s; +}, Za = (i) => ja(/* @__PURE__ */ new Map(), i)(0), Mt = "", { toString: Qa } = {}, { keys: Ja } = Object, Lt = (i) => { + const e = typeof i; + if (e !== "object" || !i) + return [Li, e]; + const t = Qa.call(i).slice(8, -1); + switch (t) { + case "Array": + return [jt, Mt]; + case "Object": + return [ki, Mt]; + case "Date": + return [Es, Mt]; + case "RegExp": + return [Fs, Mt]; + case "Map": + return [Ms, Mt]; + case "Set": + return [Ts, Mt]; + case "DataView": + return [jt, t]; + } + return t.includes("Array") ? [jt, t] : t.includes("Error") ? [xn, t] : [ki, t]; +}, di = ([i, e]) => i === Li && (e === "function" || e === "symbol"), $a = (i, e, t, s) => { + const r = (n, o) => { + const l = s.push(n) - 1; + return t.set(o, l), l; + }, a = (n) => { + if (t.has(n)) + return t.get(n); + let [o, l] = Lt(n); + switch (o) { + case Li: { + let h = n; + switch (l) { + case "bigint": + o = wn, h = n.toString(); + break; + case "function": + case "symbol": + if (i) + throw new TypeError("unable to serialize " + l); + h = null; + break; + case "undefined": + return r([vn], n); + } + return r([o, h], n); + } + case jt: { + if (l) { + let d = n; + return l === "DataView" ? d = new Uint8Array(n.buffer) : l === "ArrayBuffer" && (d = new Uint8Array(n)), r([l, [...d]], n); + } + const h = [], u = r([o, h], n); + for (const d of n) + h.push(a(d)); + return u; + } + case ki: { + if (l) + switch (l) { + case "BigInt": + return r([l, n.toString()], n); + case "Boolean": + case "Number": + case "String": + return r([l, n.valueOf()], n); + } + if (e && "toJSON" in n) + return a(n.toJSON()); + const h = [], u = r([o, h], n); + for (const d of Ja(n)) + (i || !di(Lt(n[d]))) && h.push([a(d), a(n[d])]); + return u; + } + case Es: + return r([o, n.toISOString()], n); + case Fs: { + const { source: h, flags: u } = n; + return r([o, { source: h, flags: u }], n); + } + case Ms: { + const h = [], u = r([o, h], n); + for (const [d, f] of n) + (i || !(di(Lt(d)) || di(Lt(f)))) && h.push([a(d), a(f)]); + return u; + } + case Ts: { + const h = [], u = r([o, h], n); + for (const d of n) + (i || !di(Lt(d))) && h.push(a(d)); + return u; + } + } + const { message: c } = n; + return r([o, { name: l, message: c }], n); + }; + return a; +}, eo = (i, { json: e, lossy: t } = {}) => { + const s = []; + return $a(!(e || t), !!e, /* @__PURE__ */ new Map(), s)(i), s; +}; +var ye = Uint8Array, Rt = Uint16Array, to = Int32Array, bn = new ye([ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 0, + /* unused */ + 0, + 0, + /* impossible */ + 0 +]), yn = new ye([ + 0, + 0, + 0, + 0, + 1, + 1, + 2, + 2, + 3, + 3, + 4, + 4, + 5, + 5, + 6, + 6, + 7, + 7, + 8, + 8, + 9, + 9, + 10, + 10, + 11, + 11, + 12, + 12, + 13, + 13, + /* unused */ + 0, + 0 +]), io = new ye([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]), Cn = function(i, e) { + for (var t = new Rt(31), s = 0; s < 31; ++s) + t[s] = e += 1 << i[s - 1]; + for (var r = new to(t[30]), s = 1; s < 30; ++s) + for (var a = t[s]; a < t[s + 1]; ++a) + r[a] = a - t[s] << 5 | s; + return { b: t, r }; +}, Dn = Cn(bn, 2), En = Dn.b, so = Dn.r; +En[28] = 258, so[258] = 28; +var ro = Cn(yn, 0), no = ro.b, ps = new Rt(32768); +for (var me = 0; me < 32768; ++me) { + var $e = (me & 43690) >> 1 | (me & 21845) << 1; + $e = ($e & 52428) >> 2 | ($e & 13107) << 2, $e = ($e & 61680) >> 4 | ($e & 3855) << 4, ps[me] = (($e & 65280) >> 8 | ($e & 255) << 8) >> 1; +} +var Zt = (function(i, e, t) { + for (var s = i.length, r = 0, a = new Rt(e); r < s; ++r) + i[r] && ++a[i[r] - 1]; + var n = new Rt(e); + for (r = 1; r < e; ++r) + n[r] = n[r - 1] + a[r - 1] << 1; + var o; + if (t) { + o = new Rt(1 << e); + var l = 15 - e; + for (r = 0; r < s; ++r) + if (i[r]) + for (var c = r << 4 | i[r], h = e - i[r], u = n[i[r] - 1]++ << h, d = u | (1 << h) - 1; u <= d; ++u) + o[ps[u] >> l] = c; + } else + for (o = new Rt(s), r = 0; r < s; ++r) + i[r] && (o[r] = ps[n[i[r] - 1]++] >> 15 - i[r]); + return o; +}), ti = new ye(288); +for (var me = 0; me < 144; ++me) + ti[me] = 8; +for (var me = 144; me < 256; ++me) + ti[me] = 9; +for (var me = 256; me < 280; ++me) + ti[me] = 7; +for (var me = 280; me < 288; ++me) + ti[me] = 8; +var Fn = new ye(32); +for (var me = 0; me < 32; ++me) + Fn[me] = 5; +var ao = /* @__PURE__ */ Zt(ti, 9, 1), oo = /* @__PURE__ */ Zt(Fn, 5, 1), ji = function(i) { + for (var e = i[0], t = 1; t < i.length; ++t) + i[t] > e && (e = i[t]); + return e; +}, Oe = function(i, e, t) { + var s = e / 8 | 0; + return (i[s] | i[s + 1] << 8) >> (e & 7) & t; +}, Zi = function(i, e) { + var t = e / 8 | 0; + return (i[t] | i[t + 1] << 8 | i[t + 2] << 16) >> (e & 7); +}, Mn = function(i) { + return (i + 7) / 8 | 0; +}, Ei = function(i, e, t) { + return (e == null || e < 0) && (e = 0), (t == null || t > i.length) && (t = i.length), new ye(i.subarray(e, t)); +}, lo = [ + "unexpected EOF", + "invalid block type", + "invalid length/literal", + "invalid distance", + "stream finished", + "no stream handler", + , + "no callback", + "invalid UTF-8 data", + "extra field too long", + "date not in range 1980-2099", + "filename too long", + "stream finishing", + "invalid zip data" + // determined by unknown compression method +], Ie = function(i, e, t) { + var s = new Error(e || lo[i]); + if (s.code = i, Error.captureStackTrace && Error.captureStackTrace(s, Ie), !t) + throw s; + return s; +}, Oi = function(i, e, t, s) { + var r = i.length, a = 0; + if (!r || e.f && !e.l) + return t || new ye(0); + var n = !t, o = n || e.i != 2, l = e.i; + n && (t = new ye(r * 3)); + var c = function(se) { + var ue = t.length; + if (se > ue) { + var Ve = new ye(Math.max(ue * 2, se)); + Ve.set(t), t = Ve; + } + }, h = e.f || 0, u = e.p || 0, d = e.b || 0, f = e.l, g = e.d, m = e.m, p = e.n, v = r * 8; + do { + if (!f) { + h = Oe(i, u, 1); + var A = Oe(i, u + 1, 3); + if (u += 3, A) + if (A == 1) + f = ao, g = oo, m = 9, p = 5; + else if (A == 2) { + var b = Oe(i, u, 31) + 257, C = Oe(i, u + 10, 15) + 4, E = b + Oe(i, u + 5, 31) + 1; + u += 14; + for (var F = new ye(E), M = new ye(19), S = 0; S < C; ++S) + M[io[S]] = Oe(i, u + S * 3, 7); + u += C * 3; + for (var T = ji(M), k = (1 << T) - 1, B = Zt(M, T, 1), S = 0; S < E; ) { + var U = B[Oe(i, u, k)]; + u += U & 15; + var x = U >> 4; + if (x < 16) + F[S++] = x; + else { + var V = 0, N = 0; + for (x == 16 ? (N = 3 + Oe(i, u, 3), u += 2, V = F[S - 1]) : x == 17 ? (N = 3 + Oe(i, u, 7), u += 3) : x == 18 && (N = 11 + Oe(i, u, 127), u += 7); N--; ) + F[S++] = V; + } + } + var P = F.subarray(0, b), L = F.subarray(b); + m = ji(P), p = ji(L), f = Zt(P, m, 1), g = Zt(L, p, 1); + } else + Ie(1); + else { + var x = Mn(u) + 4, w = i[x - 4] | i[x - 3] << 8, D = x + w; + if (D > r) { + l && Ie(0); + break; + } + o && c(d + w), t.set(i.subarray(x, D), d), e.b = d += w, e.p = u = D * 8, e.f = h; + continue; + } + if (u > v) { + l && Ie(0); + break; + } + } + o && c(d + 131072); + for (var q = (1 << m) - 1, X = (1 << p) - 1, O = u; ; O = u) { + var V = f[Zi(i, u) & q], Y = V >> 4; + if (u += V & 15, u > v) { + l && Ie(0); + break; + } + if (V || Ie(2), Y < 256) + t[d++] = Y; + else if (Y == 256) { + O = u, f = null; + break; + } else { + var _ = Y - 254; + if (Y > 264) { + var S = Y - 257, W = bn[S]; + _ = Oe(i, u, (1 << W) - 1) + En[S], u += W; + } + var Q = g[Zi(i, u) & X], J = Q >> 4; + Q || Ie(3), u += Q & 15; + var L = no[J]; + if (J > 3) { + var W = yn[J]; + L += Zi(i, u) & (1 << W) - 1, u += W; + } + if (u > v) { + l && Ie(0); + break; + } + o && c(d + 131072); + var ee = d + _; + if (d < L) { + var re = a - L, ne = Math.min(L, ee); + for (re + d < 0 && Ie(3); d < ne; ++d) + t[d] = s[re + d]; + } + for (; d < ee; ++d) + t[d] = t[d - L]; + } + } + e.l = f, e.p = O, e.b = d, e.f = h, f && (h = 1, e.m = m, e.d = g, e.n = p); + } while (!h); + return d != t.length && n ? Ei(t, 0, d) : t.subarray(0, d); +}, co = /* @__PURE__ */ new ye(0), Tn = function(i) { + (i[0] != 31 || i[1] != 139 || i[2] != 8) && Ie(6, "invalid gzip data"); + var e = i[3], t = 10; + e & 4 && (t += (i[10] | i[11] << 8) + 2); + for (var s = (e >> 3 & 1) + (e >> 4 & 1); s > 0; s -= !i[t++]) + ; + return t + (e & 2); +}, ho = function(i) { + var e = i.length; + return (i[e - 4] | i[e - 3] << 8 | i[e - 2] << 16 | i[e - 1] << 24) >>> 0; +}, uo = function(i, e) { + return ((i[0] & 15) != 8 || i[0] >> 4 > 7 || (i[0] << 8 | i[1]) % 31) && Ie(6, "invalid zlib data"), (i[1] >> 5 & 1) == 1 && Ie(6, "invalid zlib data: " + (i[1] & 32 ? "need" : "unexpected") + " dictionary"), (i[1] >> 3 & 4) + 2; +}, Qi = /* @__PURE__ */ (function() { + function i(e, t) { + typeof e == "function" && (t = e, e = {}), this.ondata = t; + var s = e && e.dictionary && e.dictionary.subarray(-32768); + this.s = { i: 0, b: s ? s.length : 0 }, this.o = new ye(32768), this.p = new ye(0), s && this.o.set(s); + } + return i.prototype.e = function(e) { + if (this.ondata || Ie(5), this.d && Ie(4), !this.p.length) + this.p = e; + else if (e.length) { + var t = new ye(this.p.length + e.length); + t.set(this.p), t.set(e, this.p.length), this.p = t; + } + }, i.prototype.c = function(e) { + this.s.i = +(this.d = e || !1); + var t = this.s.b, s = Oi(this.p, this.s, this.o); + this.ondata(Ei(s, t, this.s.b), this.d), this.o = Ei(s, this.s.b - 32768), this.s.b = this.o.length, this.p = Ei(this.p, this.s.p / 8 | 0), this.s.p &= 7; + }, i.prototype.push = function(e, t) { + this.e(e), this.c(t); + }, i; +})(); +function fo(i, e) { + return Oi(i, { i: 2 }, e, e); +} +var mo = /* @__PURE__ */ (function() { + function i(e, t) { + this.v = 1, this.r = 0, Qi.call(this, e, t); + } + return i.prototype.push = function(e, t) { + if (Qi.prototype.e.call(this, e), this.r += e.length, this.v) { + var s = this.p.subarray(this.v - 1), r = s.length > 3 ? Tn(s) : 4; + if (r > s.length) { + if (!t) + return; + } else this.v > 1 && this.onmember && this.onmember(this.r - s.length); + this.p = s.subarray(r), this.v = 0; + } + Qi.prototype.c.call(this, t), this.s.f && !this.s.l && !t && (this.v = Mn(this.s.p) + 9, this.s = { i: 0 }, this.o = new ye(0), this.push(new ye(0), t)); + }, i; +})(); +function go(i, e) { + var t = Tn(i); + return t + 8 > i.length && Ie(6, "invalid gzip data"), Oi(i.subarray(t, -8), { i: 2 }, new ye(ho(i)), e); +} +function po(i, e) { + return Oi(i.subarray(uo(i), -4), { i: 2 }, e, e); +} +function Ao(i, e) { + return i[0] == 31 && i[1] == 139 && i[2] == 8 ? go(i, e) : (i[0] & 15) != 8 || i[0] >> 4 > 7 || (i[0] << 8 | i[1]) % 31 ? fo(i, e) : po(i, e); +} +var vo = typeof TextDecoder < "u" && /* @__PURE__ */ new TextDecoder(), xo = 0; +try { + vo.decode(co, { stream: !0 }), xo = 1; +} catch { +} +class Sn { + esize; + ecode; + edata; + littleEndian; + constructor(e, t, s, r) { + if (e % 16 != 0) + throw new Error("This does not appear to be a NIFTI extension"); + this.esize = e, this.ecode = t, this.edata = s, this.littleEndian = r; + } + /** + * Returns extension as ArrayBuffer. + * @returns {ArrayBuffer} + */ + toArrayBuffer() { + let e = new Uint8Array(this.esize), t = new Uint8Array(this.edata); + e.set(t, 8); + let s = new DataView(e.buffer); + return s.setInt32(0, this.esize, this.littleEndian), s.setInt32(4, this.ecode, this.littleEndian), e.buffer; + } +} +class z { + /*** Static Pseudo-constants ***/ + static crcTable = null; + static GUNZIP_MAGIC_COOKIE1 = 31; + static GUNZIP_MAGIC_COOKIE2 = 139; + /*** Static methods ***/ + static getStringAt(e, t, s) { + var r = "", a, n; + for (a = t; a < s; a += 1) + n = e.getUint8(a), n !== 0 && (r += String.fromCharCode(n)); + return r; + } + static getByteAt = function(e, t) { + return e.getUint8(t); + }; + static getShortAt = function(e, t, s) { + return e.getInt16(t, s); + }; + static getIntAt(e, t, s) { + return e.getInt32(t, s); + } + static getFloatAt(e, t, s) { + return e.getFloat32(t, s); + } + static getDoubleAt(e, t, s) { + return e.getFloat64(t, s); + } + static getInt64At(e, t, s) { + const r = e.getUint32(t, s), a = e.getInt32(t + 4, s); + let n; + return s ? n = a * 2 ** 32 + r : n = r * 2 ** 32 + a, a < 0 && (n += -1 * 2 ** 32 * 2 ** 32), n; + } + static getUint64At(e, t, s) { + const r = e.getUint32(t + (s ? 0 : 4), s), a = e.getUint32(t + (s ? 4 : 0), s); + return s ? a * 2 ** 32 + r : r * 2 ** 32 + a; + } + static getExtensionsAt(e, t, s, r) { + let a = [], n = t; + for (; n < r; ) { + let o = s, l = z.getIntAt(e, n, s); + if (!l) + break; + if (l + n > r && (o = !o, l = z.getIntAt(e, n, o), l + n > r)) + throw new Error("This does not appear to be a valid NIFTI extension"); + if (l % 16 != 0) + throw new Error("This does not appear to be a NIFTI extension"); + let c = z.getIntAt(e, n + 4, o), h = e.buffer.slice(n + 8, n + l), u = new Sn(l, c, h, o); + a.push(u), n += l; + } + return a; + } + static toArrayBuffer(e) { + var t, s, r; + for (t = new ArrayBuffer(e.length), s = new Uint8Array(t), r = 0; r < e.length; r += 1) + s[r] = e[r]; + return t; + } + static isString(e) { + return typeof e == "string" || e instanceof String; + } + static formatNumber(e, t = void 0) { + let s; + return z.isString(e) ? s = Number(e) : s = e, t ? s = s.toPrecision(5) : s = s.toPrecision(7), parseFloat(s); + } + // http://stackoverflow.com/questions/18638900/javascript-crc32 + static makeCRCTable() { + let e, t = []; + for (var s = 0; s < 256; s++) { + e = s; + for (var r = 0; r < 8; r++) + e = e & 1 ? 3988292384 ^ e >>> 1 : e >>> 1; + t[s] = e; + } + return t; + } + static crc32(e) { + z.crcTable || (z.crcTable = z.makeCRCTable()); + const t = z.crcTable; + let s = -1; + for (var r = 0; r < e.byteLength; r++) + s = s >>> 8 ^ t[(s ^ e.getUint8(r)) & 255]; + return (s ^ -1) >>> 0; + } +} +class H { + littleEndian = !1; + dim_info = 0; + dims = []; + intent_p1 = 0; + intent_p2 = 0; + intent_p3 = 0; + intent_code = 0; + datatypeCode = 0; + numBitsPerVoxel = 0; + slice_start = 0; + slice_end = 0; + slice_code = 0; + pixDims = []; + vox_offset = 0; + scl_slope = 1; + scl_inter = 0; + xyzt_units = 0; + cal_max = 0; + cal_min = 0; + slice_duration = 0; + toffset = 0; + description = ""; + aux_file = ""; + intent_name = ""; + qform_code = 0; + sform_code = 0; + quatern_a = 0; + quatern_b = 0; + quatern_c = 0; + quatern_d = 0; + qoffset_x = 0; + qoffset_y = 0; + qoffset_z = 0; + affine = [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]; + qfac = 1; + quatern_R; + magic = "0"; + isHDR = !1; + extensionFlag = [0, 0, 0, 0]; + extensionSize = 0; + extensionCode = 0; + extensions = []; + /*** Static Pseudo-constants ***/ + // datatype codes + static TYPE_NONE = 0; + static TYPE_BINARY = 1; + static TYPE_UINT8 = 2; + static TYPE_INT16 = 4; + static TYPE_INT32 = 8; + static TYPE_FLOAT32 = 16; + static TYPE_COMPLEX64 = 32; + static TYPE_FLOAT64 = 64; + static TYPE_RGB24 = 128; + static TYPE_INT8 = 256; + static TYPE_UINT16 = 512; + static TYPE_UINT32 = 768; + static TYPE_INT64 = 1024; + static TYPE_UINT64 = 1280; + static TYPE_FLOAT128 = 1536; + static TYPE_COMPLEX128 = 1792; + static TYPE_COMPLEX256 = 2048; + // transform codes + static XFORM_UNKNOWN = 0; + static XFORM_SCANNER_ANAT = 1; + static XFORM_ALIGNED_ANAT = 2; + static XFORM_TALAIRACH = 3; + static XFORM_MNI_152 = 4; + // unit codes + static SPATIAL_UNITS_MASK = 7; + static TEMPORAL_UNITS_MASK = 56; + static UNITS_UNKNOWN = 0; + static UNITS_METER = 1; + static UNITS_MM = 2; + static UNITS_MICRON = 3; + static UNITS_SEC = 8; + static UNITS_MSEC = 16; + static UNITS_USEC = 24; + static UNITS_HZ = 32; + static UNITS_PPM = 40; + static UNITS_RADS = 48; + // nifti1 codes + static MAGIC_COOKIE = 348; + static STANDARD_HEADER_SIZE = 348; + static MAGIC_NUMBER_LOCATION = 344; + static MAGIC_NUMBER = [110, 43, 49]; + // n+1 (.nii) + static MAGIC_NUMBER2 = [110, 105, 49]; + // ni1 (.hdr/.img) + static EXTENSION_HEADER_SIZE = 8; + /*** Prototype Methods ***/ + /** + * Reads the header data. + * @param {ArrayBuffer} data + */ + readHeader(e) { + var t = new DataView(e), s = z.getIntAt(t, 0, this.littleEndian), r, a, n, o; + if (s !== H.MAGIC_COOKIE && (this.littleEndian = !0, s = z.getIntAt(t, 0, this.littleEndian)), s !== H.MAGIC_COOKIE) + throw new Error("This does not appear to be a NIFTI file!"); + for (this.dim_info = z.getByteAt(t, 39), r = 0; r < 8; r += 1) + o = 40 + r * 2, this.dims[r] = z.getShortAt(t, o, this.littleEndian); + for (this.intent_p1 = z.getFloatAt(t, 56, this.littleEndian), this.intent_p2 = z.getFloatAt(t, 60, this.littleEndian), this.intent_p3 = z.getFloatAt(t, 64, this.littleEndian), this.intent_code = z.getShortAt(t, 68, this.littleEndian), this.datatypeCode = z.getShortAt(t, 70, this.littleEndian), this.numBitsPerVoxel = z.getShortAt(t, 72, this.littleEndian), this.slice_start = z.getShortAt(t, 74, this.littleEndian), r = 0; r < 8; r += 1) + o = 76 + r * 4, this.pixDims[r] = z.getFloatAt(t, o, this.littleEndian); + if (this.vox_offset = z.getFloatAt(t, 108, this.littleEndian), this.scl_slope = z.getFloatAt(t, 112, this.littleEndian), this.scl_inter = z.getFloatAt(t, 116, this.littleEndian), this.slice_end = z.getShortAt(t, 120, this.littleEndian), this.slice_code = z.getByteAt(t, 122), this.xyzt_units = z.getByteAt(t, 123), this.cal_max = z.getFloatAt(t, 124, this.littleEndian), this.cal_min = z.getFloatAt(t, 128, this.littleEndian), this.slice_duration = z.getFloatAt(t, 132, this.littleEndian), this.toffset = z.getFloatAt(t, 136, this.littleEndian), this.description = z.getStringAt(t, 148, 228), this.aux_file = z.getStringAt(t, 228, 252), this.qform_code = z.getShortAt(t, 252, this.littleEndian), this.sform_code = z.getShortAt(t, 254, this.littleEndian), this.quatern_b = z.getFloatAt(t, 256, this.littleEndian), this.quatern_c = z.getFloatAt(t, 260, this.littleEndian), this.quatern_d = z.getFloatAt(t, 264, this.littleEndian), this.quatern_a = Math.sqrt(1 - (Math.pow(this.quatern_b, 2) + Math.pow(this.quatern_c, 2) + Math.pow(this.quatern_d, 2))), this.qoffset_x = z.getFloatAt(t, 268, this.littleEndian), this.qoffset_y = z.getFloatAt(t, 272, this.littleEndian), this.qoffset_z = z.getFloatAt(t, 276, this.littleEndian), this.qform_code < 1 && this.sform_code < 1 && (this.affine[0][0] = this.pixDims[1], this.affine[1][1] = this.pixDims[2], this.affine[2][2] = this.pixDims[3]), this.qform_code > 0 && this.sform_code < this.qform_code) { + const l = this.quatern_a, c = this.quatern_b, h = this.quatern_c, u = this.quatern_d; + for (this.qfac = this.pixDims[0] === 0 ? 1 : this.pixDims[0], this.quatern_R = [ + [l * l + c * c - h * h - u * u, 2 * c * h - 2 * l * u, 2 * c * u + 2 * l * h], + [2 * c * h + 2 * l * u, l * l + h * h - c * c - u * u, 2 * h * u - 2 * l * c], + [2 * c * u - 2 * l * h, 2 * h * u + 2 * l * c, l * l + u * u - h * h - c * c] + ], a = 0; a < 3; a += 1) + for (n = 0; n < 3; n += 1) + this.affine[a][n] = this.quatern_R[a][n] * this.pixDims[n + 1], n === 2 && (this.affine[a][n] *= this.qfac); + this.affine[0][3] = this.qoffset_x, this.affine[1][3] = this.qoffset_y, this.affine[2][3] = this.qoffset_z; + } else if (this.sform_code > 0) + for (a = 0; a < 3; a += 1) + for (n = 0; n < 4; n += 1) + o = 280 + (a * 4 + n) * 4, this.affine[a][n] = z.getFloatAt(t, o, this.littleEndian); + if (this.affine[3][0] = 0, this.affine[3][1] = 0, this.affine[3][2] = 0, this.affine[3][3] = 1, this.intent_name = z.getStringAt(t, 328, 344), this.magic = z.getStringAt(t, 344, 348), this.isHDR = this.magic === String.fromCharCode.apply(null, H.MAGIC_NUMBER2), t.byteLength > H.MAGIC_COOKIE) { + this.extensionFlag[0] = z.getByteAt(t, 348), this.extensionFlag[1] = z.getByteAt(t, 349), this.extensionFlag[2] = z.getByteAt(t, 350), this.extensionFlag[3] = z.getByteAt(t, 351); + let l = !0; + !this.isHDR && this.vox_offset <= 352 && (l = !1), t.byteLength <= 368 && (l = !1), l && this.extensionFlag[0] && (this.extensions = z.getExtensionsAt(t, this.getExtensionLocation(), this.littleEndian, this.vox_offset), this.extensionSize = this.extensions[0].esize, this.extensionCode = this.extensions[0].ecode); + } + } + /** + * Returns a formatted string of header fields. + * @returns {string} + */ + toFormattedString() { + var e = z.formatNumber, t = ""; + return t += "Dim Info = " + this.dim_info + ` +`, t += "Image Dimensions (1-8): " + this.dims[0] + ", " + this.dims[1] + ", " + this.dims[2] + ", " + this.dims[3] + ", " + this.dims[4] + ", " + this.dims[5] + ", " + this.dims[6] + ", " + this.dims[7] + ` +`, t += "Intent Parameters (1-3): " + this.intent_p1 + ", " + this.intent_p2 + ", " + this.intent_p3 + ` +`, t += "Intent Code = " + this.intent_code + ` +`, t += "Datatype = " + this.datatypeCode + " (" + this.getDatatypeCodeString(this.datatypeCode) + `) +`, t += "Bits Per Voxel = " + this.numBitsPerVoxel + ` +`, t += "Slice Start = " + this.slice_start + ` +`, t += "Voxel Dimensions (1-8): " + e(this.pixDims[0]) + ", " + e(this.pixDims[1]) + ", " + e(this.pixDims[2]) + ", " + e(this.pixDims[3]) + ", " + e(this.pixDims[4]) + ", " + e(this.pixDims[5]) + ", " + e(this.pixDims[6]) + ", " + e(this.pixDims[7]) + ` +`, t += "Image Offset = " + this.vox_offset + ` +`, t += "Data Scale: Slope = " + e(this.scl_slope) + " Intercept = " + e(this.scl_inter) + ` +`, t += "Slice End = " + this.slice_end + ` +`, t += "Slice Code = " + this.slice_code + ` +`, t += "Units Code = " + this.xyzt_units + " (" + this.getUnitsCodeString(H.SPATIAL_UNITS_MASK & this.xyzt_units) + ", " + this.getUnitsCodeString(H.TEMPORAL_UNITS_MASK & this.xyzt_units) + `) +`, t += "Display Range: Max = " + e(this.cal_max) + " Min = " + e(this.cal_min) + ` +`, t += "Slice Duration = " + this.slice_duration + ` +`, t += "Time Axis Shift = " + this.toffset + ` +`, t += 'Description: "' + this.description + `" +`, t += 'Auxiliary File: "' + this.aux_file + `" +`, t += "Q-Form Code = " + this.qform_code + " (" + this.getTransformCodeString(this.qform_code) + `) +`, t += "S-Form Code = " + this.sform_code + " (" + this.getTransformCodeString(this.sform_code) + `) +`, t += "Quaternion Parameters: b = " + e(this.quatern_b) + " c = " + e(this.quatern_c) + " d = " + e(this.quatern_d) + ` +`, t += "Quaternion Offsets: x = " + this.qoffset_x + " y = " + this.qoffset_y + " z = " + this.qoffset_z + ` +`, t += "S-Form Parameters X: " + e(this.affine[0][0]) + ", " + e(this.affine[0][1]) + ", " + e(this.affine[0][2]) + ", " + e(this.affine[0][3]) + ` +`, t += "S-Form Parameters Y: " + e(this.affine[1][0]) + ", " + e(this.affine[1][1]) + ", " + e(this.affine[1][2]) + ", " + e(this.affine[1][3]) + ` +`, t += "S-Form Parameters Z: " + e(this.affine[2][0]) + ", " + e(this.affine[2][1]) + ", " + e(this.affine[2][2]) + ", " + e(this.affine[2][3]) + ` +`, t += 'Intent Name: "' + this.intent_name + `" +`, this.extensionFlag[0] && (t += "Extension: Size = " + this.extensionSize + " Code = " + this.extensionCode + ` +`), t; + } + /** + * Returns a human-readable string of datatype. + * @param {number} code + * @returns {string} + */ + getDatatypeCodeString = function(e) { + return e === H.TYPE_UINT8 ? "1-Byte Unsigned Integer" : e === H.TYPE_INT16 ? "2-Byte Signed Integer" : e === H.TYPE_INT32 ? "4-Byte Signed Integer" : e === H.TYPE_FLOAT32 ? "4-Byte Float" : e === H.TYPE_FLOAT64 ? "8-Byte Float" : e === H.TYPE_RGB24 ? "RGB" : e === H.TYPE_INT8 ? "1-Byte Signed Integer" : e === H.TYPE_UINT16 ? "2-Byte Unsigned Integer" : e === H.TYPE_UINT32 ? "4-Byte Unsigned Integer" : e === H.TYPE_INT64 ? "8-Byte Signed Integer" : e === H.TYPE_UINT64 ? "8-Byte Unsigned Integer" : "Unknown"; + }; + /** + * Returns a human-readable string of transform type. + * @param {number} code + * @returns {string} + */ + getTransformCodeString = function(e) { + return e === H.XFORM_SCANNER_ANAT ? "Scanner" : e === H.XFORM_ALIGNED_ANAT ? "Aligned" : e === H.XFORM_TALAIRACH ? "Talairach" : e === H.XFORM_MNI_152 ? "MNI" : "Unknown"; + }; + /** + * Returns a human-readable string of spatial and temporal units. + * @param {number} code + * @returns {string} + */ + getUnitsCodeString = function(e) { + return e === H.UNITS_METER ? "Meters" : e === H.UNITS_MM ? "Millimeters" : e === H.UNITS_MICRON ? "Microns" : e === H.UNITS_SEC ? "Seconds" : e === H.UNITS_MSEC ? "Milliseconds" : e === H.UNITS_USEC ? "Microseconds" : e === H.UNITS_HZ ? "Hz" : e === H.UNITS_PPM ? "PPM" : e === H.UNITS_RADS ? "Rads" : "Unknown"; + }; + /** + * Returns the qform matrix. + * @returns {Array.>} + */ + getQformMat() { + return this.convertNiftiQFormToNiftiSForm(this.quatern_b, this.quatern_c, this.quatern_d, this.qoffset_x, this.qoffset_y, this.qoffset_z, this.pixDims[1], this.pixDims[2], this.pixDims[3], this.pixDims[0]); + } + /** + * Converts qform to an affine. (See http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.c) + * @param {number} qb + * @param {number} qc + * @param {number} qd + * @param {number} qx + * @param {number} qy + * @param {number} qz + * @param {number} dx + * @param {number} dy + * @param {number} dz + * @param {number} qfac + * @returns {Array.>} + */ + convertNiftiQFormToNiftiSForm(e, t, s, r, a, n, o, l, c, h) { + var u = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0] + ], d, f = e, g = t, m = s, p, v, A; + return u[3][0] = u[3][1] = u[3][2] = 0, u[3][3] = 1, d = 1 - (f * f + g * g + m * m), d < 1e-7 ? (d = 1 / Math.sqrt(f * f + g * g + m * m), f *= d, g *= d, m *= d, d = 0) : d = Math.sqrt(d), p = o > 0 ? o : 1, v = l > 0 ? l : 1, A = c > 0 ? c : 1, h < 0 && (A = -A), u[0][0] = (d * d + f * f - g * g - m * m) * p, u[0][1] = 2 * (f * g - d * m) * v, u[0][2] = 2 * (f * m + d * g) * A, u[1][0] = 2 * (f * g + d * m) * p, u[1][1] = (d * d + g * g - f * f - m * m) * v, u[1][2] = 2 * (g * m - d * f) * A, u[2][0] = 2 * (f * m - d * g) * p, u[2][1] = 2 * (g * m + d * f) * v, u[2][2] = (d * d + m * m - g * g - f * f) * A, u[0][3] = r, u[1][3] = a, u[2][3] = n, u; + } + /** + * Converts sform to an orientation string (e.g., XYZ+--). (See http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.c) + * @param {Array.>} R + * @returns {string} + */ + convertNiftiSFormToNEMA(e) { + var t, s, r, a, n, o, l, c, h, u, d, f, g, m, p, v, A, x, w, D, b, C, E, F, M, S, T, k, B, U, V, N, P, L; + if (p = 0, T = [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0] + ], k = [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0] + ], t = e[0][0], s = e[0][1], r = e[0][2], a = e[1][0], n = e[1][1], o = e[1][2], l = e[2][0], c = e[2][1], h = e[2][2], u = Math.sqrt(t * t + a * a + l * l), u === 0 || (t /= u, a /= u, l /= u, u = Math.sqrt(s * s + n * n + c * c), u === 0)) + return null; + if (s /= u, n /= u, c /= u, u = t * s + a * n + l * c, Math.abs(u) > 1e-4) { + if (s -= u * t, n -= u * a, c -= u * l, u = Math.sqrt(s * s + n * n + c * c), u === 0) + return null; + s /= u, n /= u, c /= u; + } + if (u = Math.sqrt(r * r + o * o + h * h), u === 0 ? (r = a * c - l * n, o = l * s - c * t, h = t * n - a * s) : (r /= u, o /= u, h /= u), u = t * r + a * o + l * h, Math.abs(u) > 1e-4) { + if (r -= u * t, o -= u * a, h -= u * l, u = Math.sqrt(r * r + o * o + h * h), u === 0) + return null; + r /= u, o /= u, h /= u; + } + if (u = s * r + n * o + c * h, Math.abs(u) > 1e-4) { + if (r -= u * s, o -= u * n, h -= u * c, u = Math.sqrt(r * r + o * o + h * h), u === 0) + return null; + r /= u, o /= u, h /= u; + } + if (T[0][0] = t, T[0][1] = s, T[0][2] = r, T[1][0] = a, T[1][1] = n, T[1][2] = o, T[2][0] = l, T[2][1] = c, T[2][2] = h, d = this.nifti_mat33_determ(T), d === 0) + return null; + for (S = -666, w = C = E = F = 1, D = 2, b = 3, g = 1; g <= 3; g += 1) + for (m = 1; m <= 3; m += 1) + if (g !== m) { + for (p = 1; p <= 3; p += 1) + if (!(g === p || m === p)) + for (k[0][0] = k[0][1] = k[0][2] = k[1][0] = k[1][1] = k[1][2] = k[2][0] = k[2][1] = k[2][2] = 0, v = -1; v <= 1; v += 2) + for (A = -1; A <= 1; A += 2) + for (x = -1; x <= 1; x += 2) + k[0][g - 1] = v, k[1][m - 1] = A, k[2][p - 1] = x, f = this.nifti_mat33_determ(k), f * d > 0 && (M = this.nifti_mat33_mul(k, T), u = M[0][0] + M[1][1] + M[2][2], u > S && (S = u, w = g, D = m, b = p, C = v, E = A, F = x)); + } + switch (B = U = V = N = P = L = "", w * C) { + case 1: + B = "X", N = "+"; + break; + case -1: + B = "X", N = "-"; + break; + case 2: + B = "Y", N = "+"; + break; + case -2: + B = "Y", N = "-"; + break; + case 3: + B = "Z", N = "+"; + break; + case -3: + B = "Z", N = "-"; + break; + } + switch (D * E) { + case 1: + U = "X", P = "+"; + break; + case -1: + U = "X", P = "-"; + break; + case 2: + U = "Y", P = "+"; + break; + case -2: + U = "Y", P = "-"; + break; + case 3: + U = "Z", P = "+"; + break; + case -3: + U = "Z", P = "-"; + break; + } + switch (b * F) { + case 1: + V = "X", L = "+"; + break; + case -1: + V = "X", L = "-"; + break; + case 2: + V = "Y", L = "+"; + break; + case -2: + V = "Y", L = "-"; + break; + case 3: + V = "Z", L = "+"; + break; + case -3: + V = "Z", L = "-"; + break; + } + return B + U + V + N + P + L; + } + nifti_mat33_mul = function(e, t) { + var s = [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0] + ], r, a; + for (r = 0; r < 3; r += 1) + for (a = 0; a < 3; a += 1) + s[r][a] = e[r][0] * t[0][a] + e[r][1] * t[1][a] + e[r][2] * t[2][a]; + return s; + }; + nifti_mat33_determ = function(e) { + var t, s, r, a, n, o, l, c, h; + return t = e[0][0], s = e[0][1], r = e[0][2], a = e[1][0], n = e[1][1], o = e[1][2], l = e[2][0], c = e[2][1], h = e[2][2], t * n * h - t * c * o - a * s * h + a * c * r + l * s * o - l * n * r; + }; + /** + * Returns the byte index of the extension. + * @returns {number} + */ + getExtensionLocation() { + return H.MAGIC_COOKIE + 4; + } + /** + * Returns the extension size. + * @param {DataView} data + * @returns {number} + */ + getExtensionSize(e) { + return z.getIntAt(e, this.getExtensionLocation(), this.littleEndian); + } + /** + * Returns the extension code. + * @param {DataView} data + * @returns {number} + */ + getExtensionCode(e) { + return z.getIntAt(e, this.getExtensionLocation() + 4, this.littleEndian); + } + /** + * Adds an extension + * @param {NIFTIEXTENSION} extension + * @param {number} index + */ + addExtension(e, t = -1) { + t == -1 ? this.extensions.push(e) : this.extensions.splice(t, 0, e), this.vox_offset += e.esize; + } + /** + * Removes an extension + * @param {number} index + */ + removeExtension(e) { + let t = this.extensions[e]; + t && (this.vox_offset -= t.esize), this.extensions.splice(e, 1); + } + /** + * Returns header as ArrayBuffer. + * @param {boolean} includeExtensions - should extension bytes be included + * @returns {ArrayBuffer} + */ + toArrayBuffer(e = !1) { + let r = 352; + if (e) + for (let l of this.extensions) + r += l.esize; + let a = new Uint8Array(r), n = new DataView(a.buffer); + n.setInt32(0, 348, this.littleEndian), n.setUint8(39, this.dim_info); + for (let l = 0; l < 8; l++) + n.setUint16(40 + 2 * l, this.dims[l], this.littleEndian); + n.setFloat32(56, this.intent_p1, this.littleEndian), n.setFloat32(60, this.intent_p2, this.littleEndian), n.setFloat32(64, this.intent_p3, this.littleEndian), n.setInt16(68, this.intent_code, this.littleEndian), n.setInt16(70, this.datatypeCode, this.littleEndian), n.setInt16(72, this.numBitsPerVoxel, this.littleEndian), n.setInt16(74, this.slice_start, this.littleEndian); + for (let l = 0; l < 8; l++) + n.setFloat32(76 + 4 * l, this.pixDims[l], this.littleEndian); + n.setFloat32(108, this.vox_offset, this.littleEndian), n.setFloat32(112, this.scl_slope, this.littleEndian), n.setFloat32(116, this.scl_inter, this.littleEndian), n.setInt16(120, this.slice_end, this.littleEndian), n.setUint8(122, this.slice_code), n.setUint8(123, this.xyzt_units), n.setFloat32(124, this.cal_max, this.littleEndian), n.setFloat32(128, this.cal_min, this.littleEndian), n.setFloat32(132, this.slice_duration, this.littleEndian), n.setFloat32(136, this.toffset, this.littleEndian), a.set(new TextEncoder().encode(this.description), 148), a.set(new TextEncoder().encode(this.aux_file), 228), n.setInt16(252, this.qform_code, this.littleEndian), n.setInt16(254, this.sform_code, this.littleEndian), n.setFloat32(256, this.quatern_b, this.littleEndian), n.setFloat32(260, this.quatern_c, this.littleEndian), n.setFloat32(264, this.quatern_d, this.littleEndian), n.setFloat32(268, this.qoffset_x, this.littleEndian), n.setFloat32(272, this.qoffset_y, this.littleEndian), n.setFloat32(276, this.qoffset_z, this.littleEndian); + const o = this.affine.flat(); + for (let l = 0; l < 12; l++) + n.setFloat32(280 + 4 * l, o[l], this.littleEndian); + if (a.set(new TextEncoder().encode(this.intent_name), 328), a.set(new TextEncoder().encode(this.magic), 344), e) { + a.set(Uint8Array.from([1, 0, 0, 0]), 348); + let l = this.getExtensionLocation(); + for (const c of this.extensions) + n.setInt32(l, c.esize, c.littleEndian), n.setInt32(l + 4, c.ecode, c.littleEndian), a.set(new Uint8Array(c.edata), l + 8), l += c.esize; + } else + a.set(new Uint8Array(4).fill(0), 348); + return a.buffer; + } +} +class De { + littleEndian = !1; + dim_info = 0; + dims = []; + intent_p1 = 0; + intent_p2 = 0; + intent_p3 = 0; + intent_code = 0; + datatypeCode = 0; + numBitsPerVoxel = 0; + slice_start = 0; + slice_end = 0; + slice_code = 0; + pixDims = []; + vox_offset = 0; + scl_slope = 1; + scl_inter = 0; + xyzt_units = 0; + cal_max = 0; + cal_min = 0; + slice_duration = 0; + toffset = 0; + description = ""; + aux_file = ""; + intent_name = ""; + qform_code = 0; + sform_code = 0; + quatern_b = 0; + quatern_c = 0; + quatern_d = 0; + qoffset_x = 0; + qoffset_y = 0; + qoffset_z = 0; + affine = [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]; + magic = "0"; + extensionFlag = [0, 0, 0, 0]; + extensions = []; + extensionSize = 0; + extensionCode = 0; + /*** Static Pseudo-constants ***/ + static MAGIC_COOKIE = 540; + static MAGIC_NUMBER_LOCATION = 4; + static MAGIC_NUMBER = [110, 43, 50, 0, 13, 10, 26, 10]; + // n+2\0 + static MAGIC_NUMBER2 = [110, 105, 50, 0, 13, 10, 26, 10]; + // ni2\0 + /*** Prototype Methods ***/ + /** + * Reads the header data. + * @param {ArrayBuffer} data + */ + readHeader(e) { + var t = new DataView(e), s = z.getIntAt(t, 0, this.littleEndian), r, a, n, o; + if (s !== De.MAGIC_COOKIE && (this.littleEndian = !0, s = z.getIntAt(t, 0, this.littleEndian)), s !== De.MAGIC_COOKIE) + throw new Error("This does not appear to be a NIFTI file!"); + for (this.magic = z.getStringAt(t, 4, 12), this.datatypeCode = z.getShortAt(t, 12, this.littleEndian), this.numBitsPerVoxel = z.getShortAt(t, 14, this.littleEndian), r = 0; r < 8; r += 1) + o = 16 + r * 8, this.dims[r] = z.getInt64At(t, o, this.littleEndian); + for (this.intent_p1 = z.getDoubleAt(t, 80, this.littleEndian), this.intent_p2 = z.getDoubleAt(t, 88, this.littleEndian), this.intent_p3 = z.getDoubleAt(t, 96, this.littleEndian), r = 0; r < 8; r += 1) + o = 104 + r * 8, this.pixDims[r] = z.getDoubleAt(t, o, this.littleEndian); + for (this.vox_offset = z.getInt64At(t, 168, this.littleEndian), this.scl_slope = z.getDoubleAt(t, 176, this.littleEndian), this.scl_inter = z.getDoubleAt(t, 184, this.littleEndian), this.cal_max = z.getDoubleAt(t, 192, this.littleEndian), this.cal_min = z.getDoubleAt(t, 200, this.littleEndian), this.slice_duration = z.getDoubleAt(t, 208, this.littleEndian), this.toffset = z.getDoubleAt(t, 216, this.littleEndian), this.slice_start = z.getInt64At(t, 224, this.littleEndian), this.slice_end = z.getInt64At(t, 232, this.littleEndian), this.description = z.getStringAt(t, 240, 320), this.aux_file = z.getStringAt(t, 320, 344), this.qform_code = z.getIntAt(t, 344, this.littleEndian), this.sform_code = z.getIntAt(t, 348, this.littleEndian), this.quatern_b = z.getDoubleAt(t, 352, this.littleEndian), this.quatern_c = z.getDoubleAt(t, 360, this.littleEndian), this.quatern_d = z.getDoubleAt(t, 368, this.littleEndian), this.qoffset_x = z.getDoubleAt(t, 376, this.littleEndian), this.qoffset_y = z.getDoubleAt(t, 384, this.littleEndian), this.qoffset_z = z.getDoubleAt(t, 392, this.littleEndian), a = 0; a < 3; a += 1) + for (n = 0; n < 4; n += 1) + o = 400 + (a * 4 + n) * 8, this.affine[a][n] = z.getDoubleAt(t, o, this.littleEndian); + this.affine[3][0] = 0, this.affine[3][1] = 0, this.affine[3][2] = 0, this.affine[3][3] = 1, this.slice_code = z.getIntAt(t, 496, this.littleEndian), this.xyzt_units = z.getIntAt(t, 500, this.littleEndian), this.intent_code = z.getIntAt(t, 504, this.littleEndian), this.intent_name = z.getStringAt(t, 508, 524), this.dim_info = z.getByteAt(t, 524), t.byteLength > De.MAGIC_COOKIE && (this.extensionFlag[0] = z.getByteAt(t, 540), this.extensionFlag[1] = z.getByteAt(t, 541), this.extensionFlag[2] = z.getByteAt(t, 542), this.extensionFlag[3] = z.getByteAt(t, 543), this.extensionFlag[0] && (this.extensions = z.getExtensionsAt(t, this.getExtensionLocation(), this.littleEndian, this.vox_offset), this.extensionSize = this.extensions[0].esize, this.extensionCode = this.extensions[0].ecode)); + } + /** + * Returns a formatted string of header fields. + * @returns {string} + */ + toFormattedString() { + var e = z.formatNumber, t = ""; + return t += "Datatype = " + +this.datatypeCode + " (" + this.getDatatypeCodeString(this.datatypeCode) + `) +`, t += "Bits Per Voxel = = " + this.numBitsPerVoxel + ` +`, t += "Image Dimensions (1-8): " + this.dims[0] + ", " + this.dims[1] + ", " + this.dims[2] + ", " + this.dims[3] + ", " + this.dims[4] + ", " + this.dims[5] + ", " + this.dims[6] + ", " + this.dims[7] + ` +`, t += "Intent Parameters (1-3): " + this.intent_p1 + ", " + this.intent_p2 + ", " + this.intent_p3 + ` +`, t += "Voxel Dimensions (1-8): " + e(this.pixDims[0]) + ", " + e(this.pixDims[1]) + ", " + e(this.pixDims[2]) + ", " + e(this.pixDims[3]) + ", " + e(this.pixDims[4]) + ", " + e(this.pixDims[5]) + ", " + e(this.pixDims[6]) + ", " + e(this.pixDims[7]) + ` +`, t += "Image Offset = " + this.vox_offset + ` +`, t += "Data Scale: Slope = " + e(this.scl_slope) + " Intercept = " + e(this.scl_inter) + ` +`, t += "Display Range: Max = " + e(this.cal_max) + " Min = " + e(this.cal_min) + ` +`, t += "Slice Duration = " + this.slice_duration + ` +`, t += "Time Axis Shift = " + this.toffset + ` +`, t += "Slice Start = " + this.slice_start + ` +`, t += "Slice End = " + this.slice_end + ` +`, t += 'Description: "' + this.description + `" +`, t += 'Auxiliary File: "' + this.aux_file + `" +`, t += "Q-Form Code = " + this.qform_code + " (" + this.getTransformCodeString(this.qform_code) + `) +`, t += "S-Form Code = " + this.sform_code + " (" + this.getTransformCodeString(this.sform_code) + `) +`, t += "Quaternion Parameters: b = " + e(this.quatern_b) + " c = " + e(this.quatern_c) + " d = " + e(this.quatern_d) + ` +`, t += "Quaternion Offsets: x = " + this.qoffset_x + " y = " + this.qoffset_y + " z = " + this.qoffset_z + ` +`, t += "S-Form Parameters X: " + e(this.affine[0][0]) + ", " + e(this.affine[0][1]) + ", " + e(this.affine[0][2]) + ", " + e(this.affine[0][3]) + ` +`, t += "S-Form Parameters Y: " + e(this.affine[1][0]) + ", " + e(this.affine[1][1]) + ", " + e(this.affine[1][2]) + ", " + e(this.affine[1][3]) + ` +`, t += "S-Form Parameters Z: " + e(this.affine[2][0]) + ", " + e(this.affine[2][1]) + ", " + e(this.affine[2][2]) + ", " + e(this.affine[2][3]) + ` +`, t += "Slice Code = " + this.slice_code + ` +`, t += "Units Code = " + this.xyzt_units + " (" + this.getUnitsCodeString(H.SPATIAL_UNITS_MASK & this.xyzt_units) + ", " + this.getUnitsCodeString(H.TEMPORAL_UNITS_MASK & this.xyzt_units) + `) +`, t += "Intent Code = " + this.intent_code + ` +`, t += 'Intent Name: "' + this.intent_name + `" +`, t += "Dim Info = " + this.dim_info + ` +`, t; + } + /** + * Returns the byte index of the extension. + * @returns {number} + */ + getExtensionLocation = function() { + return De.MAGIC_COOKIE + 4; + }; + /** + * Returns the extension size. + * @param {DataView} data + * @returns {number} + */ + getExtensionSize = H.prototype.getExtensionSize; + /** + * Returns the extension code. + * @param {DataView} data + * @returns {number} + */ + getExtensionCode = H.prototype.getExtensionCode; + /** + * Adds an extension + * @param {NIFTIEXTENSION} extension + * @param {number} index + */ + addExtension = H.prototype.addExtension; + /** + * Removes an extension + * @param {number} index + */ + removeExtension = H.prototype.removeExtension; + /** + * Returns a human-readable string of datatype. + * @param {number} code + * @returns {string} + */ + getDatatypeCodeString = H.prototype.getDatatypeCodeString; + /** + * Returns a human-readable string of transform type. + * @param {number} code + * @returns {string} + */ + getTransformCodeString = H.prototype.getTransformCodeString; + /** + * Returns a human-readable string of spatial and temporal units. + * @param {number} code + * @returns {string} + */ + getUnitsCodeString = H.prototype.getUnitsCodeString; + /** + * Returns the qform matrix. + * @returns {Array.>} + */ + getQformMat = H.prototype.getQformMat; + /** + * Converts qform to an affine. (See http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.c) + * @param {number} qb + * @param {number} qc + * @param {number} qd + * @param {number} qx + * @param {number} qy + * @param {number} qz + * @param {number} dx + * @param {number} dy + * @param {number} dz + * @param {number} qfac + * @returns {Array.>} + */ + convertNiftiQFormToNiftiSForm = H.prototype.convertNiftiQFormToNiftiSForm; + /** + * Converts sform to an orientation string (e.g., XYZ+--). (See http://nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.c) + * @param {Array.>} R + * @returns {string} + */ + convertNiftiSFormToNEMA = H.prototype.convertNiftiSFormToNEMA; + nifti_mat33_mul = H.prototype.nifti_mat33_mul; + nifti_mat33_determ = H.prototype.nifti_mat33_determ; + /** + * Returns header as ArrayBuffer. + * @param {boolean} includeExtensions - should extension bytes be included + * @returns {ArrayBuffer} + */ + toArrayBuffer(e = !1) { + let r = 544; + if (e) + for (let l of this.extensions) + r += l.esize; + let a = new Uint8Array(r), n = new DataView(a.buffer); + n.setInt32(0, 540, this.littleEndian), a.set(new TextEncoder().encode(this.magic), 4), n.setInt16(12, this.datatypeCode, this.littleEndian), n.setInt16(14, this.numBitsPerVoxel, this.littleEndian); + for (let l = 0; l < 8; l++) + n.setBigInt64(16 + 8 * l, BigInt(this.dims[l]), this.littleEndian); + n.setFloat64(80, this.intent_p1, this.littleEndian), n.setFloat64(88, this.intent_p2, this.littleEndian), n.setFloat64(96, this.intent_p3, this.littleEndian); + for (let l = 0; l < 8; l++) + n.setFloat64(104 + 8 * l, this.pixDims[l], this.littleEndian); + n.setBigInt64(168, BigInt(this.vox_offset), this.littleEndian), n.setFloat64(176, this.scl_slope, this.littleEndian), n.setFloat64(184, this.scl_inter, this.littleEndian), n.setFloat64(192, this.cal_max, this.littleEndian), n.setFloat64(200, this.cal_min, this.littleEndian), n.setFloat64(208, this.slice_duration, this.littleEndian), n.setFloat64(216, this.toffset, this.littleEndian), n.setBigInt64(224, BigInt(this.slice_start), this.littleEndian), n.setBigInt64(232, BigInt(this.slice_end), this.littleEndian), a.set(new TextEncoder().encode(this.description), 240), a.set(new TextEncoder().encode(this.aux_file), 320), n.setInt32(344, this.qform_code, this.littleEndian), n.setInt32(348, this.sform_code, this.littleEndian), n.setFloat64(352, this.quatern_b, this.littleEndian), n.setFloat64(360, this.quatern_c, this.littleEndian), n.setFloat64(368, this.quatern_d, this.littleEndian), n.setFloat64(376, this.qoffset_x, this.littleEndian), n.setFloat64(384, this.qoffset_y, this.littleEndian), n.setFloat64(392, this.qoffset_z, this.littleEndian); + const o = this.affine.flat(); + for (let l = 0; l < 12; l++) + n.setFloat64(400 + 8 * l, o[l], this.littleEndian); + if (n.setInt32(496, this.slice_code, this.littleEndian), n.setInt32(500, this.xyzt_units, this.littleEndian), n.setInt32(504, this.intent_code, this.littleEndian), a.set(new TextEncoder().encode(this.intent_name), 508), n.setUint8(524, this.dim_info), e) { + a.set(Uint8Array.from([1, 0, 0, 0]), 540); + let l = this.getExtensionLocation(); + for (const c of this.extensions) + n.setInt32(l, c.esize, c.littleEndian), n.setInt32(l + 4, c.ecode, c.littleEndian), a.set(new Uint8Array(c.edata), l + 8), l += c.esize; + } else + a.set(new Uint8Array(4).fill(0), 540); + return a.buffer; + } +} +function wo(i, e = !1) { + var t, s, r, a; + return i.byteLength < H.STANDARD_HEADER_SIZE ? !1 : (t = new DataView(i), t && (s = t.getUint8(H.MAGIC_NUMBER_LOCATION)), r = t.getUint8(H.MAGIC_NUMBER_LOCATION + 1), a = t.getUint8(H.MAGIC_NUMBER_LOCATION + 2), e && s === H.MAGIC_NUMBER2[0] && r === H.MAGIC_NUMBER2[1] && a === H.MAGIC_NUMBER2[2] ? !0 : s === H.MAGIC_NUMBER[0] && r === H.MAGIC_NUMBER[1] && a === H.MAGIC_NUMBER[2]); +} +function bo(i, e = !1) { + var t, s, r, a; + return i.byteLength < H.STANDARD_HEADER_SIZE ? !1 : (t = new DataView(i), s = t.getUint8(De.MAGIC_NUMBER_LOCATION), r = t.getUint8(De.MAGIC_NUMBER_LOCATION + 1), a = t.getUint8(De.MAGIC_NUMBER_LOCATION + 2), e && s === De.MAGIC_NUMBER2[0] && r === De.MAGIC_NUMBER2[1] && a === De.MAGIC_NUMBER2[2] ? !0 : s === De.MAGIC_NUMBER[0] && r === De.MAGIC_NUMBER[1] && a === De.MAGIC_NUMBER[2]); +} +function Ss(i) { + var e, t, s; + return !!(i && (e = new DataView(i), t = e.getUint8(0), s = e.getUint8(1), t === z.GUNZIP_MAGIC_COOKIE1 || s === z.GUNZIP_MAGIC_COOKIE2)); +} +function yo(i) { + return Ao(new Uint8Array(i)).buffer; +} +async function Co(i) { + const e = new Uint8Array(i), t = e[0] === 31 && e[1] === 139 && e[2] === 8 ? "gzip" : e[0] === 120 && (e[1] === 1 || e[1] === 94 || e[1] === 156 || e[1] === 218) ? "deflate" : "deflate-raw", s = new DecompressionStream(t), r = s.writable.getWriter(); + r.write(e).catch(console.error); + const a = r.close().catch(console.error), o = await new Response(s.readable).arrayBuffer(); + return await a, o; +} +async function mr(i, e = 1 / 0) { + const t = (f) => f[0] === 31 && f[1] === 139 && f[2] === 8 ? "gzip" : f[0] === 120 && [1, 94, 156, 218].includes(f[1]) ? "deflate" : "deflate-raw", s = new Uint8Array(i), r = t(s), a = new DecompressionStream(r), n = new TransformStream({ + transform(f, g) { + g.enqueue(f); + }, + flush(f) { + f.terminate(); + } + }), { readable: o, writable: l } = a, c = l.getWriter(), h = o.pipeThrough(n).getReader(); + c.write(s).catch((f) => { + f instanceof Error && f.name === "AbortError" || console.error("Error during write:", f); + }); + const u = []; + let d = 0; + try { + for (; d < e; ) { + const { done: f, value: g } = await h.read(); + if (f) + break; + const m = e - d, p = g.subarray(0, Math.min(g.length, m)); + if (u.push(p), d += p.length, d >= e) { + await Promise.all([ + h.cancel().catch(() => { + }), + c.abort().catch(() => { + }) + ]); + break; + } + } + } catch (f) { + f instanceof Error && f.name === "AbortError" || console.error("Error during decompression:", f); + } finally { + await Promise.allSettled([ + h.cancel().catch(() => { + }), + c.close().catch(() => { + }) + ]); + } + return u.length === 1 ? u[0].buffer : u.reduce((f, g) => { + const m = new Uint8Array(f.byteLength + g.byteLength); + return m.set(new Uint8Array(f), 0), m.set(g, f.byteLength), m.buffer; + }, new ArrayBuffer(0)); +} +function Do(i, e = !1) { + let t = null; + if (Ss(i) && (i = yo(i)), wo(i, e) ? t = new H() : bo(i, e) && (t = new De()), t) + t.readHeader(i); + else + throw new Error("That file does not appear to be NIFTI!"); + return t; +} +async function Ht(i, e = !1) { + if (!Ss(i)) + return Do(i, e); + let t = null, s = await mr(i, 540), r = !0, a = !0; + var n = new DataView(s); + const o = n.getInt32(0, !0), l = n.getInt32(0, !1); + if (o !== 348) if (l === 348) + r = !1; + else if (o === 540) + a = !1; + else if (l === 540) + a = !1, r = !1; + else + throw new Error("That file does not appear to be NIFTI!"); + let c = Math.round(n.getFloat32(108, r)); + return De && (c = z.getUint64At(n, 168, r)), c > s.byteLength && (s = await mr(i, c)), a ? t = new H() : t = new De(), t.readHeader(s), t; +} +function Eo(i) { + return i.extensionFlag[0] != 0; +} +function gr(i, e) { + var t = i.vox_offset, s = 1, r = 1; + i.dims[4] && (s = i.dims[4]), i.dims[5] && (r = i.dims[5]); + var a = i.dims[1] * i.dims[2] * i.dims[3] * s * r * (i.numBitsPerVoxel / 8); + return e.slice(t, t + a); +} +function In(i, e, t, s = {}) { + return e !== void 0 && t !== void 0 && (s = { + ...s, + headers: { + ...s.headers, + Range: `bytes=${e}-${e + t - 1}` + } + }), fetch(i, s); +} +function Fo(i, e) { + return { + ...i, + ...e, + headers: { + ...i.headers, + ...e.headers + } + }; +} +function pr(i, e) { + const t = typeof i == "string" ? new URL(i) : i; + t.pathname.endsWith("/") || (t.pathname += "/"); + const s = new URL(e.slice(1), t); + return s.search = t.search, s; +} +async function Ar(i) { + if (i.status !== 404) { + if (i.status === 200 || i.status === 206) + return new Uint8Array(await i.arrayBuffer()); + throw new Error(`Unexpected response status ${i.status} ${i.statusText}`); + } +} +async function Mo(i, e, t, s) { + if (s) + return fetch(i, { + ...t, + headers: { ...t.headers, Range: `bytes=-${e}` } + }); + let r = await fetch(i, { ...t, method: "HEAD" }); + if (!r.ok) + return r; + let a = r.headers.get("Content-Length"), n = Number(a); + return In(i, n - e, n, t); +} +class To { + url; + #e; + #t; + constructor(e, t = {}) { + this.url = e, this.#e = t.overrides ?? {}, this.#t = t.useSuffixRequest ?? !1; + } + #i(e) { + return Fo(this.#e, e); + } + async get(e, t = {}) { + let s = pr(this.url, e).href, r = await fetch(s, this.#i(t)); + return Ar(r); + } + async getRange(e, t, s = {}) { + let r = pr(this.url, e), a = this.#i(s), n; + return "suffixLength" in t ? n = await Mo(r, t.suffixLength, a, this.#t) : n = await In(r, t.offset, t.length, a), Ar(n); + } +} +class Bn { + #e; + constructor(e, t, s) { + typeof e == "number" ? this.#e = new Uint8Array(e) : e instanceof ArrayBuffer ? this.#e = new Uint8Array(e, t, s) : this.#e = new Uint8Array(Array.from(e, (r) => r ? 1 : 0)); + } + get BYTES_PER_ELEMENT() { + return 1; + } + get byteOffset() { + return this.#e.byteOffset; + } + get byteLength() { + return this.#e.byteLength; + } + get buffer() { + return this.#e.buffer; + } + get length() { + return this.#e.length; + } + get(e) { + let t = this.#e[e]; + return typeof t == "number" ? t !== 0 : t; + } + set(e, t) { + this.#e[e] = t ? 1 : 0; + } + fill(e) { + this.#e.fill(e ? 1 : 0); + } + *[Symbol.iterator]() { + for (let e = 0; e < this.length; e++) + yield this.get(e); + } +} +class Is { + _data; + chars; + #e; + constructor(e, t, s, r) { + if (this.chars = e, this.#e = new TextEncoder(), typeof t == "number") + this._data = new Uint8Array(t * e); + else if (t instanceof ArrayBuffer) + r && (r = r * e), this._data = new Uint8Array(t, s, r); + else { + let a = Array.from(t); + this._data = new Uint8Array(a.length * e); + for (let n = 0; n < a.length; n++) + this.set(n, a[n]); + } + } + get BYTES_PER_ELEMENT() { + return this.chars; + } + get byteOffset() { + return this._data.byteOffset; + } + get byteLength() { + return this._data.byteLength; + } + get buffer() { + return this._data.buffer; + } + get length() { + return this.byteLength / this.BYTES_PER_ELEMENT; + } + get(e) { + const t = new Uint8Array(this.buffer, this.byteOffset + this.chars * e, this.chars); + return new TextDecoder().decode(t).replace(/\x00/g, ""); + } + set(e, t) { + const s = new Uint8Array(this.buffer, this.byteOffset + this.chars * e, this.chars); + s.fill(0), s.set(this.#e.encode(t)); + } + fill(e) { + const t = this.#e.encode(e); + for (let s = 0; s < this.length; s++) + this._data.set(t, s * this.chars); + } + *[Symbol.iterator]() { + for (let e = 0; e < this.length; e++) + yield this.get(e); + } +} +class ii { + #e; + chars; + constructor(e, t, s, r) { + if (this.chars = e, typeof t == "number") + this.#e = new Int32Array(t * e); + else if (t instanceof ArrayBuffer) + r && (r *= e), this.#e = new Int32Array(t, s, r); + else { + const a = t, n = new ii(e, 1); + this.#e = new Int32Array((function* () { + for (let o of a) + n.set(0, o), yield* n.#e; + })()); + } + } + get BYTES_PER_ELEMENT() { + return this.#e.BYTES_PER_ELEMENT * this.chars; + } + get byteLength() { + return this.#e.byteLength; + } + get byteOffset() { + return this.#e.byteOffset; + } + get buffer() { + return this.#e.buffer; + } + get length() { + return this.#e.length / this.chars; + } + get(e) { + const t = this.chars * e; + let s = ""; + for (let r = 0; r < this.chars; r++) + s += String.fromCodePoint(this.#e[t + r]); + return s.replace(/\u0000/g, ""); + } + set(e, t) { + const s = this.chars * e, r = this.#e.subarray(s, s + this.chars); + r.fill(0); + for (let a = 0; a < this.chars; a++) + r[a] = t.codePointAt(a) ?? 0; + } + fill(e) { + this.set(0, e); + let t = this.#e.subarray(0, this.chars); + for (let s = 1; s < this.length; s++) + this.#e.set(t, s * this.chars); + } + *[Symbol.iterator]() { + for (let e = 0; e < this.length; e++) + yield this.get(e); + } +} +function si(i) { + const e = new TextDecoder().decode(i); + return JSON.parse(e); +} +function vr(i, e) { + const t = e / 2, s = e - 1; + let r = 0; + for (let a = 0; a < i.length; a += e) + for (let n = 0; n < t; n += 1) + r = i[a + n], i[a + n] = i[a + s - n], i[a + s - n] = r; +} +function kn(i) { + if (i === "v2:object") + return globalThis.Array; + let e = i.match(/v2:([US])(\d+)/); + if (e) { + let [, s, r] = e; + return (s === "U" ? ii : Is).bind(null, Number(r)); + } + let t = { + int8: Int8Array, + int16: Int16Array, + int32: Int32Array, + int64: globalThis.BigInt64Array, + uint8: Uint8Array, + uint16: Uint16Array, + uint32: Uint32Array, + uint64: globalThis.BigUint64Array, + float16: globalThis.Float16Array, + float32: Float32Array, + float64: Float64Array, + bool: Bn + }[i]; + return be(t, `Unknown or unsupported data_type: ${i}`), t; +} +function vt(i, e) { + const t = i.length; + typeof e == "string" && (e = e === "C" ? Array.from({ length: t }, (a, n) => n) : Array.from({ length: t }, (a, n) => t - 1 - n)), be(t === e.length, "Order length must match the number of dimensions."); + let s = 1, r = new Array(t); + for (let a = e.length - 1; a >= 0; a--) + r[e[a]] = s, s *= i[e[a]]; + return r; +} +function So({ name: i, configuration: e }) { + if (i === "default") { + const t = e?.separator ?? "/"; + return (s) => ["c", ...s].join(t); + } + if (i === "v2") { + const t = e?.separator ?? "."; + return (s) => s.join(t) || "0"; + } + throw new Error(`Unknown chunk key encoding: ${i}`); +} +function Io(i) { + if (i === "|O") + return { data_type: "v2:object" }; + let e = i.match(/^([<|>])(.*)$/); + be(e, `Invalid dtype: ${i}`); + let [, t, s] = e, r = { + b1: "bool", + i1: "int8", + u1: "uint8", + i2: "int16", + u2: "uint16", + i4: "int32", + u4: "uint32", + i8: "int64", + u8: "uint64", + f2: "float16", + f4: "float32", + f8: "float64" + }[s] ?? (s.startsWith("S") || s.startsWith("U") ? `v2:${s}` : void 0); + return be(r, `Unsupported or unknown dtype: ${i}`), t === "|" ? { data_type: r } : { data_type: r, endian: t === "<" ? "little" : "big" }; +} +function Bo(i, e = {}) { + let t = [], s = Io(i.dtype); + i.order === "F" && t.push({ name: "transpose", configuration: { order: "F" } }), "endian" in s && s.endian === "big" && t.push({ name: "bytes", configuration: { endian: "big" } }); + for (let { id: r, ...a } of i.filters ?? []) + t.push({ name: r, configuration: a }); + if (i.compressor) { + let { id: r, ...a } = i.compressor; + t.push({ name: r, configuration: a }); + } + return { + zarr_format: 3, + node_type: "array", + shape: i.shape, + data_type: s.data_type, + chunk_grid: { + name: "regular", + configuration: { + chunk_shape: i.chunks + } + }, + chunk_key_encoding: { + name: "v2", + configuration: { + separator: i.dimension_separator ?? "." + } + }, + codecs: t, + fill_value: i.fill_value, + attributes: e + }; +} +function ko(i, e = {}) { + return { + zarr_format: 3, + node_type: "group", + attributes: e + }; +} +function Ro(i, e) { + if (e !== "number" && e !== "bigint" && e !== "boolean" && e !== "object" && e !== "string") + return i === e; + let t = i === "bool"; + if (e === "boolean") + return t; + let s = i.startsWith("v2:U") || i.startsWith("v2:S"); + if (e === "string") + return s; + let r = i === "int64" || i === "uint64"; + if (e === "bigint") + return r; + let a = i === "v2:object"; + return e === "object" ? a : !s && !r && !t && !a; +} +function Vo(i) { + return i?.name === "sharding_indexed"; +} +function Rn(i) { + return (i.data_type === "uint64" || i.data_type === "int64") && i.fill_value != null ? BigInt(i.fill_value) : i.fill_value; +} +function Vn(i, ...e) { + if (!e.some((t) => i instanceof t)) + throw i; +} +function be(i, e = "") { + if (!i) + throw new Error(e); +} +async function Un(i, { format: e, signal: t }) { + const s = i instanceof Response ? i : new Response(i); + be(s.body, "Response does not contain body."); + try { + return await new Response(s.body.pipeThrough(new DecompressionStream(e), { signal: t })).arrayBuffer(); + } catch { + throw t?.throwIfAborted(), new Error(`Failed to decode ${e}`); + } +} +class Bs { + kind = "array_to_array"; + constructor(e, t) { + be(e.keepbits >= 0, "keepbits must be zero or positive"); + } + static fromConfig(e, t) { + return new Bs(e, t); + } + /** + * Encode a chunk of data with bit-rounding. + * @param _arr - The chunk to encode + */ + encode(e) { + throw new Error("`BitroundCodec.encode` is not implemented. Please open an issue at https://github.com/manzt/zarrita.js/issues."); + } + /** + * Decode a chunk of data (no-op). + * @param arr - The chunk to decode + * @returns The decoded chunk + */ + decode(e) { + return e; + } +} +const xr = Uo(); +function Uo() { + const i = new Uint32Array([305419896]); + return new Uint8Array(i.buffer, i.byteOffset, i.byteLength)[0] !== 18; +} +function wr(i) { + return "BYTES_PER_ELEMENT" in i ? i.BYTES_PER_ELEMENT : 4; +} +class zi { + kind = "array_to_bytes"; + #e; + #t; + #i; + #r; + #s; + constructor(e, t) { + this.#s = e?.endian, this.#t = kn(t.data_type), this.#r = t.shape, this.#e = vt(t.shape, "C"); + const s = new this.#t(0); + this.#i = s.BYTES_PER_ELEMENT; + } + static fromConfig(e, t) { + return new zi(e, t); + } + encode(e) { + let t = new Uint8Array(e.data.buffer); + return xr && this.#s === "big" && vr(t, wr(this.#t)), t; + } + decode(e) { + return xr && this.#s === "big" && vr(e, wr(this.#t)), { + data: new this.#t(e.buffer, e.byteOffset, e.byteLength / this.#i), + shape: this.#r, + stride: this.#e + }; + } +} +class ks { + kind = "bytes_to_bytes"; + static fromConfig() { + return new ks(); + } + encode(e) { + throw new Error("Not implemented"); + } + decode(e) { + return new Uint8Array(e.buffer, e.byteOffset, e.byteLength - 4); + } +} +class Rs { + kind = "bytes_to_bytes"; + static fromConfig(e) { + return new Rs(); + } + encode(e) { + throw new Error("Gzip encoding is not enabled by default. Please register a custom codec with `numcodecs/gzip`."); + } + async decode(e) { + const t = await Un(e, { format: "gzip" }); + return new Uint8Array(t); + } +} +function No(i, e) { + return be(!Number.isNaN(e), "JsonCodec allow_nan is false but NaN was encountered during encoding."), be(e !== Number.POSITIVE_INFINITY, "JsonCodec allow_nan is false but Infinity was encountered during encoding."), be(e !== Number.NEGATIVE_INFINITY, "JsonCodec allow_nan is false but -Infinity was encountered during encoding."), e; +} +function Po(i, e) { + return e instanceof Object && !Array.isArray(e) ? Object.keys(e).sort().reduce((t, s) => (t[s] = e[s], t), {}) : e; +} +class Vs { + configuration; + kind = "array_to_bytes"; + #e; + #t; + constructor(e = {}) { + this.configuration = e; + const { encoding: t = "utf-8", skipkeys: s = !1, ensure_ascii: r = !0, check_circular: a = !0, allow_nan: n = !0, sort_keys: o = !0, indent: l, strict: c = !0 } = e; + let h = e.separators; + h || (l ? h = [", ", ": "] : h = [",", ":"]), this.#e = { + encoding: t, + skipkeys: s, + ensure_ascii: r, + check_circular: a, + allow_nan: n, + indent: l, + separators: h, + sort_keys: o + }, this.#t = { strict: c }; + } + static fromConfig(e) { + return new Vs(e); + } + encode(e) { + const { indent: t, encoding: s, ensure_ascii: r, check_circular: a, allow_nan: n, sort_keys: o } = this.#e; + be(s === "utf-8", "JsonCodec does not yet support non-utf-8 encoding."); + const l = []; + be(a, "JsonCodec does not yet support skipping the check for circular references during encoding."), n || l.push(No), o && l.push(Po); + const c = Array.from(e.data); + c.push("|O"), c.push(e.shape); + let h; + l.length && (h = (d, f) => { + let g = f; + for (let m of l) + g = m(d, g); + return g; + }); + let u = JSON.stringify(c, h, t); + return r && (u = u.replace(/[\u007F-\uFFFF]/g, (d) => { + const f = `0000${d.charCodeAt(0).toString(16)}`; + return `\\u${f.substring(f.length - 4)}`; + })), new TextEncoder().encode(u); + } + decode(e) { + const { strict: t } = this.#t; + be(t, "JsonCodec does not yet support non-strict decoding."); + const s = si(e), r = s.pop(); + s.pop(), be(r, "0D not implemented for JsonCodec."); + const a = vt(r, "C"); + return { data: s, shape: r, stride: a }; + } +} +function br(i) { + return i instanceof Bn || i instanceof Is || i instanceof ii ? new Proxy(i, { + get(t, s) { + return t.get(Number(s)); + }, + set(t, s, r) { + return t.set(Number(s), r), !0; + } + }) : i; +} +function Lo(i, e) { + let t; + return i.data instanceof Is || i.data instanceof ii ? t = new i.constructor( + // @ts-expect-error + i.data.length, + i.data.chars + ) : t = new i.constructor(i.data.length), { + data: t, + shape: i.shape, + stride: vt(i.shape, e) + }; +} +function Oo(i, e) { + let t = Lo(i, e), s = i.shape.length, r = i.data.length, a = Array(s).fill(0), n = br(i.data), o = br(t.data); + for (let l = 0; l < r; l++) { + let c = 0; + for (let h = 0; h < s; h++) + c += a[h] * t.stride[h]; + o[c] = n[l], a[0] += 1; + for (let h = 0; h < s; h++) + if (a[h] === i.shape[h]) { + if (h + 1 === s) + break; + a[h] = 0, a[h + 1] += 1; + } + } + return t; +} +function zo(i) { + let e = i.shape.length; + return be(e === i.stride.length, "Shape and stride must have the same length."), i.stride.map((t, s) => ({ stride: t, index: s })).sort((t, s) => s.stride - t.stride).map((t) => t.index); +} +function Go(i, e) { + let t = zo(i); + return be(t.length === e.length, "Orders must match"), t.every((s, r) => s === e[r]); +} +class Us { + kind = "array_to_array"; + #e; + #t; + constructor(e, t) { + let s = e.order ?? "C", r = t.shape.length, a = new Array(r), n = new Array(r); + if (s === "C") + for (let o = 0; o < r; ++o) + a[o] = o, n[o] = o; + else if (s === "F") + for (let o = 0; o < r; ++o) + a[o] = r - o - 1, n[o] = r - o - 1; + else + a = s, a.forEach((o, l) => { + be(n[o] === void 0, `Invalid permutation: ${JSON.stringify(s)}`), n[o] = l; + }); + this.#e = a, this.#t = n; + } + static fromConfig(e, t) { + return new Us(e, t); + } + encode(e) { + return Go(e, this.#t) ? e : Oo(e, this.#t); + } + decode(e) { + return { + data: e.data, + shape: e.shape, + stride: vt(e.shape, this.#e) + }; + } +} +class Ns { + kind = "array_to_bytes"; + #e; + #t; + constructor(e) { + this.#e = e, this.#t = vt(e, "C"); + } + static fromConfig(e, t) { + return new Ns(t.shape); + } + encode(e) { + throw new Error("Method not implemented."); + } + decode(e) { + let t = new TextDecoder(), s = new DataView(e.buffer), r = Array(s.getUint32(0, !0)), a = 4; + for (let n = 0; n < r.length; n++) { + let o = s.getUint32(a, !0); + a += 4, r[n] = t.decode(e.buffer.slice(a, a + o)), a += o; + } + return { data: r, shape: this.#e, stride: this.#t }; + } +} +class Ps { + kind = "bytes_to_bytes"; + static fromConfig(e) { + return new Ps(); + } + encode(e) { + throw new Error("Zlib encoding is not enabled by default. Please register a codec with `numcodecs/zlib`."); + } + async decode(e) { + const t = await Un(e, { format: "deflate" }); + return new Uint8Array(t); + } +} +function Yo() { + return (/* @__PURE__ */ new Map()).set("blosc", () => import("./blosc-D1xNXZJs.js").then((i) => i.default)).set("lz4", () => import("./lz4-1Ws5oVWR.js").then((i) => i.default)).set("zstd", () => import("./zstd-C4EcZnjq.js").then((i) => i.default)).set("gzip", () => Rs).set("zlib", () => Ps).set("transpose", () => Us).set("bytes", () => zi).set("crc32c", () => ks).set("vlen-utf8", () => Ns).set("json2", () => Vs).set("bitround", () => Bs); +} +const _o = Yo(); +function As(i) { + let e; + return { + async encode(t) { + e || (e = await yr(i)); + for (const r of e.array_to_array) + t = await r.encode(t); + let s = await e.array_to_bytes.encode(t); + for (const r of e.bytes_to_bytes) + s = await r.encode(s); + return s; + }, + async decode(t) { + e || (e = await yr(i)); + for (let r = e.bytes_to_bytes.length - 1; r >= 0; r--) + t = await e.bytes_to_bytes[r].decode(t); + let s = await e.array_to_bytes.decode(t); + for (let r = e.array_to_array.length - 1; r >= 0; r--) + s = await e.array_to_array[r].decode(s); + return s; + } + }; +} +async function yr(i) { + let e = i.codecs.map(async (a) => { + let n = await _o.get(a.name)?.(); + return be(n, `Unknown codec: ${a.name}`), { Codec: n, meta: a }; + }), t = [], s, r = []; + for await (let { Codec: a, meta: n } of e) { + let o = a.fromConfig(n.configuration, i); + switch (o.kind) { + case "array_to_array": + t.push(o); + break; + case "array_to_bytes": + s = o; + break; + default: + r.push(o); + } + } + return s || (be(qo(i), `Cannot encode ${i.data_type} to bytes without a codec`), s = zi.fromConfig({ endian: "little" }, i)), { array_to_array: t, array_to_bytes: s, bytes_to_bytes: r }; +} +function qo(i) { + return i.data_type !== "v2:object"; +} +class ri extends Error { + constructor(e, t = {}) { + super(`Node not found: ${e}`, t), this.name = "NodeNotFoundError"; + } +} +class Ls extends Error { + constructor(e) { + super(`Missing key: ${e}`), this.name = "KeyError"; + } +} +const Cr = 18446744073709551615n; +function Ho(i, e, t, s) { + be(i.store.getRange, "Store does not support range requests"); + let r = i.store.getRange.bind(i.store), a = e.map((l, c) => l / s.chunk_shape[c]), n = As({ + data_type: "uint64", + shape: [...a, 2], + codecs: s.index_codecs + }), o = {}; + return async (l) => { + let c = l.map((A, x) => Math.floor(A / a[x])), h = i.resolve(t(c)).path, u; + if (h in o) + u = o[h]; + else { + let A = 4, x = 16 * a.reduce((D, b) => D * b, 1), w = await r(h, { + suffixLength: x + A + }); + u = o[h] = w ? await n.decode(w) : null; + } + if (u === null) + return; + let { data: d, shape: f, stride: g } = u, m = l.map((A, x) => A % f[x]).reduce((A, x, w) => A + x * g[w], 0), p = d[m], v = d[m + 1]; + if (!(p === Cr && v === Cr)) + return r(h, { + offset: Number(p), + length: Number(v) + }); + }; +} +class bt { + store; + path; + constructor(e, t = "/") { + this.store = e, this.path = t; + } + resolve(e) { + let t = new URL(`file://${this.path.endsWith("/") ? this.path : `${this.path}/`}`); + return new bt(this.store, decodeURIComponent(new URL(e, t).pathname)); + } +} +function Wo(i) { + return new bt(i ?? /* @__PURE__ */ new Map()); +} +class Os extends bt { + kind = "group"; + #e; + constructor(e, t, s) { + super(e, t), this.#e = s; + } + get attrs() { + return this.#e.attributes; + } +} +function Dr(i) { + return i.find((t) => t.name === "transpose")?.configuration?.order ?? "C"; +} +const Wt = Symbol("zarrita.context"); +function Ko(i) { + return i[Wt]; +} +function Xo(i, e) { + let { configuration: t } = e.codecs.find(Vo) ?? {}, s = { + encode_chunk_key: So(e.chunk_key_encoding), + TypedArray: kn(e.data_type), + fill_value: e.fill_value + }; + if (t) { + let a = Dr(t.codecs); + return { + ...s, + kind: "sharded", + chunk_shape: t.chunk_shape, + codec: As({ + data_type: e.data_type, + shape: t.chunk_shape, + codecs: t.codecs + }), + get_strides(n) { + return vt(n, a); + }, + get_chunk_bytes: Ho(i, e.chunk_grid.configuration.chunk_shape, s.encode_chunk_key, t) + }; + } + let r = Dr(e.codecs); + return { + ...s, + kind: "regular", + chunk_shape: e.chunk_grid.configuration.chunk_shape, + codec: As({ + data_type: e.data_type, + shape: e.chunk_grid.configuration.chunk_shape, + codecs: e.codecs + }), + get_strides(a) { + return vt(a, r); + }, + async get_chunk_bytes(a, n) { + let o = s.encode_chunk_key(a), l = i.resolve(o).path; + return i.store.get(l, n); + } + }; +} +let Ri = class extends bt { + kind = "array"; + #e; + [Wt]; + constructor(e, t, s) { + super(e, t), this.#e = { + ...s, + fill_value: Rn(s) + }, this[Wt] = Xo(this, s); + } + get attrs() { + return this.#e.attributes; + } + get shape() { + return this.#e.shape; + } + get chunks() { + return this[Wt].chunk_shape; + } + get dtype() { + return this.#e.data_type; + } + async getChunk(e, t) { + let s = this[Wt], r = await s.get_chunk_bytes(e, t); + if (!r) { + let a = s.chunk_shape.reduce((o, l) => o * l, 1), n = new s.TypedArray(a); + return n.fill(s.fill_value), { + data: n, + shape: s.chunk_shape, + stride: s.get_strides(s.chunk_shape) + }; + } + return s.codec.decode(r); + } + /** + * A helper method to narrow `zarr.Array` Dtype. + * + * ```typescript + * let arr: zarr.Array = zarr.open(store, { kind: "array" }); + * + * // Option 1: narrow by scalar type (e.g. "bool", "raw", "bigint", "number") + * if (arr.is("bigint")) { + * // zarr.Array<"int64" | "uint64", FetchStore> + * } + * + * // Option 3: exact match + * if (arr.is("float32")) { + * // zarr.Array<"float32", FetchStore, "/"> + * } + * ``` + */ + is(e) { + return Ro(this.dtype, e); + } +}; +function* jo(i, e, t = 1) { + e === void 0 && (e = i, i = 0); + for (let s = i; s < e; s += t) + yield s; +} +function* Zo(...i) { + if (i.length === 0) + return; + const e = i.map((s) => s[Symbol.iterator]()), t = e.map((s) => s.next()); + if (t.some((s) => s.done)) + throw new Error("Input contains an empty iterator."); + for (let s = 0; ; ) { + if (t[s].done) { + if (e[s] = i[s][Symbol.iterator](), t[s] = e[s].next(), ++s >= e.length) + return; + } else + yield t.map(({ value: r }) => r), s = 0; + t[s] = e[s].next(); + } +} +function Qo({ start: i, stop: e, step: t }, s) { + if (t === 0) + throw new Error("slice step cannot be zero"); + t = t ?? 1; + const r = t < 0, [a, n] = r ? [-1, s - 1] : [0, s]; + return i === null ? i = r ? n : a : i < 0 ? (i += s, i < a && (i = a)) : i > n && (i = n), e === null ? e = r ? a : n : e < 0 ? (e += s, e < a && (e = a)) : e > n && (e = n), [i, e, t]; +} +function Qt(i, e, t = null) { + return e === void 0 && (e = i, i = null), { + start: i, + stop: e, + step: t + }; +} +function Jo() { + const i = []; + return { + add: (e) => i.push(e()), + onIdle: () => Promise.all(i) + }; +} +class zs extends Error { + constructor(e) { + super(e), this.name = "IndexError"; + } +} +function $o(i, e) { + throw new zs(`too many indicies for array; expected ${e.length}, got ${i.length}`); +} +function el(i) { + throw new zs(`index out of bounds for dimension with length ${i}`); +} +function tl() { + throw new zs("only slices with step >= 1 are supported"); +} +function il(i, e) { + i.length > e.length && $o(i, e); +} +function sl(i, e) { + return i = Math.trunc(i), i < 0 && (i = e + i), (i >= e || i < 0) && el(e), i; +} +class rl { + dim_sel; + dim_len; + dim_chunk_len; + nitems; + constructor({ dim_sel: e, dim_len: t, dim_chunk_len: s }) { + e = sl(e, t), this.dim_sel = e, this.dim_len = t, this.dim_chunk_len = s, this.nitems = 1; + } + *[Symbol.iterator]() { + const e = Math.floor(this.dim_sel / this.dim_chunk_len), t = e * this.dim_chunk_len, s = this.dim_sel - t; + yield { dim_chunk_ix: e, dim_chunk_sel: s }; + } +} +class Er { + start; + stop; + step; + dim_len; + dim_chunk_len; + nitems; + nchunks; + constructor({ dim_sel: e, dim_len: t, dim_chunk_len: s }) { + const [r, a, n] = Qo(e, t); + this.start = r, this.stop = a, this.step = n, this.step < 1 && tl(), this.dim_len = t, this.dim_chunk_len = s, this.nitems = Math.max(0, Math.ceil((this.stop - this.start) / this.step)), this.nchunks = Math.ceil(this.dim_len / this.dim_chunk_len); + } + *[Symbol.iterator]() { + const e = Math.floor(this.start / this.dim_chunk_len), t = Math.ceil(this.stop / this.dim_chunk_len); + for (const s of jo(e, t)) { + const r = s * this.dim_chunk_len, a = Math.min(this.dim_len, (s + 1) * this.dim_chunk_len), n = a - r; + let o = 0, l = 0; + if (this.start < r) { + const f = (r - this.start) % this.step; + f && (l += this.step - f), o = Math.ceil((r - this.start) / this.step); + } else + l = this.start - r; + const c = this.stop > a ? n : this.stop - r, h = [ + l, + c, + this.step + ], u = Math.ceil((c - l) / this.step), d = [ + o, + o + u, + 1 + ]; + yield { dim_chunk_ix: s, dim_chunk_sel: h, dim_out_sel: d }; + } + } +} +function nl(i, e) { + let t = []; + return i === null ? t = e.map((s) => Qt(null)) : Array.isArray(i) && (t = i.map((s) => s ?? Qt(null))), il(t, e), t; +} +class al { + dim_indexers; + shape; + constructor({ selection: e, shape: t, chunk_shape: s }) { + this.dim_indexers = nl(e, t).map((r, a) => new (typeof r == "number" ? rl : Er)({ + // @ts-expect-error ts inference not strong enough to know correct chunk + dim_sel: r, + dim_len: t[a], + dim_chunk_len: s[a] + })), this.shape = this.dim_indexers.filter((r) => r instanceof Er).map((r) => r.nitems); + } + *[Symbol.iterator]() { + for (const e of Zo(...this.dim_indexers)) { + const t = e.map((r) => r.dim_chunk_ix), s = e.map((r) => "dim_out_sel" in r ? { from: r.dim_chunk_sel, to: r.dim_out_sel } : { from: r.dim_chunk_sel, to: null }); + yield { chunk_coords: t, mapping: s }; + } + } +} +function ol(i, e) { + return "get" in i ? i.get(e) : i[e]; +} +async function ll(i, e, t, s) { + let r = Ko(i), a = new al({ + selection: e, + shape: i.shape, + chunk_shape: i.chunks + }), n = s.prepare(new r.TypedArray(a.shape.reduce((l, c) => l * c, 1)), a.shape, r.get_strides(a.shape)), o = t.create_queue?.() ?? Jo(); + for (const { chunk_coords: l, mapping: c } of a) + o.add(async () => { + let { data: h, shape: u, stride: d } = await i.getChunk(l, t.opts), f = s.prepare(h, u, d); + s.set_from_chunk(n, f, c); + }); + return await o.onIdle(), a.shape.length === 0 ? ol(n.data, 0) : n; +} +function Gs(i, e = 0, t) { + let s = t ?? i.length - e; + return { + length: s, + subarray(r, a = s) { + return Gs(i, e + r, a - r); + }, + set(r, a = 0) { + for (let n = 0; n < r.length; n++) + i[e + a + n] = r.get(n); + }, + get(r) { + return i[e + r]; + } + }; +} +function Ji(i) { + return globalThis.Array.isArray(i.data) ? { + // @ts-expect-error + data: Gs(i.data), + stride: i.stride, + bytes_per_element: 1 + } : { + data: new Uint8Array(i.data.buffer, i.data.byteOffset, i.data.byteLength), + stride: i.stride, + bytes_per_element: i.data.BYTES_PER_ELEMENT + }; +} +function cl(i) { + return "chars" in i ? i.constructor.bind(null, i.chars) : i.constructor; +} +function hl(i, e) { + if (globalThis.Array.isArray(i.data)) + return Gs([e]); + let t = cl(i.data), s = new t([e]); + return new Uint8Array(s.buffer, s.byteOffset, s.byteLength); +} +const ul = { + prepare(i, e, t) { + return { data: i, shape: e, stride: t }; + }, + set_scalar(i, e, t) { + let s = Ji(i); + vs(s, e, hl(i, t), s.bytes_per_element); + }, + set_from_chunk(i, e, t) { + let s = Ji(i); + Fi(s, Ji(e), s.bytes_per_element, t); + } +}; +async function Fr(i, e = null, t = {}) { + return ll(i, e, t, ul); +} +function Nn(i, e, t) { + return t < 0 && e < i ? Math.floor((i - e - 1) / -t) + 1 : i < e ? Math.floor((e - i - 1) / t) + 1 : 0; +} +function vs(i, e, t, s) { + if (e.length === 0) { + i.data.set(t, 0); + return; + } + const [r, ...a] = e, [n, ...o] = i.stride; + if (typeof r == "number") { + const d = i.data.subarray(n * r * s); + vs({ data: d, stride: o }, a, t, s); + return; + } + const [l, c, h] = r, u = Nn(l, c, h); + if (a.length === 0) { + for (let d = 0; d < u; d++) + i.data.set(t, n * (l + h * d) * s); + return; + } + for (let d = 0; d < u; d++) { + const f = i.data.subarray(n * (l + h * d) * s); + vs({ data: f, stride: o }, a, t, s); + } +} +function Fi(i, e, t, s) { + const [r, ...a] = s, [n, ...o] = i.stride, [l, ...c] = e.stride; + if (r.from === null) { + if (a.length === 0) { + i.data.set(e.data.subarray(0, t), r.to * t); + return; + } + Fi({ + data: i.data.subarray(n * r.to * t), + stride: o + }, e, t, a); + return; + } + if (r.to === null) { + if (a.length === 0) { + let v = r.from * t; + i.data.set(e.data.subarray(v, v + t), 0); + return; + } + Fi(i, { + data: e.data.subarray(l * r.from * t), + stride: c + }, t, a); + return; + } + const [h, u, d] = r.to, [f, g, m] = r.from, p = Nn(h, u, d); + if (a.length === 0) { + if (d === 1 && m === 1 && n === 1 && l === 1) { + let v = f * t, A = p * t; + i.data.set(e.data.subarray(v, v + A), h * t); + return; + } + for (let v = 0; v < p; v++) { + let A = l * (f + m * v) * t; + i.data.set(e.data.subarray(A, A + t), n * (h + d * v) * t); + } + return; + } + for (let v = 0; v < p; v++) + Fi({ + data: i.data.subarray(n * (h + v * d) * t), + stride: o + }, { + data: e.data.subarray(l * (f + v * m) * t), + stride: c + }, t, a); +} +let Gi = dl(); +function dl() { + let i = /* @__PURE__ */ new WeakMap(); + function e(t) { + let s = i.get(t) ?? { v2: 0, v3: 0 }; + return i.set(t, s), s; + } + return { + increment(t, s) { + e(t)[s] += 1; + }, + version_max(t) { + let s = e(t); + return s.v3 > s.v2 ? "v3" : "v2"; + } + }; +} +async function fl(i) { + let e = await i.store.get(i.resolve(".zattrs").path); + return e ? si(e) : {}; +} +async function ml(i, e = {}) { + let t = "store" in i ? i : new bt(i), s = {}; + return (e.attrs ?? !0) && (s = await fl(t)), e.kind === "array" ? Mr(t, s) : e.kind === "group" ? Tr(t, s) : Mr(t, s).catch((r) => (Vn(r, ri), Tr(t, s))); +} +async function Mr(i, e) { + let { path: t } = i.resolve(".zarray"), s = await i.store.get(t); + if (!s) + throw new ri("v2 array", { + cause: new Ls(t) + }); + return Gi.increment(i.store, "v2"), new Ri(i.store, i.path, Bo(si(s), e)); +} +async function Tr(i, e) { + let { path: t } = i.resolve(".zgroup"), s = await i.store.get(t); + if (!s) + throw new ri("v2 group", { + cause: new Ls(t) + }); + return Gi.increment(i.store, "v2"), new Os(i.store, i.path, ko(si(s), e)); +} +async function gl(i) { + let { store: e, path: t } = i.resolve("zarr.json"), s = await i.store.get(t); + if (!s) + throw new ri("v3 array or group", { + cause: new Ls(t) + }); + let r = si(s); + return r.node_type === "array" && (r.fill_value = Rn(r)), r.node_type === "array" ? new Ri(e, i.path, r) : new Os(e, i.path, r); +} +async function pl(i, e = {}) { + let t = "store" in i ? i : new bt(i), s = await gl(t); + if (Gi.increment(t.store, "v3"), e.kind === void 0 || e.kind === "array" && s instanceof Ri || e.kind === "group" && s instanceof Os) + return s; + let r = s instanceof Ri ? "array" : "group"; + throw new Error(`Expected node of kind ${e.kind}, found ${r}.`); +} +async function at(i, e = {}) { + let t = "store" in i ? i.store : i, s = Gi.version_max(t), r = s === "v2" ? at.v2 : at.v3, a = s === "v2" ? at.v3 : at.v2; + return r(i, e).catch((n) => (Vn(n, ri), a(i, e))); +} +at.v2 = ml; +at.v3 = pl; +var Pn = Object.defineProperty, Ln = (i) => { + throw TypeError(i); +}, Al = (i, e, t) => e in i ? Pn(i, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : i[e] = t, Yi = (i, e) => { + for (var t in e) + Pn(i, t, { get: e[t], enumerable: !0 }); +}, I = (i, e, t) => Al(i, typeof e != "symbol" ? e + "" : e, t), On = (i, e, t) => e.has(i) || Ln("Cannot " + t), K = (i, e, t) => (On(i, e, "read from private field"), t ? t.call(i) : e.get(i)), It = (i, e, t) => e.has(i) ? Ln("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(i) : e.set(i, t), st = (i, e, t, s) => (On(i, e, "write to private field"), e.set(i, t), t), vl = { + version: "0.65.0" +}, xl = new Float32Array([ + -1, + -1, + -1, + 0.28, + 0.28, + 0.28, + -1, + -1, + -1, + 0.28, + 0.28, + 0.28, + -1, + 1, + -1, + 0.28, + 0.28, + 0.28, + 1, + -1, + -1, + 0.28, + 0.28, + 0.28, + 1, + 1, + -1, + 0.28, + 0.28, + 0.28, + 1, + 1, + -1, + 0.28, + 0.28, + 0.28, + -1, + -1, + 1, + 0.8, + 0.8, + 0.8, + -1, + -1, + 1, + 0.8, + 0.8, + 0.8, + 1, + -1, + 1, + 0.8, + 0.8, + 0.8, + -1, + 1, + 1, + 0.8, + 0.8, + 0.8, + 1, + 1, + 1, + 0.8, + 0.8, + 0.8, + 1, + 1, + 1, + 0.8, + 0.8, + 0.8, + -1, + 1, + -1, + 0, + 0, + 0.74, + -1, + 1, + -1, + 0, + 0, + 0.74, + -1, + 1, + 1, + 0, + 0, + 0.74, + 1, + 1, + -1, + 0, + 0, + 0.74, + 1, + 1, + 1, + 0, + 0, + 0.74, + 1, + 1, + 1, + 0, + 0, + 0.74, + -1, + -1, + -1, + 0.42, + 0, + 0.42, + -1, + -1, + -1, + 0.42, + 0, + 0.42, + 1, + -1, + -1, + 0.42, + 0, + 0.42, + -1, + -1, + 1, + 0.42, + 0, + 0.42, + 1, + -1, + 1, + 0.42, + 0, + 0.42, + 1, + -1, + 1, + 0.42, + 0, + 0.42, + -1, + -1, + -1, + 0.64, + 0, + 0, + -1, + -1, + -1, + 0.64, + 0, + 0, + -1, + -1, + 1, + 0.64, + 0, + 0, + -1, + 1, + -1, + 0.64, + 0, + 0, + -1, + 1, + 1, + 0.64, + 0, + 0, + -1, + 1, + 1, + 0.64, + 0, + 0, + 1, + -1, + -1, + 0, + 0.5, + 0, + 1, + -1, + -1, + 0, + 0.5, + 0, + 1, + 1, + -1, + 0, + 0.5, + 0, + 1, + -1, + 1, + 0, + 0.5, + 0, + 1, + 1, + 1, + 0, + 0.5, + 0, + 1, + 1, + 1, + 0, + 0.5, + 0, + // P + -0.45, + 1, + -0.8, + 0, + 0, + 0, + -0.45, + 1, + -0.8, + 0, + 0, + 0, + -0.45, + 1, + 0.8, + 0, + 0, + 0, + -0.25, + 1, + -0.8, + 0, + 0, + 0, + -0.25, + 1, + 0.8, + 0, + 0, + 0, + -0.25, + 1, + 0.8, + 0, + 0, + 0, + -0.25, + 1, + 0.6, + 0, + 0, + 0, + -0.25, + 1, + 0.6, + 0, + 0, + 0, + -0.25, + 1, + 0.8, + 0, + 0, + 0, + 0.45, + 1, + 0.6, + 0, + 0, + 0, + 0.25, + 1, + 0.8, + 0, + 0, + 0, + 0.25, + 1, + 0.8, + 0, + 0, + 0, + 0.25, + 1, + 0.1, + 0, + 0, + 0, + 0.25, + 1, + 0.1, + 0, + 0, + 0, + 0.25, + 1, + 0.6, + 0, + 0, + 0, + 0.45, + 1, + 0.1, + 0, + 0, + 0, + 0.45, + 1, + 0.6, + 0, + 0, + 0, + 0.45, + 1, + 0.6, + 0, + 0, + 0, + -0.25, + 1, + -0.1, + 0, + 0, + 0, + -0.25, + 1, + -0.1, + 0, + 0, + 0, + -0.25, + 1, + 0.1, + 0, + 0, + 0, + 0.25, + 1, + -0.1, + 0, + 0, + 0, + 0.45, + 1, + 0.1, + 0, + 0, + 0, + 0.45, + 1, + 0.1, + 0, + 0, + 0, + // A + 0.45, + -1, + -0.8, + 0, + 0, + 0, + 0.45, + -1, + -0.8, + 0, + 0, + 0, + 0.05, + -1, + 0.8, + 0, + 0, + 0, + 0.25, + -1, + -0.8, + 0, + 0, + 0, + -0.15, + -1, + 0.8, + 0, + 0, + 0, + -0.15, + -1, + 0.8, + 0, + 0, + 0, + -0.25, + -1, + -0.8, + 0, + 0, + 0, + -0.25, + -1, + -0.8, + 0, + 0, + 0, + 0.05, + -1, + 0.8, + 0, + 0, + 0, + -0.45, + -1, + -0.8, + 0, + 0, + 0, + -0.15, + -1, + 0.8, + 0, + 0, + 0, + -0.15, + -1, + 0.8, + 0, + 0, + 0, + 0.13, + -1, + -0.3, + 0, + 0, + 0, + 0.13, + -1, + -0.3, + 0, + 0, + 0, + 0.07, + -1, + -0.1, + 0, + 0, + 0, + -0.33, + -1, + -0.3, + 0, + 0, + 0, + -0.27, + -1, + -0.1, + 0, + 0, + 0, + -0.27, + -1, + -0.1, + 0, + 0, + 0, + // S + -0.45, + 0.6, + 1, + 0, + 0, + 0, + -0.45, + 0.6, + 1, + 0, + 0, + 0, + -0.45, + 0.4, + 1, + 0, + 0, + 0, + -0.25, + 0.8, + 1, + 0, + 0, + 0, + -0.25, + 0.4, + 1, + 0, + 0, + 0, + -0.25, + 0.4, + 1, + 0, + 0, + 0, + -0.25, + 0.8, + 1, + 0, + 0, + 0, + -0.25, + 0.8, + 1, + 0, + 0, + 0, + -0.25, + 0.6, + 1, + 0, + 0, + 0, + 0.25, + 0.8, + 1, + 0, + 0, + 0, + 0.45, + 0.6, + 1, + 0, + 0, + 0, + 0.45, + 0.6, + 1, + 0, + 0, + 0, + 0.25, + 0.8, + 1, + 0, + 0, + 0, + 0.25, + 0.8, + 1, + 0, + 0, + 0, + 0.25, + -0.1, + 1, + 0, + 0, + 0, + 0.45, + 0.6, + 1, + 0, + 0, + 0, + 0.45, + 0.1, + 1, + 0, + 0, + 0, + 0.45, + 0.1, + 1, + 0, + 0, + 0, + -0.25, + 0.1, + 1, + 0, + 0, + 0, + -0.25, + 0.1, + 1, + 0, + 0, + 0, + -0.45, + -0.1, + 1, + 0, + 0, + 0, + 0.25, + 0.1, + 1, + 0, + 0, + 0, + 0.25, + -0.1, + 1, + 0, + 0, + 0, + 0.25, + -0.1, + 1, + 0, + 0, + 0, + -0.45, + -0.1, + 1, + 0, + 0, + 0, + -0.45, + -0.1, + 1, + 0, + 0, + 0, + -0.45, + -0.6, + 1, + 0, + 0, + 0, + -0.25, + -0.1, + 1, + 0, + 0, + 0, + -0.25, + -0.8, + 1, + 0, + 0, + 0, + -0.25, + -0.8, + 1, + 0, + 0, + 0, + -0.25, + -0.6, + 1, + 0, + 0, + 0, + -0.25, + -0.6, + 1, + 0, + 0, + 0, + -0.25, + -0.8, + 1, + 0, + 0, + 0, + 0.45, + -0.6, + 1, + 0, + 0, + 0, + 0.25, + -0.8, + 1, + 0, + 0, + 0, + 0.25, + -0.8, + 1, + 0, + 0, + 0, + 0.25, + -0.4, + 1, + 0, + 0, + 0, + 0.25, + -0.4, + 1, + 0, + 0, + 0, + 0.25, + -0.6, + 1, + 0, + 0, + 0, + 0.45, + -0.4, + 1, + 0, + 0, + 0, + 0.45, + -0.6, + 1, + 0, + 0, + 0, + 0.45, + -0.6, + 1, + 0, + 0, + 0, + // I + -0.1, + -0.8, + -1, + 0, + 0, + 0, + -0.1, + -0.8, + -1, + 0, + 0, + 0, + -0.1, + 0.8, + -1, + 0, + 0, + 0, + 0.1, + -0.8, + -1, + 0, + 0, + 0, + 0.1, + 0.8, + -1, + 0, + 0, + 0, + 0.1, + 0.8, + -1, + 0, + 0, + 0, + // L + -1, + -0.45, + -0.8, + 0, + 0, + 0, + -1, + -0.45, + -0.8, + 0, + 0, + 0, + -1, + -0.45, + 0.8, + 0, + 0, + 0, + -1, + -0.25, + -0.8, + 0, + 0, + 0, + -1, + -0.25, + 0.8, + 0, + 0, + 0, + -1, + -0.25, + 0.8, + 0, + 0, + 0, + -1, + -0.25, + -0.8, + 0, + 0, + 0, + -1, + -0.25, + -0.8, + 0, + 0, + 0, + -1, + -0.25, + -0.6, + 0, + 0, + 0, + -1, + 0.45, + -0.8, + 0, + 0, + 0, + -1, + 0.45, + -0.6, + 0, + 0, + 0, + -1, + 0.45, + -0.6, + 0, + 0, + 0, + // R + 1, + 0.45, + -0.8, + 0, + 0, + 0, + 1, + 0.45, + -0.8, + 0, + 0, + 0, + 1, + 0.45, + 0.8, + 0, + 0, + 0, + 1, + 0.25, + -0.8, + 0, + 0, + 0, + 1, + 0.25, + 0.8, + 0, + 0, + 0, + 1, + 0.25, + 0.8, + 0, + 0, + 0, + 1, + 0.25, + 0.6, + 0, + 0, + 0, + 1, + 0.25, + 0.6, + 0, + 0, + 0, + 1, + 0.25, + 0.8, + 0, + 0, + 0, + 1, + -0.45, + 0.6, + 0, + 0, + 0, + 1, + -0.25, + 0.8, + 0, + 0, + 0, + 1, + -0.25, + 0.8, + 0, + 0, + 0, + 1, + -0.25, + 0.1, + 0, + 0, + 0, + 1, + -0.25, + 0.1, + 0, + 0, + 0, + 1, + -0.25, + 0.6, + 0, + 0, + 0, + 1, + -0.45, + 0.1, + 0, + 0, + 0, + 1, + -0.45, + 0.6, + 0, + 0, + 0, + 1, + -0.45, + 0.6, + 0, + 0, + 0, + 1, + 0.25, + -0.1, + 0, + 0, + 0, + 1, + 0.25, + -0.1, + 0, + 0, + 0, + 1, + 0.25, + 0.1, + 0, + 0, + 0, + 1, + -0.25, + -0.1, + 0, + 0, + 0, + 1, + -0.45, + 0.1, + 0, + 0, + 0, + 1, + -0.45, + 0.1, + 0, + 0, + 0, + 1, + -0.25, + -0.8, + 0, + 0, + 0, + 1, + -0.25, + -0.8, + 0, + 0, + 0, + 1, + -0.05, + -0.1, + 0, + 0, + 0, + 1, + -0.45, + -0.8, + 0, + 0, + 0, + 1, + -0.25, + -0.1, + 0, + 0, + 0, + 1, + -0.25, + -0.1, + 0, + 0, + 0 +]), ae = class { + constructor(e, t, s, r, a = null, n = null) { + I(this, "sphereIdx", []), I(this, "sphereVtx", []), I(this, "renderShaders", []), I(this, "isVisible", !0), I(this, "isPickable", !0), I(this, "vertexBuffer"), I(this, "indexCount"), I(this, "indexBuffer"), I(this, "vao"), I(this, "mode"), I(this, "glFlags", 0), I(this, "id"), I(this, "colorId"), I(this, "modelMatrix", ie()), I(this, "scale", [1, 1, 1]), I(this, "position", [0, 0, 0]), I(this, "rotation", [0, 0, 0]), I(this, "rotationRadians", 0), I(this, "extentsMin", []), I(this, "extentsMax", []), I(this, "furthestVertexFromOrigin"), I(this, "originNegate"), I(this, "fieldOfViewDeObliqueMM"), I(this, "mm"), this.vertexBuffer = t, this.indexCount = r, this.indexBuffer = a, this.vao = n, this.mode = s, this.id = e, this.colorId = [ + (e >> 0 & 255) / 255, + (e >> 8 & 255) / 255, + (e >> 16 & 255) / 255, + (e >> 24 & 255) / 255 + ]; + } +}; +I(ae, "BLEND", 1); +I(ae, "CULL_FACE", 2); +I(ae, "CULL_FRONT", 4); +I(ae, "CULL_BACK", 8); +I(ae, "ENABLE_DEPTH_TEST", 16); +I(ae, "generateCrosshairs", function(i, e, t, s, r, a, n = 20, o = 0) { + const l = ae.generateCrosshairsGeometry(i, t, s, r, a, n, o); + return new ae( + e, + l.vertexBuffer, + i.TRIANGLES, + l.indexCount, + l.indexBuffer, + l.vao + ); +}); +I(ae, "generateCrosshairsGeometry", function(i, e, t, s, r, a = 20, n = 0) { + const o = [], l = [], c = r * n; + if (c <= 0) { + let f = G(t[0], e[1], e[2]), g = G(s[0], e[1], e[2]); + ae.makeCylinder(o, l, f, g, r, a), f = G(e[0], t[1], e[2]), g = G(e[0], s[1], e[2]), ae.makeCylinder(o, l, f, g, r, a), f = G(e[0], e[1], t[2]), g = G(e[0], e[1], s[2]), ae.makeCylinder(o, l, f, g, r, a); + } else { + let f = G(t[0], e[1], e[2]), g = G(e[0] - c, e[1], e[2]); + ae.makeCylinder(o, l, f, g, r, a, !1), f = G(e[0] + c, e[1], e[2]), g = G(s[0], e[1], e[2]), ae.makeCylinder(o, l, f, g, r, a, !1), f = G(e[0], t[1], e[2]), g = G(e[0], e[1] - c, e[2]), ae.makeCylinder(o, l, f, g, r, a, !1), f = G(e[0], e[1] + c, e[2]), g = G(e[0], s[1], e[2]), ae.makeCylinder(o, l, f, g, r, a, !1), f = G(e[0], e[1], t[2]), g = G(e[0], e[1], e[2] - c), ae.makeCylinder(o, l, f, g, r, a, !1), f = G(e[0], e[1], e[2] + c), g = G(e[0], e[1], s[2]), ae.makeCylinder(o, l, f, g, r, a, !1); + } + const h = i.createBuffer(); + if (h === null) + throw new Error("could not instantiate vertex buffer"); + i.bindBuffer(i.ARRAY_BUFFER, h), i.bufferData(i.ARRAY_BUFFER, new Float32Array(o), i.STATIC_DRAW); + const u = i.createBuffer(); + if (u === null) + throw new Error("could not instantiate index buffer"); + i.bindBuffer(i.ELEMENT_ARRAY_BUFFER, u), i.bufferData(i.ELEMENT_ARRAY_BUFFER, new Uint32Array(l), i.STATIC_DRAW); + const d = i.createVertexArray(); + return i.bindVertexArray(d), i.bindBuffer(i.ELEMENT_ARRAY_BUFFER, u), i.bindBuffer(i.ARRAY_BUFFER, h), i.enableVertexAttribArray(0), i.vertexAttribPointer(0, 3, i.FLOAT, !1, 0, 0), i.bindVertexArray(null), { + vertexBuffer: h, + indexBuffer: u, + indexCount: l.length, + vao: d + }; +}); +I(ae, "getFirstPerpVector", function(i) { + const e = G(0, 0, 0); + return i[0] === 0 ? e[0] = 1 : i[1] === 0 ? e[1] = 1 : i[2] === 0 ? e[2] = 1 : (e[0] = i[2], e[1] = i[2], e[2] = -(i[0] + i[1]), Pe(e, e)), e; +}); +I(ae, "subdivide", function(i, e) { + let t = i.length / 3, s = e.length / 3; + const r = s, a = le(), n = le(); + for (let o = 0; o < r; o++) { + const l = e[o * 3 + 0], c = e[o * 3 + 1], h = e[o * 3 + 2], u = G(i[l * 3 + 0], i[l * 3 + 1], i[l * 3 + 2]), d = G(i[c * 3 + 0], i[c * 3 + 1], i[c * 3 + 2]), f = G(i[h * 3 + 0], i[h * 3 + 1], i[h * 3 + 2]); + At(a, u, d), Pe(n, a), i.push(...n), At(a, d, f), Pe(n, a), i.push(...n), At(a, u, f), Pe(n, a), i.push(...n); + let g = [t, t + 1, t + 2]; + e.push(...g), g = [l, t, t + 2], e.push(...g), g = [t, c, t + 1], e.push(...g), e[o * 3 + 0] = t + 2, e[o * 3 + 1] = t + 1, e[o * 3 + 2] = h, s = s + 3, t = t + 3; + } +}); +I(ae, "weldVertices", function(i, e) { + const t = i.length / 3; + let s = 0; + const r = new Int32Array(t); + for (let o = 0; o < t - 1; o++) { + if (r[o] !== 0) + continue; + r[o] = s; + let l = o * 3; + const c = i[l], h = i[l + 1], u = i[l + 2]; + for (let d = o + 1; d < t; d++) + l += 3, c === i[l] && h === i[l + 1] && u === i[l + 2] && (r[d] = s); + s++; + } + if (s === t) + return i; + const a = e.length; + for (let o = 0; o < a; o++) + e[o] = r[e[o]]; + const n = i.slice(0, s * 3 - 1); + for (let o = 0; o < t - 1; o++) { + const l = o * 3, c = r[o] * 3; + n[c] = i[l], n[c + 1] = i[l + 1], n[c + 2] = i[l + 2]; + } + return n; +}); +I(ae, "makeSphere", function(i, e, t, s = [0, 0, 0]) { + let r = [ + 0, + 0, + 1, + 0.894, + 0, + 0.447, + 0.276, + 0.851, + 0.447, + -0.724, + 0.526, + 0.447, + -0.724, + -0.526, + 0.447, + 0.276, + -0.851, + 0.447, + 0.724, + 0.526, + -0.447, + -0.276, + 0.851, + -0.447, + -0.894, + 0, + -0.447, + -0.276, + -0.851, + -0.447, + 0.724, + -0.526, + -0.447, + 0, + 0, + -1 + ]; + const a = [ + 0, + 1, + 2, + 0, + 2, + 3, + 0, + 3, + 4, + 0, + 4, + 5, + 0, + 5, + 1, + 7, + 6, + 11, + 8, + 7, + 11, + 9, + 8, + 11, + 10, + 9, + 11, + 6, + 10, + 11, + 6, + 2, + 1, + 7, + 3, + 2, + 8, + 4, + 3, + 9, + 5, + 4, + 10, + 1, + 5, + 6, + 7, + 2, + 7, + 8, + 3, + 8, + 9, + 4, + 9, + 10, + 5, + 10, + 6, + 1 + ]; + ae.subdivide(r, a), ae.subdivide(r, a), r = ae.weldVertices(r, a); + for (let c = 0; c < r.length; c++) + r[c] = r[c] * t; + const n = r.length / 3; + let o = 0; + for (let c = 0; c < n; c++) + r[o] = r[o] + s[0], o++, r[o] = r[o] + s[1], o++, r[o] = r[o] + s[2], o++; + const l = Math.floor(i.length / 3); + for (let c = 0; c < a.length; c++) + a[c] = a[c] + l; + e.push(...a), i.push(...r); +}); +I(ae, "makeCylinder", function(i, e, t, s, r, a = 20, n = !0) { + a < 3 && (a = 3); + const o = le(); + de(o, s, t), Pe(o, o); + const l = ae.getFirstPerpVector(o), c = le(); + Ci(c, o, l), Pe(c, c); + let h = 2 * a, u = 2 * a; + n && (u += 2 * a, h += 2); + const d = Math.floor(i.length / 3), f = new Uint32Array(u * 3), g = new Float32Array(h * 3); + function m(D, b) { + g[D * 3 + 0] = b[0], g[D * 3 + 1] = b[1], g[D * 3 + 2] = b[2]; + } + function p(D, b, C, E) { + f[D * 3 + 0] = b + d, f[D * 3 + 1] = C + d, f[D * 3 + 2] = E + d; + } + const v = 2 * a, A = v + 1; + n && (m(v, t), m(A, s)); + const x = le(), w = le(); + for (let D = 0; D < a; D++) { + const b = Math.cos(D / a * 2 * Math.PI), C = Math.sin(D / a * 2 * Math.PI); + x[0] = r * (b * l[0] + C * c[0]), x[1] = r * (b * l[1] + C * c[1]), x[2] = r * (b * l[2] + C * c[2]), At(w, t, x), m(D, w), At(w, s, x), m(D + a, w); + let E = 0; + D < a - 1 && (E = D + 1), p(D * 2, D, E, D + a), p(D * 2 + 1, E, E + a, D + a), n && (p(a * 2 + D, D, v, E), p(a * 2 + D + a, A, D + a, E + a)); + } + e.push(...f), i.push(...g); +}); +I(ae, "makeColoredCylinder", function(i, e, t, s, r, a, n = [192, 0, 0, 255], o = 20, l = !1) { + let c = i.length / 3; + ae.makeCylinder(i, e, s, r, a, o, l), c = i.length / 3 - c; + const h = []; + for (let u = 0; u < c * 4 - 1; u += 4) + h[u] = n[0], h[u + 1] = n[1], h[u + 2] = n[2], h[u + 3] = n[3]; + t.push(...h); +}); +I(ae, "makeColoredSphere", function(i, e, t, s, r = [0, 0, 0], a = [0, 0, 192, 255]) { + let n = i.length / 3; + ae.makeSphere(i, e, s, r), n = i.length / 3 - n; + const o = []; + for (let l = 0; l < n * 4 - 1; l += 4) + o[l] = a[0], o[l + 1] = a[1], o[l + 2] = a[2], o[l + 3] = a[3]; + t.push(...o); +}); +var xt = ae, zn = class ze { + constructor({ name: e = "niivue", level: t = "info" } = {}) { + I(this, "level"), I(this, "name"), this.name = `${e}`, this.level = t; + } + debug(...e) { + ze.levels[this.level] > ze.levels.debug || console.debug(`${this.name}-debug`, ...e); + } + info(...e) { + ze.levels[this.level] > ze.levels.info || console.info(`${this.name}-info`, ...e); + } + warn(...e) { + ze.levels[this.level] > ze.levels.warn || console.warn(`${this.name}-warn`, ...e); + } + error(...e) { + ze.levels[this.level] > ze.levels.error || console.error(`${this.name}-error`, ...e); + } + fatal(...e) { + ze.levels[this.level] > ze.levels.fatal || console.error(`${this.name}-fatal`, ...e); + } + setLogLevel(e) { + this.level = e; + } + setName(e) { + this.name = e; + } +}; +I(zn, "levels", { + debug: 0, + info: 1, + warn: 2, + error: 3, + fatal: 4, + silent: 1 / 0 +}); +var wl = zn, R = new wl({ name: "niivue", level: "info" }), Mi = {}; +Yi(Mi, { + $itksnap: () => bl, + $slicer3d: () => yl, + actc: () => Cl, + afni_blues_inv: () => Nc, + afni_reds_inv: () => Pc, + batlow: () => Dl, + bcgwhw: () => Fl, + bcgwhw_dark: () => El, + blue: () => Bl, + blue2cyan: () => Sl, + blue2magenta: () => Tl, + blue2red: () => Ml, + bluegrn: () => Il, + bone: () => kl, + bronze: () => Rl, + cet_l17: () => Vl, + cividis: () => Ul, + cool: () => Nl, + copper: () => Ll, + copper2: () => Pl, + ct_airways: () => Ol, + ct_artery: () => zl, + ct_bones: () => Gl, + ct_brain: () => _l, + ct_brain_gray: () => Yl, + ct_cardiac: () => ql, + ct_head: () => Hl, + ct_kidneys: () => Wl, + ct_liver: () => Kl, + ct_muscles: () => Xl, + ct_scalp: () => jl, + ct_skull: () => Zl, + ct_soft: () => Ql, + ct_soft_tissue: () => Jl, + ct_surface: () => $l, + ct_vessels: () => ec, + ct_w_contrast: () => tc, + cubehelix: () => ic, + electric_blue: () => sc, + freesurfer: () => rc, + ge_color: () => nc, + gold: () => ac, + gray: () => oc, + green: () => lc, + green2cyan: () => cc, + green2orange: () => hc, + hot: () => dc, + hotiron: () => uc, + hsv: () => fc, + inferno: () => mc, + jet: () => gc, + kry: () => pc, + linspecer: () => Ac, + lipari: () => vc, + magma: () => xc, + mako: () => wc, + navia: () => bc, + nih: () => yc, + plasma: () => Cc, + random: () => Dc, + red: () => Ec, + redyell: () => Fc, + rocket: () => Mc, + roi_i256: () => Lc, + surface: () => Tc, + thermal: () => Sc, + turbo: () => Ic, + violet: () => Bc, + viridis: () => kc, + warm: () => Rc, + winter: () => Vc, + x_rain: () => Uc +}); +var bl = { + R: [ + 0, + 255, + 0, + 0, + 255, + 0, + 255, + 255, + 0, + 205, + 210, + 102, + 0, + 0, + 46, + 255, + 106, + 221, + 233, + 165, + 255, + 147, + 218, + 75, + 255, + 60, + 255, + 255, + 218, + 0, + 188, + 255, + 255, + 222, + 127, + 139, + 124, + 255, + 70, + 0, + 238, + 238, + 240, + 245, + 184, + 32, + 255, + 25, + 112, + 34, + 248, + 245, + 255, + 144, + 173, + 65, + 255, + 250, + 128, + 50, + 244, + 255, + 123, + 255, + 173, + 255, + 127, + 255, + 143, + 220, + 253, + 255, + 0, + 0, + 128, + 255, + 250, + 148, + 178, + 255, + 135, + 100, + 240, + 250, + 255, + 107, + 135, + 0, + 139, + 245, + 186, + 255, + 255, + 0, + 210, + 255, + 47, + 72, + 175, + 128, + 176, + 255, + 139, + 240, + 255, + 216, + 119, + 219, + 72, + 255, + 199, + 154, + 189, + 240, + 230, + 0, + 85, + 64, + 153, + 205, + 250, + 95, + 0, + 255, + 224, + 176, + 138, + 30, + 240, + 152, + 160 + ], + G: [ + 0, + 0, + 255, + 0, + 255, + 255, + 0, + 239, + 0, + 133, + 180, + 205, + 0, + 139, + 139, + 228, + 90, + 160, + 150, + 42, + 250, + 112, + 112, + 0, + 182, + 179, + 235, + 228, + 165, + 128, + 143, + 105, + 218, + 184, + 255, + 69, + 252, + 255, + 130, + 100, + 130, + 232, + 255, + 222, + 134, + 178, + 20, + 25, + 128, + 139, + 248, + 255, + 160, + 238, + 255, + 105, + 99, + 240, + 0, + 205, + 164, + 255, + 104, + 165, + 216, + 192, + 255, + 140, + 188, + 20, + 245, + 250, + 206, + 255, + 0, + 250, + 128, + 0, + 34, + 127, + 206, + 149, + 230, + 235, + 245, + 142, + 206, + 0, + 0, + 245, + 85, + 228, + 222, + 191, + 105, + 248, + 79, + 61, + 238, + 128, + 224, + 240, + 0, + 255, + 215, + 191, + 136, + 112, + 209, + 0, + 21, + 205, + 183, + 248, + 230, + 250, + 107, + 224, + 50, + 92, + 250, + 158, + 128, + 69, + 255, + 196, + 43, + 144, + 128, + 251, + 82 + ], + B: [ + 0, + 0, + 0, + 255, + 0, + 255, + 255, + 213, + 205, + 63, + 140, + 170, + 128, + 139, + 87, + 225, + 205, + 221, + 122, + 42, + 250, + 219, + 214, + 130, + 193, + 113, + 205, + 196, + 32, + 128, + 143, + 180, + 185, + 135, + 0, + 19, + 0, + 224, + 180, + 0, + 238, + 170, + 240, + 179, + 11, + 170, + 147, + 112, + 144, + 34, + 255, + 250, + 122, + 144, + 47, + 225, + 71, + 230, + 0, + 50, + 96, + 240, + 238, + 0, + 230, + 203, + 212, + 0, + 143, + 60, + 230, + 240, + 209, + 127, + 128, + 205, + 114, + 211, + 34, + 80, + 235, + 237, + 140, + 215, + 238, + 35, + 250, + 139, + 139, + 220, + 211, + 181, + 173, + 255, + 30, + 220, + 79, + 139, + 238, + 0, + 230, + 245, + 0, + 255, + 0, + 216, + 153, + 147, + 204, + 255, + 133, + 50, + 107, + 255, + 250, + 154, + 47, + 208, + 204, + 92, + 210, + 160, + 0, + 0, + 255, + 222, + 226, + 255, + 128, + 152, + 45 + ], + A: [ + 0, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255 + ], + I: [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130 + ] +}, yl = { + labels: [ + "background", + "tissue", + "bone", + "skin", + "connective tissue", + "blood", + "organ", + "mass", + "muscle", + "foreign object", + "waste", + "teeth", + "fat", + "gray matter", + "white matter", + "nerve", + "vein", + "artery", + "capillary", + "ligament", + "tendon", + "cartilage", + "meniscus", + "lymph node", + "lymphatic vessel", + "cerebro-spinal fluid", + "bile", + "urine", + "feces", + "gas", + "fluid", + "edema", + "bleeding", + "necrosis", + "clot", + "embolism", + "head", + "central nervous system", + "brain", + "gray matter of brain", + "telencephalon", + "cerebral cortex", + "right frontal lobe", + "left frontal lobe", + "right temporal lobe", + "left temporal lobe", + "right parietal lobe", + "left parietal lobe", + "right occipital lobe", + "left occipital lobe", + "right insular lobe", + "left insular lobe", + "right limbic lobe", + "left limbic lobe", + "right striatum", + "left striatum", + "right caudate nucleus", + "left caudate nucleus", + "right putamen", + "left putamen", + "right pallidum", + "left pallidum", + "right amygdaloid complex", + "left amygdaloid complex", + "diencephalon", + "thalamus", + "right thalamus", + "left thalamus", + "pineal gland", + "midbrain", + "substantia nigra", + "right substantia nigra", + "left substantia nigra", + "cerebral white matter", + "right superior longitudinal fasciculus", + "left superior longitudinal fasciculus", + "right inferior longitudinal fasciculus", + "left inferior longitudinal fasciculus", + "right arcuate fasciculus", + "left arcuate fasciculus", + "right uncinate fasciculus", + "left uncinate fasciculus", + "right cingulum bundle", + "left cingulum bundle", + "projection fibers", + "right corticospinal tract", + "left corticospinal tract", + "right optic radiation", + "left optic radiation", + "right medial lemniscus", + "left medial lemniscus", + "right superior cerebellar peduncle", + "left superior cerebellar peduncle", + "right middle cerebellar peduncle", + "left middle cerebellar peduncle", + "right inferior cerebellar peduncle", + "left inferior cerebellar peduncle", + "optic chiasm", + "right optic tract", + "left optic tract", + "right fornix", + "left fornix", + "commissural fibers", + "corpus callosum", + "posterior commissure", + "cerebellar white matter", + "CSF space", + "ventricles of brain", + "right lateral ventricle", + "left lateral ventricle", + "right third ventricle", + "left third ventricle", + "cerebral aqueduct", + "fourth ventricle", + "subarachnoid space", + "spinal cord", + "gray matter of spinal cord", + "white matter of spinal cord", + "endocrine system of brain", + "pituitary gland", + "adenohypophysis", + "neurohypophysis", + "meninges", + "dura mater", + "arachnoid", + "pia mater", + "muscles of head", + "salivary glands", + "lips", + "nose", + "tongue", + "soft palate", + "right inner ear", + "left inner ear", + "right external ear", + "left external ear", + "right middle ear", + "left middle ear", + "right eyeball", + "left eyeball", + "skull", + "right frontal bone", + "left frontal bone", + "right parietal bone", + "left parietal bone", + "right temporal bone", + "left temporal bone", + "right sphenoid bone", + "left sphenoid bone", + "right ethmoid bone", + "left ethmoid bone", + "occipital bone", + "maxilla", + "right zygomatic bone", + "right lacrimal bone", + "vomer bone", + "right palatine bone", + "left palatine bone", + "mandible", + "neck", + "muscles of neck", + "pharynx", + "larynx", + "thyroid gland", + "right parathyroid glands", + "left parathyroid glands", + "skeleton of neck", + "hyoid bone", + "cervical vertebral column", + "thorax", + "trachea", + "bronchi", + "right lung", + "left lung", + "superior lobe of right lung", + "superior lobe of left lung", + "middle lobe of right lung", + "inferior lobe of right lung", + "inferior lobe of left lung", + "pleura", + "heart", + "right atrium", + "left atrium", + "atrial septum", + "ventricular septum", + "right ventricle of heart", + "left ventricle of heart", + "mitral valve", + "tricuspid valve", + "aortic valve", + "pulmonary valve", + "aorta", + "pericardium", + "pericardial cavity", + "esophagus", + "thymus", + "mediastinum", + "skin of thoracic wall", + "muscles of thoracic wall", + "skeleton of thorax", + "thoracic vertebral column", + "ribs", + "sternum", + "right clavicle", + "left clavicle", + "abdominal cavity", + "abdomen", + "peritoneum", + "omentum", + "peritoneal cavity", + "retroperitoneal space", + "stomach", + "duodenum", + "small bowel", + "colon", + "anus", + "liver", + "biliary tree", + "gallbladder", + "pancreas", + "spleen", + "urinary system", + "right kidney", + "left kidney", + "right ureter", + "left ureter", + "urinary bladder", + "urethra", + "right adrenal gland", + "left adrenal gland", + "female internal genitalia", + "uterus", + "right fallopian tube", + "left fallopian tube", + "right ovary", + "left ovary", + "vagina", + "male internal genitalia", + "prostate", + "right seminal vesicle", + "left seminal vesicle", + "right deferent duct", + "left deferent duct", + "skin of abdominal wall", + "muscles of abdominal wall", + "skeleton of abdomen", + "lumbar vertebral column", + "female external genitalia", + "male external genitalia", + "skeleton of upper limb", + "muscles of upper limb", + "right upper limb", + "left upper limb", + "right shoulder", + "left shoulder", + "right arm" + ], + R: [ + 0, + 128, + 241, + 177, + 111, + 216, + 221, + 144, + 192, + 220, + 78, + 255, + 230, + 200, + 250, + 244, + 0, + 216, + 183, + 183, + 152, + 111, + 178, + 68, + 111, + 85, + 0, + 214, + 78, + 218, + 170, + 140, + 188, + 216, + 145, + 150, + 177, + 244, + 250, + 200, + 68, + 128, + 83, + 83, + 162, + 162, + 141, + 141, + 182, + 182, + 188, + 188, + 154, + 154, + 177, + 177, + 30, + 30, + 210, + 210, + 48, + 48, + 98, + 98, + 69, + 166, + 122, + 122, + 253, + 145, + 46, + 0, + 0, + 250, + 127, + 127, + 159, + 159, + 125, + 125, + 106, + 106, + 154, + 154, + 126, + 201, + 201, + 78, + 78, + 174, + 174, + 139, + 139, + 148, + 148, + 186, + 186, + 99, + 156, + 156, + 64, + 64, + 138, + 97, + 126, + 194, + 85, + 88, + 88, + 88, + 88, + 88, + 88, + 88, + 88, + 244, + 200, + 250, + 82, + 57, + 60, + 92, + 255, + 255, + 255, + 255, + 201, + 70, + 188, + 177, + 166, + 182, + 229, + 229, + 174, + 174, + 201, + 201, + 194, + 194, + 241, + 203, + 203, + 229, + 229, + 255, + 255, + 209, + 209, + 248, + 248, + 255, + 196, + 255, + 255, + 255, + 242, + 242, + 222, + 177, + 213, + 184, + 150, + 62, + 62, + 62, + 242, + 250, + 255, + 177, + 182, + 175, + 197, + 197, + 172, + 172, + 202, + 224, + 224, + 255, + 206, + 210, + 203, + 233, + 195, + 181, + 152, + 159, + 166, + 218, + 225, + 224, + 255, + 184, + 211, + 47, + 255, + 173, + 188, + 255, + 226, + 253, + 244, + 205, + 205, + 186, + 177, + 255, + 234, + 204, + 180, + 216, + 255, + 205, + 204, + 255, + 221, + 0, + 139, + 249, + 157, + 203, + 185, + 185, + 247, + 247, + 222, + 124, + 249, + 249, + 244, + 255, + 255, + 227, + 213, + 213, + 193, + 216, + 230, + 245, + 245, + 241, + 241, + 177, + 171, + 217, + 212, + 185, + 185, + 198, + 194, + 177, + 177, + 177, + 177, + 177 + ], + G: [ + 0, + 174, + 214, + 122, + 184, + 101, + 130, + 238, + 104, + 245, + 63, + 250, + 220, + 200, + 250, + 214, + 151, + 101, + 156, + 214, + 189, + 184, + 212, + 172, + 197, + 188, + 145, + 230, + 63, + 255, + 250, + 224, + 65, + 191, + 60, + 98, + 122, + 214, + 250, + 200, + 131, + 174, + 146, + 146, + 115, + 115, + 93, + 93, + 166, + 166, + 135, + 135, + 150, + 150, + 140, + 140, + 111, + 111, + 157, + 157, + 129, + 129, + 153, + 153, + 110, + 113, + 101, + 101, + 135, + 92, + 101, + 108, + 108, + 250, + 150, + 150, + 116, + 116, + 102, + 102, + 174, + 174, + 146, + 146, + 126, + 160, + 160, + 152, + 152, + 140, + 140, + 126, + 126, + 120, + 120, + 135, + 135, + 106, + 171, + 171, + 123, + 123, + 95, + 113, + 161, + 195, + 188, + 106, + 106, + 106, + 106, + 106, + 106, + 106, + 106, + 214, + 200, + 250, + 174, + 157, + 143, + 162, + 244, + 244, + 244, + 244, + 121, + 163, + 91, + 122, + 84, + 105, + 147, + 147, + 122, + 122, + 112, + 112, + 142, + 142, + 213, + 179, + 179, + 204, + 204, + 243, + 243, + 185, + 185, + 223, + 223, + 230, + 172, + 255, + 250, + 237, + 217, + 217, + 198, + 122, + 124, + 105, + 208, + 162, + 162, + 162, + 206, + 210, + 255, + 122, + 228, + 216, + 165, + 165, + 138, + 138, + 164, + 186, + 186, + 245, + 110, + 115, + 108, + 138, + 100, + 85, + 55, + 63, + 70, + 123, + 130, + 97, + 244, + 122, + 171, + 150, + 244, + 121, + 95, + 239, + 202, + 232, + 217, + 179, + 179, + 124, + 122, + 255, + 234, + 142, + 119, + 132, + 253, + 167, + 168, + 224, + 130, + 145, + 150, + 180, + 108, + 136, + 102, + 102, + 182, + 182, + 154, + 186, + 186, + 186, + 170, + 181, + 190, + 153, + 141, + 141, + 123, + 146, + 158, + 172, + 172, + 172, + 172, + 124, + 85, + 198, + 188, + 135, + 135, + 175, + 98, + 122, + 122, + 122, + 122, + 122 + ], + B: [ + 0, + 128, + 145, + 101, + 210, + 79, + 101, + 144, + 88, + 20, + 0, + 220, + 70, + 235, + 210, + 49, + 206, + 79, + 220, + 211, + 207, + 210, + 242, + 100, + 131, + 255, + 30, + 130, + 0, + 255, + 250, + 228, + 28, + 216, + 66, + 83, + 101, + 49, + 225, + 215, + 98, + 128, + 164, + 164, + 105, + 105, + 137, + 137, + 110, + 110, + 166, + 166, + 201, + 201, + 190, + 190, + 85, + 85, + 166, + 166, + 126, + 126, + 112, + 112, + 53, + 137, + 38, + 38, + 192, + 109, + 131, + 112, + 112, + 225, + 88, + 88, + 163, + 163, + 154, + 154, + 155, + 155, + 83, + 83, + 55, + 133, + 133, + 141, + 141, + 103, + 103, + 177, + 177, + 72, + 72, + 135, + 135, + 24, + 108, + 108, + 147, + 147, + 74, + 158, + 197, + 164, + 255, + 215, + 215, + 215, + 215, + 215, + 215, + 215, + 215, + 49, + 215, + 225, + 128, + 110, + 83, + 109, + 209, + 209, + 209, + 209, + 77, + 117, + 95, + 101, + 94, + 107, + 118, + 118, + 90, + 90, + 73, + 73, + 0, + 0, + 144, + 77, + 77, + 109, + 109, + 152, + 152, + 85, + 85, + 131, + 131, + 138, + 68, + 167, + 160, + 145, + 123, + 123, + 101, + 101, + 109, + 108, + 243, + 114, + 114, + 114, + 142, + 139, + 207, + 101, + 255, + 244, + 145, + 145, + 115, + 115, + 140, + 162, + 162, + 217, + 84, + 89, + 81, + 112, + 73, + 57, + 13, + 27, + 38, + 97, + 104, + 76, + 209, + 154, + 143, + 103, + 209, + 88, + 76, + 172, + 134, + 158, + 154, + 108, + 108, + 161, + 101, + 220, + 194, + 178, + 153, + 105, + 229, + 142, + 143, + 199, + 101, + 30, + 98, + 111, + 162, + 116, + 83, + 83, + 164, + 164, + 132, + 223, + 150, + 150, + 147, + 158, + 165, + 130, + 113, + 113, + 103, + 127, + 140, + 147, + 147, + 151, + 151, + 92, + 68, + 131, + 102, + 134, + 134, + 125, + 79, + 101, + 101, + 101, + 101, + 101 + ], + A: [ + 0, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255 + ], + I: [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239, + 240, + 241, + 242, + 243, + 244, + 245, + 246, + 247, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + 255 + ] +}, Cl = { + R: [0, 0, 24, 248, 255], + G: [0, 0, 177, 254, 0], + B: [0, 136, 0, 0, 0], + A: [0, 32, 64, 78, 128], + I: [0, 64, 128, 156, 255] +}, Dl = { + R: [1, 10, 15, 17, 21, 27, 36, 49, 65, 82, 99, 118, 140, 161, 183, 203, 222, 238, 248, 253, 253, 253, 252, 250], + G: [25, 42, 56, 68, 79, 88, 97, 105, 111, 116, 122, 127, 133, 138, 142, 146, 150, 155, 162, 170, 178, 186, 195, 204], + B: [89, 92, 95, 96, 98, 98, 96, 91, 83, 74, 64, 55, 46, 43, 49, 62, 79, 100, 126, 151, 175, 199, 223, 250], + A: [0, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64], + I: [0, 11, 22, 33, 44, 55, 66, 77, 88, 99, 110, 121, 133, 144, 155, 166, 177, 188, 199, 210, 221, 232, 243, 255] +}, El = { + R: [ + 0, + 248, + 242, + 235, + 229, + 222, + 212, + 202, + 193, + 183, + 173, + 145, + 117, + 78, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 64, + 128, + 191, + 255, + 255, + 255, + 255, + 255, + 238, + 221, + 204, + 214, + 224, + 235, + 245, + 255, + 249, + 242, + 236, + 229, + 223, + 217, + 210, + 204, + 197, + 191, + 184, + 176, + 169, + 161, + 154, + 146, + 139, + 132, + 124, + 117, + 109, + 102, + 0 + ], + G: [ + 0, + 251, + 247, + 243, + 239, + 235, + 235, + 235, + 235, + 235, + 235, + 216, + 197, + 174, + 150, + 127, + 153, + 178, + 204, + 229, + 255, + 255, + 255, + 255, + 255, + 220, + 185, + 150, + 115, + 77, + 38, + 0, + 31, + 61, + 92, + 122, + 153, + 138, + 122, + 107, + 92, + 77, + 61, + 46, + 31, + 15, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + B: [ + 0, + 254, + 253, + 252, + 251, + 250, + 251, + 252, + 253, + 254, + 255, + 226, + 198, + 132, + 66, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 80, + 120, + 160, + 200, + 199, + 198, + 197, + 196, + 196, + 195, + 194, + 193, + 192, + 191, + 184, + 176, + 169, + 161, + 154, + 146, + 139, + 132, + 124, + 117, + 109, + 102, + 0 + ], + A: [ + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256 + ], + I: [ + 0, + 4, + 8, + 12, + 16, + 20, + 24, + 28, + 32, + 36, + 40, + 44, + 48, + 52, + 56, + 60, + 64, + 68, + 72, + 76, + 80, + 84, + 88, + 92, + 96, + 100, + 104, + 108, + 112, + 116, + 120, + 124, + 128, + 132, + 136, + 140, + 144, + 148, + 152, + 156, + 160, + 164, + 168, + 172, + 176, + 180, + 184, + 188, + 192, + 196, + 200, + 204, + 208, + 212, + 216, + 220, + 224, + 228, + 232, + 236, + 240, + 244, + 248, + 255 + ] +}, Fl = { + R: [ + 255, + 248, + 242, + 235, + 229, + 222, + 212, + 202, + 193, + 183, + 173, + 145, + 117, + 78, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 64, + 128, + 191, + 255, + 255, + 255, + 255, + 255, + 238, + 221, + 204, + 214, + 224, + 235, + 245, + 255, + 249, + 242, + 236, + 229, + 223, + 217, + 210, + 204, + 197, + 191, + 184, + 176, + 169, + 161, + 154, + 146, + 139, + 132, + 124, + 117, + 109, + 102, + 0 + ], + G: [ + 255, + 251, + 247, + 243, + 239, + 235, + 235, + 235, + 235, + 235, + 235, + 216, + 197, + 174, + 150, + 127, + 153, + 178, + 204, + 229, + 255, + 255, + 255, + 255, + 255, + 220, + 185, + 150, + 115, + 77, + 38, + 0, + 31, + 61, + 92, + 122, + 153, + 138, + 122, + 107, + 92, + 77, + 61, + 46, + 31, + 15, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + B: [ + 255, + 254, + 253, + 252, + 251, + 250, + 251, + 252, + 253, + 254, + 255, + 226, + 198, + 132, + 66, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 80, + 120, + 160, + 200, + 199, + 198, + 197, + 196, + 196, + 195, + 194, + 193, + 192, + 191, + 184, + 176, + 169, + 161, + 154, + 146, + 139, + 132, + 124, + 117, + 109, + 102, + 0 + ], + A: [ + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256, + 256 + ], + I: [ + 0, + 4, + 8, + 12, + 16, + 20, + 24, + 28, + 32, + 36, + 40, + 44, + 48, + 52, + 56, + 60, + 64, + 68, + 72, + 76, + 80, + 84, + 88, + 92, + 96, + 100, + 104, + 108, + 112, + 116, + 120, + 124, + 128, + 132, + 136, + 140, + 144, + 148, + 152, + 156, + 160, + 164, + 168, + 172, + 176, + 180, + 184, + 188, + 192, + 196, + 200, + 204, + 208, + 212, + 216, + 220, + 224, + 228, + 232, + 236, + 240, + 244, + 248, + 255 + ] +}, Ml = { + R: [0, 0, 0, 0, 196, 255], + G: [0, 32, 128, 128, 128, 32], + B: [0, 255, 196, 0, 0, 0], + A: [0, 128, 64, 64, 64, 128], + I: [0, 1, 64, 128, 192, 255] +}, Tl = { + R: [0, 255], + G: [0, 0], + B: [255, 255], + A: [0, 128], + I: [0, 255] +}, Sl = { + R: [0, 0], + G: [0, 255], + B: [255, 255], + A: [0, 128], + I: [0, 255] +}, Il = { + R: [0, 0, 0, 0], + G: [0, 1, 128, 255], + B: [0, 222, 127, 32], + A: [0, 0, 64, 128], + I: [0, 1, 128, 255] +}, Bl = { + R: [0, 0, 0], + G: [0, 0, 0], + B: [0, 128, 255], + A: [0, 64, 128], + I: [0, 128, 255] +}, kl = { + R: [0, 103, 255], + G: [0, 126, 255], + B: [0, 165, 255], + A: [0, 76, 128], + I: [0, 153, 255] +}, Rl = { + R: [0, 43, 103, 199, 216, 255], + G: [0, 0, 37, 155, 213, 255], + B: [0, 0, 20, 97, 201, 255], + A: [0, 44, 48, 54, 56, 56], + I: [0, 64, 128, 196, 240, 255] +}, Vl = { + R: [ + 0, + 9, + 24, + 33, + 40, + 46, + 52, + 57, + 62, + 66, + 70, + 74, + 78, + 81, + 85, + 88, + 91, + 94, + 98, + 101, + 103, + 106, + 109, + 112, + 114, + 117, + 119, + 121, + 124, + 126, + 128, + 130, + 133, + 135, + 137, + 139, + 141, + 143, + 145, + 147, + 149, + 151, + 153, + 155, + 157, + 159, + 161, + 163, + 164, + 166, + 168, + 169, + 171, + 172, + 174, + 175, + 177, + 178, + 180, + 181, + 183, + 184, + 186, + 187, + 189, + 190, + 191, + 193, + 194, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 222, + 223, + 224, + 224, + 225, + 226, + 226, + 227, + 228, + 228, + 229, + 229, + 230, + 231, + 231, + 232, + 233, + 233, + 234, + 234, + 235, + 235, + 236, + 236, + 236, + 237, + 237, + 237, + 238, + 238, + 238, + 239, + 239, + 239, + 240, + 240, + 240, + 241, + 241, + 241, + 242, + 242, + 242, + 243, + 243, + 243, + 243, + 243, + 243, + 243, + 243, + 244, + 244, + 244, + 244, + 244, + 244, + 244, + 244, + 244, + 244, + 244, + 245, + 245, + 245, + 245, + 245, + 245, + 245, + 245, + 245, + 245, + 245, + 245, + 245, + 245, + 245, + 245, + 244, + 244, + 244, + 244, + 244, + 244, + 244, + 244, + 244, + 244, + 244, + 244, + 244, + 244, + 244, + 243, + 243, + 243, + 243, + 243, + 243, + 243, + 243, + 243, + 243, + 243, + 243, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 241, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 243, + 243, + 244, + 244, + 245, + 246, + 246, + 247, + 247, + 248, + 249, + 249, + 250, + 250, + 251, + 251, + 252, + 252, + 253, + 253, + 254, + 254, + 254 + ], + G: [ + 42, + 41, + 41, + 41, + 40, + 40, + 40, + 39, + 39, + 39, + 38, + 38, + 37, + 37, + 36, + 36, + 35, + 35, + 34, + 34, + 33, + 32, + 32, + 31, + 30, + 30, + 30, + 30, + 29, + 29, + 29, + 29, + 28, + 28, + 28, + 27, + 27, + 27, + 26, + 26, + 26, + 25, + 25, + 25, + 24, + 24, + 24, + 24, + 24, + 25, + 25, + 26, + 27, + 27, + 28, + 28, + 29, + 30, + 30, + 31, + 31, + 32, + 33, + 33, + 34, + 34, + 35, + 36, + 36, + 37, + 38, + 39, + 40, + 42, + 43, + 44, + 45, + 47, + 48, + 49, + 50, + 51, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 62, + 63, + 64, + 65, + 66, + 68, + 69, + 71, + 72, + 73, + 75, + 76, + 77, + 79, + 80, + 81, + 83, + 84, + 85, + 87, + 88, + 89, + 90, + 92, + 93, + 94, + 95, + 97, + 98, + 100, + 101, + 102, + 104, + 105, + 107, + 108, + 109, + 111, + 112, + 113, + 115, + 116, + 117, + 119, + 120, + 121, + 122, + 124, + 125, + 126, + 128, + 129, + 130, + 132, + 133, + 134, + 136, + 137, + 138, + 140, + 141, + 142, + 144, + 145, + 146, + 147, + 149, + 150, + 151, + 153, + 154, + 155, + 156, + 158, + 159, + 160, + 161, + 163, + 164, + 165, + 167, + 168, + 169, + 170, + 172, + 173, + 174, + 175, + 176, + 178, + 179, + 180, + 181, + 183, + 184, + 185, + 186, + 187, + 189, + 190, + 191, + 192, + 193, + 194, + 196, + 197, + 198, + 199, + 200, + 201, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 238, + 239, + 240, + 241, + 241, + 242, + 243, + 244, + 244, + 245, + 246, + 247, + 248, + 248, + 249, + 250, + 251, + 251, + 252, + 253, + 254, + 255 + ], + B: [ + 167, + 167, + 166, + 166, + 166, + 165, + 165, + 165, + 164, + 164, + 164, + 163, + 163, + 162, + 162, + 162, + 161, + 161, + 161, + 160, + 160, + 160, + 159, + 159, + 158, + 158, + 157, + 157, + 156, + 155, + 155, + 154, + 154, + 153, + 153, + 152, + 151, + 151, + 150, + 150, + 149, + 149, + 148, + 147, + 147, + 146, + 146, + 145, + 144, + 144, + 143, + 143, + 142, + 141, + 141, + 140, + 139, + 139, + 138, + 137, + 137, + 136, + 135, + 135, + 134, + 133, + 133, + 132, + 131, + 131, + 130, + 130, + 129, + 128, + 128, + 127, + 127, + 126, + 125, + 125, + 124, + 124, + 123, + 123, + 122, + 121, + 121, + 120, + 119, + 119, + 118, + 118, + 117, + 116, + 116, + 116, + 115, + 115, + 114, + 114, + 113, + 113, + 113, + 112, + 112, + 111, + 111, + 110, + 110, + 109, + 109, + 109, + 108, + 108, + 107, + 107, + 106, + 106, + 106, + 106, + 106, + 105, + 105, + 105, + 105, + 105, + 105, + 105, + 104, + 104, + 104, + 104, + 104, + 103, + 103, + 103, + 103, + 103, + 102, + 102, + 102, + 103, + 103, + 103, + 103, + 104, + 104, + 104, + 104, + 104, + 105, + 105, + 105, + 105, + 106, + 106, + 106, + 106, + 106, + 107, + 107, + 107, + 107, + 108, + 108, + 109, + 110, + 111, + 111, + 112, + 113, + 113, + 114, + 115, + 115, + 116, + 117, + 117, + 118, + 119, + 120, + 120, + 121, + 122, + 122, + 123, + 124, + 125, + 126, + 128, + 129, + 130, + 131, + 132, + 134, + 135, + 136, + 137, + 138, + 140, + 141, + 142, + 143, + 144, + 146, + 147, + 148, + 149, + 150, + 152, + 153, + 155, + 157, + 158, + 160, + 162, + 163, + 165, + 167, + 168, + 170, + 172, + 173, + 175, + 177, + 178, + 180, + 182, + 183, + 185, + 187, + 188, + 190, + 193, + 196, + 199, + 201, + 204, + 207, + 210, + 212, + 215, + 218, + 221, + 224, + 226, + 229, + 232, + 235, + 238, + 240, + 243, + 246, + 249, + 252, + 255 + ], + A: [ + 0, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64 + ], + I: [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239, + 240, + 241, + 242, + 243, + 244, + 245, + 246, + 247, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + 255 + ] +}, Ul = { + R: [0, 86, 166, 255], + G: [32, 92, 156, 233], + B: [76, 108, 117, 69], + A: [0, 56, 80, 88], + I: [0, 64, 192, 255] +}, Nl = { + R: [0, 0, 0], + G: [127, 196, 254], + B: [255, 255, 255], + A: [0, 64, 128], + I: [0, 128, 255] +}, Pl = { + R: [0, 61, 122, 183, 244, 255], + G: [0, 41, 81, 122, 163, 255], + B: [0, 25, 51, 76, 102, 255], + A: [0, 25, 51, 71, 102, 128], + I: [0, 51, 102, 153, 204, 255] +}, Ll = { + R: [0, 61, 122, 183, 244, 255], + G: [0, 41, 81, 122, 163, 203], + B: [0, 25, 51, 76, 102, 127], + A: [0, 25, 51, 71, 102, 128], + I: [0, 51, 102, 153, 204, 255] +}, Ol = { + min: -643, + max: -235, + R: [0, 0, 0], + G: [154, 154, 154], + B: [179, 179, 101], + A: [0, 32, 0], + I: [0, 163, 255] +}, zl = { + min: 114, + max: 800, + R: [0, 255, 255, 255], + G: [0, 0, 129, 255], + B: [0, 0, 0, 255], + A: [0, 64, 88, 228], + I: [0, 80, 160, 255] +}, Gl = { + min: 180, + max: 600, + R: [0, 0, 113, 255], + G: [0, 0, 109, 250], + B: [0, 0, 101, 245], + A: [0, 0, 100, 160], + I: [0, 1, 128, 255] +}, Yl = { + min: -10, + max: 110, + R: [0, 127, 255], + G: [0, 127, 255], + B: [0, 127, 255], + A: [0, 48, 128], + I: [0, 124, 255] +}, _l = { + min: -10, + max: 110, + R: [0, 199, 255], + G: [0, 127, 255], + B: [0, 127, 255], + A: [0, 48, 128], + I: [0, 124, 255] +}, ql = { + min: -80, + max: 1e3, + R: [0, 189, 150, 150, 150, 150, 255], + G: [0, 169, 54, 54, 54, 54, 240], + B: [0, 153, 52, 52, 52, 52, 242], + A: [0, 32, 64, 0, 0, 64, 64], + I: [0, 1, 82, 92, 234, 242, 255] +}, Hl = { + min: -590, + max: 600, + R: [0, 241, 241, 248, 248, 178, 178, 232, 255, 255, 255], + G: [0, 156, 156, 222, 222, 36, 36, 51, 255, 255, 255], + B: [0, 130, 130, 169, 169, 24, 24, 37, 255, 255, 255], + A: [0, 8, 0, 0, 0, 64, 64, 0, 0, 222, 222], + I: [0, 2, 3, 64, 122, 142, 172, 182, 252, 253, 255] +}, Wl = { + min: 114, + max: 302, + R: [0, 255, 255], + G: [0, 129, 255], + B: [0, 0, 255], + A: [0, 88, 228], + I: [0, 103, 255] +}, Kl = { + min: -23, + max: 246, + R: [0, 44, 255, 255, 255], + G: [0, 128, 90, 255, 255], + B: [0, 0, 70, 0, 255], + A: [0, 0, 82, 184, 228], + I: [0, 64, 131, 196, 255] +}, Xl = { + min: -100, + max: 246, + R: [0, 128, 159, 255, 255, 255, 255], + G: [0, 0, 56, 90, 0, 255, 255], + B: [0, 0, 41, 70, 0, 0, 255], + A: [0, 63, 105, 135, 167, 184, 228], + I: [0, 100, 128, 155, 180, 209, 255] +}, jl = { + min: -590, + max: 600, + R: [0, 241, 241, 248, 248, 178, 232, 255, 255], + G: [0, 156, 156, 222, 222, 36, 51, 255, 255], + B: [0, 130, 130, 169, 169, 24, 37, 255, 255], + A: [0, 63, 105, 135, 167, 184, 228, 228, 228], + I: [0, 1, 52, 127, 137, 162, 172, 252, 255] +}, Zl = { + min: 140, + max: 1024, + R: [0, 2, 113, 255], + G: [0, 1, 109, 250], + B: [0, 1, 101, 245], + A: [0, 1, 96, 168], + I: [0, 1, 128, 255] +}, Ql = { + min: -923, + max: 679, + R: [0, 0, 0, 0, 0, 255, 255, 255], + G: [154, 154, 154, 154, 0, 0, 254, 255], + B: [179, 179, 179, 179, 0, 0, 0, 255], + A: [0, 3, 8, 0, 0, 10, 15, 20], + I: [0, 30, 62, 88, 170, 200, 232, 255] +}, Jl = { + min: -10, + max: 110, + R: [0, 199, 255], + G: [0, 127, 255], + B: [0, 127, 255], + A: [0, 48, 128], + I: [0, 124, 255] +}, $l = { + min: -600, + max: 100, + R: [0, 134, 255], + G: [0, 109, 250], + B: [0, 101, 245], + A: [0, 60, 148], + I: [0, 128, 255] +}, ec = { + min: 114, + max: 246, + R: [0, 255, 255], + G: [0, 128, 255], + B: [0, 128, 255], + A: [0, 64, 96], + I: [0, 87, 255] +}, tc = { + min: 50, + max: 1e3, + R: [98, 210, 169, 128, 255], + G: [94, 26, 77, 128, 255], + B: [45, 21, 74, 128, 255], + A: [0, 25, 0, 4, 168], + I: [0, 41, 87, 154, 255] +}, ic = { + R: [ + 0, + 13, + 21, + 26, + 27, + 25, + 22, + 21, + 22, + 28, + 39, + 54, + 75, + 98, + 124, + 148, + 171, + 189, + 202, + 210, + 213, + 211, + 206, + 200, + 195, + 193, + 195, + 201, + 211, + 225, + 240, + 255 + ], + G: [ + 0, + 5, + 11, + 20, + 31, + 44, + 58, + 72, + 86, + 99, + 109, + 116, + 120, + 122, + 122, + 122, + 121, + 121, + 124, + 129, + 137, + 147, + 161, + 175, + 190, + 205, + 218, + 229, + 238, + 245, + 251, + 255 + ], + B: [ + 0, + 14, + 30, + 46, + 61, + 71, + 77, + 78, + 75, + 68, + 60, + 52, + 48, + 47, + 53, + 65, + 83, + 105, + 131, + 157, + 183, + 205, + 222, + 235, + 241, + 243, + 242, + 240, + 239, + 240, + 245, + 255 + ], + A: [ + 0, + 4, + 8, + 12, + 17, + 21, + 25, + 29, + 33, + 37, + 41, + 45, + 50, + 54, + 58, + 62, + 66, + 70, + 74, + 78, + 83, + 87, + 91, + 95, + 99, + 103, + 107, + 111, + 116, + 120, + 124, + 128 + ], + I: [ + 0, + 8, + 16, + 25, + 33, + 41, + 49, + 58, + 66, + 74, + 82, + 90, + 99, + 107, + 115, + 123, + 132, + 140, + 148, + 156, + 165, + 173, + 181, + 189, + 197, + 206, + 214, + 222, + 230, + 239, + 247, + 255 + ] +}, sc = { + R: [0, 10, 136, 255], + G: [0, 39, 220, 255], + B: [0, 223, 253, 255], + A: [0, 48, 64, 70], + I: [0, 92, 192, 255] +}, rc = { + R: [ + 0, + 245, + 205, + 120, + 196, + 220, + 230, + 0, + 122, + 236, + 12, + 204, + 42, + 119, + 220, + 103, + 60, + 255, + 165, + 160, + 0, + 245, + 205, + 120, + 196, + 220, + 230, + 0, + 122, + 236, + 13, + 220, + 103, + 255, + 165, + 160, + 0, + 120, + 200, + 255, + 255, + 164, + 164, + 164, + 234, + 0, + 0, + 0, + 0, + 0 + ], + G: [ + 0, + 245, + 62, + 18, + 58, + 248, + 148, + 118, + 186, + 13, + 48, + 182, + 204, + 159, + 216, + 255, + 60, + 165, + 42, + 32, + 200, + 245, + 62, + 18, + 58, + 248, + 148, + 118, + 186, + 13, + 48, + 216, + 255, + 165, + 42, + 32, + 200, + 190, + 70, + 148, + 148, + 108, + 108, + 108, + 169, + 0, + 0, + 0, + 0, + 0 + ], + B: [ + 0, + 245, + 78, + 134, + 250, + 164, + 34, + 14, + 220, + 176, + 255, + 142, + 164, + 176, + 20, + 255, + 60, + 0, + 42, + 240, + 200, + 245, + 78, + 134, + 250, + 164, + 34, + 14, + 220, + 176, + 255, + 20, + 255, + 0, + 42, + 240, + 221, + 150, + 255, + 10, + 10, + 226, + 226, + 226, + 30, + 64, + 112, + 160, + 208, + 255 + ], + A: [ + 0, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64 + ], + I: [ + 0, + 2, + 3, + 4, + 5, + 7, + 8, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 24, + 26, + 28, + 30, + 31, + 41, + 42, + 43, + 44, + 46, + 47, + 49, + 50, + 51, + 52, + 53, + 54, + 58, + 60, + 62, + 63, + 72, + 77, + 78, + 79, + 80, + 81, + 82, + 85, + 251, + 252, + 253, + 254, + 255 + ] +}, nc = { + R: [0, 0, 128, 255, 255], + G: [0, 128, 0, 128, 255], + B: [0, 125, 255, 0, 255], + A: [0, 32, 64, 96, 128], + I: [0, 63, 128, 192, 255] +}, ac = { + R: [0, 142, 227, 255], + G: [0, 85, 170, 255], + B: [0, 14, 76, 255], + A: [0, 42, 84, 128], + I: [0, 85, 170, 255] +}, oc = { + R: [0, 255], + G: [0, 255], + B: [0, 255], + A: [0, 128], + I: [0, 255] +}, lc = { + R: [0, 0, 0], + G: [0, 128, 255], + B: [0, 0, 0], + A: [0, 64, 128], + I: [0, 128, 255] +}, cc = { + R: [0, 0, 0], + G: [72, 72, 255], + B: [0, 255, 255], + A: [0, 64, 128], + I: [0, 88, 255] +}, hc = { + R: [0, 255, 255], + G: [72, 88, 255], + B: [0, 0, 0], + A: [0, 64, 128], + I: [0, 88, 255] +}, uc = { + R: [0, 255, 255, 255], + G: [0, 0, 126, 255], + B: [0, 0, 0, 255], + A: [0, 64, 96, 128], + I: [0, 128, 191, 255] +}, dc = { + R: [3, 255, 255, 255], + G: [0, 0, 255, 255], + B: [0, 0, 0, 255], + A: [0, 48, 96, 128], + I: [0, 95, 191, 255] +}, fc = { + R: [255, 255, 0, 0, 0, 255, 255], + G: [0, 255, 255, 255, 0, 0, 0], + B: [0, 0, 0, 255, 255, 255, 0], + A: [0, 14, 28, 43, 57, 71, 85], + I: [0, 43, 85, 128, 170, 213, 255] +}, mc = { + R: [0, 120, 237, 240], + G: [0, 28, 105, 249], + B: [4, 109, 37, 33], + A: [0, 56, 80, 88], + I: [0, 64, 192, 255] +}, gc = { + R: [0, 0, 127, 255, 127], + G: [0, 127, 255, 127, 0], + B: [127, 255, 127, 0, 0], + A: [0, 32, 64, 96, 128], + I: [0, 63, 128, 192, 255] +}, pc = { + R: [0, 255, 255], + G: [0, 0, 255], + B: [0, 0, 0], + A: [0, 64, 64], + I: [0, 86, 255] +}, Ac = { + R: [94, 50, 90, 152, 215, 238, 249, 254, 252, 241, 209, 158], + G: [79, 131, 186, 214, 240, 244, 237, 210, 157, 100, 57, 1], + B: [162, 189, 167, 164, 155, 169, 168, 123, 86, 68, 79, 66], + A: [0, 12, 23, 35, 47, 58, 70, 81, 93, 105, 116, 128], + I: [0, 23, 46, 70, 93, 116, 139, 162, 185, 209, 232, 255] +}, vc = { + R: [3, 7, 13, 25, 45, 67, 84, 98, 110, 124, 138, 154, 173, 191, 210, 225, 233, 233, 231, 229, 230, 235, 243, 253], + G: [19, 33, 48, 63, 77, 87, 92, 94, 95, 96, 96, 97, 99, 101, 106, 118, 133, 149, 163, 177, 192, 208, 225, 245], + B: [38, 58, 79, 99, 114, 122, 122, 120, 117, 113, 110, 105, 101, 97, 94, 96, 103, 112, 122, 134, 149, 169, 192, 218], + A: [0, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64], + I: [0, 11, 22, 33, 44, 55, 66, 77, 88, 99, 110, 121, 133, 144, 155, 166, 177, 188, 199, 210, 221, 232, 243, 255] +}, xc = { + R: [0, 148, 183, 223, 247, 252], + G: [0, 44, 55, 74, 112, 253], + B: [4, 128, 121, 104, 92, 191], + A: [0, 44, 53, 64, 75, 107], + I: [0, 107, 128, 154, 179, 255] +}, wc = { + R: [11, 59, 55, 222], + G: [4, 45, 165, 245], + B: [5, 91, 172, 229], + A: [0, 23, 70, 107], + I: [0, 56, 167, 255] +}, bc = { + R: [3, 5, 6, 8, 12, 19, 28, 36, 42, 48, 54, 61, 68, 76, 86, 96, 110, 129, 154, 181, 205, 224, 239, 252], + G: [19, 32, 44, 58, 72, 86, 98, 108, 116, 122, 128, 134, 141, 149, 157, 167, 178, 191, 204, 216, 225, 232, 239, 244], + B: [39, 60, 82, 104, 123, 137, 144, 145, 142, 138, 134, 130, 126, 121, 116, 110, 105, 104, 112, 131, 155, 178, 198, 217], + A: [0, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64], + I: [0, 11, 22, 33, 44, 55, 66, 77, 88, 99, 110, 121, 133, 144, 155, 166, 177, 188, 199, 210, 221, 232, 243, 255] +}, yc = { + R: [0, 85, 0, 0, 0, 0, 0, 0, 85, 255, 255, 255, 172], + G: [0, 0, 0, 0, 85, 170, 255, 255, 255, 255, 85, 0, 0], + B: [0, 170, 85, 255, 255, 170, 170, 0, 85, 0, 0, 0, 0], + A: [0, 5, 10, 21, 26, 32, 37, 42, 48, 53, 64, 72, 85], + I: [0, 15, 31, 63, 79, 95, 111, 127, 143, 159, 191, 217, 255] +}, Cc = { + R: [13, 156, 237, 240], + G: [8, 23, 121, 249], + B: [135, 158, 83, 33], + A: [0, 56, 80, 88], + I: [0, 64, 192, 255] +}, Dc = { + R: [ + 208, + 71, + 33, + 192, + 32, + 195, + 208, + 173, + 233, + 202, + 25, + 210, + 145, + 89, + 87, + 245, + 246, + 38, + 3, + 25, + 57, + 167, + 245, + 86, + 227, + 208, + 81, + 64, + 90, + 199, + 140, + 48, + 212, + 180, + 70, + 120, + 9, + 192, + 245, + 177, + 65, + 157, + 9, + 193, + 100, + 181, + 125, + 145, + 62, + 8, + 108, + 36, + 140, + 237, + 242, + 248, + 161, + 189, + 41, + 114, + 65, + 121, + 97, + 50, + 238, + 149, + 44, + 214, + 124, + 167, + 40, + 167, + 127, + 178, + 231, + 30, + 173, + 244, + 193, + 203, + 204, + 238, + 139, + 135, + 71, + 234, + 234, + 217, + 66, + 14, + 129, + 19, + 97, + 165, + 112, + 244, + 35, + 73, + 192, + 12, + 149, + 71, + 33, + 192, + 32, + 195, + 208, + 173, + 233, + 202, + 25, + 210, + 145, + 89, + 87, + 245, + 246, + 38, + 3, + 25, + 57, + 167, + 245, + 86, + 227, + 208, + 81, + 64, + 90, + 199, + 140, + 48, + 212, + 180, + 70, + 120, + 9, + 192, + 245, + 177, + 65, + 157, + 9, + 193, + 100, + 181, + 125, + 145, + 62, + 8, + 108, + 36, + 140, + 237, + 242, + 248, + 161, + 189, + 41, + 114, + 65, + 121, + 97, + 50, + 238, + 149, + 44, + 214, + 124, + 167, + 40, + 167, + 127, + 178, + 231, + 30, + 173, + 244, + 193, + 203, + 204, + 238, + 139, + 135, + 71, + 234, + 234, + 217, + 66, + 14, + 129, + 19, + 97, + 165, + 112, + 244, + 35, + 73, + 192, + 12, + 149, + 71, + 33, + 192, + 32, + 195, + 208, + 173, + 233, + 202, + 25, + 210, + 145, + 89, + 87, + 245, + 246, + 38, + 3, + 25, + 57, + 167, + 245, + 86, + 227, + 208, + 81, + 64, + 90, + 199, + 140, + 48, + 212, + 180, + 70, + 120, + 9, + 192, + 245, + 177, + 65, + 157, + 9, + 193, + 100, + 181, + 125, + 145, + 62, + 8, + 108, + 36, + 140, + 237, + 242, + 248 + ], + G: [ + 182, + 46, + 78, + 199, + 79, + 89, + 41, + 208, + 135, + 20, + 154, + 35, + 21, + 43, + 230, + 113, + 191, + 147, + 208, + 37, + 28, + 27, + 86, + 203, + 25, + 209, + 148, + 187, + 139, + 111, + 48, + 102, + 76, + 110, + 106, + 130, + 37, + 160, + 34, + 222, + 90, + 165, + 245, + 222, + 102, + 47, + 19, + 130, + 4, + 232, + 137, + 211, + 240, + 11, + 140, + 21, + 42, + 22, + 241, + 61, + 99, + 115, + 199, + 166, + 114, + 190, + 204, + 60, + 233, + 66, + 115, + 230, + 125, + 103, + 203, + 125, + 13, + 176, + 94, + 131, + 39, + 198, + 167, + 124, + 67, + 175, + 254, + 1, + 15, + 198, + 62, + 237, + 159, + 31, + 218, + 58, + 244, + 47, + 61, + 67, + 94, + 46, + 78, + 199, + 79, + 89, + 41, + 208, + 135, + 20, + 154, + 35, + 21, + 43, + 230, + 113, + 191, + 147, + 208, + 37, + 28, + 27, + 86, + 203, + 25, + 209, + 148, + 187, + 139, + 111, + 48, + 102, + 76, + 110, + 106, + 130, + 37, + 160, + 34, + 222, + 90, + 165, + 245, + 222, + 102, + 47, + 19, + 130, + 4, + 232, + 137, + 211, + 240, + 11, + 140, + 21, + 42, + 22, + 241, + 61, + 99, + 115, + 199, + 166, + 114, + 190, + 204, + 60, + 233, + 66, + 115, + 230, + 125, + 103, + 203, + 125, + 13, + 176, + 94, + 131, + 39, + 198, + 167, + 124, + 67, + 175, + 254, + 1, + 15, + 198, + 62, + 237, + 159, + 31, + 218, + 58, + 244, + 47, + 61, + 67, + 94, + 46, + 78, + 199, + 79, + 89, + 41, + 208, + 135, + 20, + 154, + 35, + 21, + 43, + 230, + 113, + 191, + 147, + 208, + 37, + 28, + 27, + 86, + 203, + 25, + 209, + 148, + 187, + 139, + 111, + 48, + 102, + 76, + 110, + 106, + 130, + 37, + 160, + 34, + 222, + 90, + 165, + 245, + 222, + 102, + 47, + 19, + 130, + 4, + 232, + 137, + 211, + 240, + 11, + 140, + 21 + ], + B: [ + 191, + 154, + 43, + 10, + 207, + 204, + 164, + 231, + 136, + 58, + 239, + 30, + 147, + 230, + 101, + 111, + 150, + 35, + 128, + 57, + 252, + 79, + 173, + 120, + 25, + 126, + 81, + 85, + 8, + 7, + 122, + 237, + 190, + 152, + 246, + 182, + 130, + 219, + 67, + 76, + 167, + 178, + 235, + 250, + 28, + 61, + 186, + 250, + 199, + 67, + 58, + 50, + 86, + 182, + 108, + 77, + 89, + 112, + 59, + 125, + 226, + 50, + 205, + 227, + 125, + 128, + 104, + 27, + 59, + 66, + 53, + 133, + 159, + 203, + 97, + 125, + 139, + 159, + 158, + 7, + 215, + 47, + 140, + 226, + 223, + 231, + 44, + 110, + 184, + 61, + 233, + 47, + 67, + 148, + 22, + 120, + 173, + 156, + 117, + 181, + 94, + 154, + 43, + 10, + 207, + 204, + 164, + 231, + 136, + 58, + 239, + 30, + 147, + 230, + 101, + 111, + 150, + 35, + 128, + 57, + 252, + 79, + 173, + 120, + 25, + 126, + 81, + 85, + 8, + 7, + 122, + 237, + 190, + 152, + 246, + 182, + 130, + 219, + 67, + 76, + 167, + 178, + 235, + 250, + 28, + 61, + 186, + 250, + 199, + 67, + 58, + 50, + 86, + 182, + 108, + 77, + 89, + 112, + 59, + 125, + 226, + 50, + 205, + 227, + 125, + 128, + 104, + 27, + 59, + 66, + 53, + 133, + 159, + 203, + 97, + 125, + 139, + 159, + 158, + 7, + 215, + 47, + 140, + 226, + 223, + 231, + 44, + 110, + 184, + 61, + 233, + 47, + 67, + 148, + 22, + 120, + 173, + 156, + 117, + 181, + 94, + 154, + 43, + 10, + 207, + 204, + 164, + 231, + 136, + 58, + 239, + 30, + 147, + 230, + 101, + 111, + 150, + 35, + 128, + 57, + 252, + 79, + 173, + 120, + 25, + 126, + 81, + 85, + 8, + 7, + 122, + 237, + 190, + 152, + 246, + 182, + 130, + 219, + 67, + 76, + 167, + 178, + 235, + 250, + 28, + 61, + 186, + 250, + 199, + 67, + 58, + 50, + 86, + 182, + 108, + 77 + ], + A: [ + 0, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64 + ], + I: [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239, + 240, + 241, + 242, + 243, + 244, + 245, + 246, + 247, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + 255 + ] +}, Ec = { + R: [0, 128, 255], + G: [0, 0, 0], + B: [0, 0, 0], + A: [0, 64, 128], + I: [0, 128, 255] +}, Fc = { + R: [192, 224, 255], + G: [1, 128, 255], + B: [0, 0, 0], + A: [0, 64, 128], + I: [0, 128, 255] +}, Mc = { + R: [3, 112, 144, 188, 236, 246, 255], + G: [5, 31, 29, 22, 76, 158, 250], + B: [26, 87, 91, 86, 62, 117, 235], + A: [0, 30, 38, 49, 67, 85, 107], + I: [0, 73, 92, 118, 160, 205, 255] +}, Tc = { + R: [1, 240, 255], + G: [1, 128, 255], + B: [1, 128, 255], + A: [0, 76, 128], + I: [0, 153, 255] +}, Sc = { + R: [0, 5, 18, 34, 53, 72, 90, 107, 126, 144, 161, 178, 194, 209, 222, 233, 242, 248, 251, 251, 249, 244, 241, 252], + G: [0, 4, 10, 11, 9, 11, 17, 23, 30, 36, 43, 50, 59, 70, 83, 98, 117, 136, 157, 177, 199, 220, 240, 254], + B: [3, 24, 50, 76, 96, 106, 109, 110, 108, 104, 97, 89, 79, 67, 55, 42, 26, 12, 6, 22, 47, 79, 121, 164], + A: [0, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64], + I: [0, 11, 22, 33, 44, 55, 66, 77, 88, 99, 110, 121, 133, 144, 155, 166, 177, 188, 199, 210, 221, 232, 243, 255] +}, Ic = { + R: [48, 48, 64, 70, 65, 25, 132, 195, 244, 254, 218, 122], + G: [18, 18, 64, 107, 150, 226, 255, 241, 199, 158, 57, 4], + B: [59, 59, 162, 227, 255, 187, 81, 52, 58, 47, 7, 3], + A: [0, 22, 26, 30, 34, 43, 52, 57, 63, 67, 77, 86], + I: [0, 1, 16, 32, 49, 83, 118, 140, 164, 181, 219, 255] +}, Bc = { + R: [0, 128, 255], + G: [0, 0, 0], + B: [0, 128, 255], + A: [0, 64, 128], + I: [0, 128, 255] +}, kc = { + R: [68, 49, 53, 253], + G: [1, 104, 183, 231], + B: [84, 142, 121, 37], + A: [0, 56, 80, 88], + I: [0, 64, 192, 255] +}, Rc = { + R: [255, 255, 255], + G: [127, 196, 254], + B: [0, 0, 0], + A: [0, 64, 128], + I: [0, 128, 255] +}, Vc = { + R: [0, 0, 0], + G: [0, 128, 255], + B: [255, 196, 128], + A: [0, 64, 128], + I: [0, 128, 255] +}, Uc = { + R: [3, 64, 0, 0, 255, 255, 255], + G: [0, 0, 0, 255, 255, 192, 3], + B: [0, 32, 48, 56, 64, 96, 128], + A: [0, 8, 16, 24, 32, 52, 80], + I: [0, 32, 64, 96, 160, 192, 255] +}, Nc = { + min: 0, + max: 0, + R: [0, 37], + G: [242, 0], + B: [255, 255], + A: [0, 64], + I: [0, 255] +}, Pc = { + R: [255, 255], + G: [255, 11], + B: [0, 0], + A: [0, 64], + I: [0, 255] +}, Lc = { + min: 0, + max: 0, + R: [ + 65, + 10, + 223, + 120, + 216, + 207, + 251, + 93, + 252, + 217, + 21, + 253, + 131, + 165, + 173, + 143, + 144, + 217, + 86, + 89, + 63, + 254, + 172, + 1, + 142, + 107, + 42, + 78, + 221, + 8, + 222, + 71, + 127, + 126, + 167, + 33, + 104, + 2, + 208, + 216, + 0, + 251, + 7, + 105, + 2, + 101, + 248, + 190, + 253, + 62, + 255, + 224, + 166, + 37, + 110, + 138, + 45, + 34, + 6, + 37, + 69, + 201, + 43, + 122, + 199, + 37, + 173, + 194, + 103, + 211, + 75, + 159, + 96, + 4, + 239, + 98, + 110, + 193, + 253, + 166, + 40, + 255, + 48, + 130, + 140, + 243, + 101, + 9, + 177, + 220, + 133, + 32, + 4, + 81, + 48, + 48, + 210, + 109, + 60, + 132, + 1, + 119, + 1, + 159, + 247, + 33, + 212, + 187, + 253, + 144, + 196, + 254, + 168, + 79, + 64, + 196, + 39, + 97, + 73, + 173, + 104, + 216, + 217, + 43, + 101, + 119, + 254, + 5, + 237, + 103, + 203, + 122, + 57, + 87, + 251, + 164, + 19, + 75, + 200, + 2, + 252, + 150, + 66, + 0, + 255, + 157, + 23, + 254, + 55, + 16, + 240, + 161, + 69, + 253, + 207, + 195, + 5, + 60, + 255, + 251, + 200, + 217, + 134, + 123, + 253, + 180, + 27, + 246, + 130, + 136, + 250, + 232, + 4, + 125, + 140, + 22, + 253, + 255, + 13, + 180, + 123, + 61, + 254, + 111, + 10, + 185, + 76, + 192, + 255, + 223, + 186, + 61, + 198, + 5, + 172, + 13, + 83, + 172, + 171, + 6, + 23, + 73, + 134, + 133, + 109, + 61, + 213, + 55, + 57, + 132, + 36, + 209, + 2, + 144, + 1, + 253, + 68, + 155, + 3, + 160, + 2, + 77, + 121, + 70, + 67, + 176, + 223, + 131, + 4, + 162, + 232, + 255, + 150, + 94, + 235, + 191, + 207, + 10, + 246, + 0, + 225, + 4, + 209, + 116, + 57, + 112, + 172, + 253, + 1, + 6, + 92, + 227, + 73, + 62, + 135, + 223 + ], + G: [ + 162, + 50, + 112, + 245, + 4, + 124, + 176, + 243, + 56, + 125, + 183, + 139, + 182, + 68, + 189, + 4, + 225, + 10, + 20, + 223, + 7, + 46, + 31, + 193, + 225, + 250, + 219, + 41, + 2, + 100, + 83, + 181, + 34, + 150, + 72, + 223, + 168, + 248, + 80, + 253, + 7, + 117, + 214, + 2, + 248, + 10, + 3, + 59, + 179, + 160, + 90, + 244, + 146, + 4, + 207, + 4, + 125, + 255, + 96, + 100, + 1, + 208, + 130, + 51, + 116, + 181, + 236, + 53, + 244, + 5, + 165, + 28, + 181, + 86, + 96, + 42, + 254, + 1, + 134, + 194, + 214, + 92, + 41, + 204, + 137, + 86, + 207, + 3, + 143, + 3, + 100, + 239, + 164, + 94, + 65, + 251, + 131, + 244, + 173, + 57, + 143, + 107, + 217, + 53, + 210, + 0, + 148, + 250, + 45, + 198, + 81, + 40, + 61, + 218, + 252, + 88, + 171, + 0, + 248, + 24, + 248, + 4, + 41, + 100, + 187, + 46, + 208, + 145, + 43, + 168, + 121, + 46, + 175, + 250, + 125, + 107, + 233, + 112, + 207, + 231, + 174, + 215, + 53, + 9, + 43, + 153, + 52, + 124, + 128, + 65, + 210, + 147, + 255, + 147, + 63, + 200, + 148, + 254, + 0, + 179, + 241, + 42, + 239, + 2, + 230, + 8, + 82, + 135, + 87, + 16, + 3, + 87, + 86, + 151, + 254, + 8, + 255, + 86, + 53, + 19, + 7, + 192, + 171, + 201, + 253, + 247, + 197, + 103, + 251, + 126, + 0, + 149, + 54, + 183, + 61, + 126, + 79, + 113, + 10, + 103, + 184, + 75, + 11, + 195, + 222, + 136, + 149, + 131, + 8, + 99, + 240, + 177, + 252, + 255, + 198, + 16, + 7, + 68, + 178, + 66, + 191, + 150, + 73, + 26, + 211, + 109, + 78, + 209, + 240, + 254, + 1, + 166, + 247, + 131, + 2, + 0, + 167, + 127, + 133, + 10, + 43, + 99, + 235, + 3, + 214, + 142, + 176, + 82, + 132, + 38, + 10, + 249, + 255, + 215, + 44, + 81 + ], + B: [ + 176, + 182, + 248, + 37, + 117, + 35, + 96, + 69, + 32, + 152, + 108, + 20, + 237, + 250, + 2, + 89, + 141, + 216, + 111, + 251, + 211, + 149, + 10, + 44, + 214, + 103, + 31, + 251, + 3, + 32, + 252, + 246, + 97, + 1, + 219, + 167, + 197, + 4, + 36, + 116, + 206, + 118, + 106, + 43, + 205, + 204, + 114, + 69, + 127, + 205, + 87, + 80, + 41, + 251, + 145, + 204, + 253, + 161, + 247, + 1, + 149, + 9, + 43, + 253, + 97, + 72, + 136, + 161, + 171, + 181, + 26, + 255, + 108, + 80, + 218, + 214, + 231, + 255, + 84, + 31, + 109, + 4, + 218, + 3, + 217, + 36, + 68, + 85, + 241, + 39, + 221, + 2, + 240, + 2, + 173, + 42, + 206, + 5, + 110, + 46, + 103, + 27, + 212, + 184, + 2, + 207, + 246, + 45, + 116, + 72, + 110, + 253, + 38, + 105, + 248, + 159, + 243, + 81, + 192, + 93, + 141, + 145, + 24, + 157, + 234, + 131, + 57, + 178, + 62, + 75, + 65, + 176, + 148, + 40, + 253, + 66, + 76, + 240, + 51, + 154, + 17, + 251, + 139, + 253, + 207, + 9, + 114, + 49, + 200, + 254, + 96, + 73, + 138, + 118, + 204, + 102, + 137, + 89, + 145, + 161, + 4, + 112, + 66, + 234, + 147, + 178, + 212, + 205, + 185, + 11, + 203, + 131, + 2, + 250, + 118, + 169, + 1, + 185, + 154, + 53, + 171, + 197, + 61, + 175, + 249, + 96, + 15, + 254, + 95, + 5, + 222, + 75, + 246, + 194, + 2, + 61, + 180, + 25, + 133, + 165, + 15, + 233, + 59, + 35, + 221, + 140, + 109, + 7, + 114, + 255, + 198, + 0, + 115, + 168, + 252, + 23, + 242, + 80, + 75, + 142, + 137, + 255, + 12, + 182, + 68, + 201, + 4, + 111, + 37, + 228, + 83, + 248, + 24, + 192, + 249, + 5, + 54, + 223, + 160, + 122, + 160, + 114, + 145, + 119, + 252, + 31, + 253, + 250, + 10, + 214, + 8, + 47, + 0, + 142, + 222, + 70 + ], + A: [ + 0, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64, + 64 + ], + I: [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239, + 240, + 241, + 242, + 243, + 244, + 245, + 246, + 247, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + 255 + ] +}, Oc = class { + /** + * Sets cluts to alphabetically sorted cmaps + */ + constructor() { + I(this, "gamma", 1), I(this, "version", 0.1), I(this, "cluts", {}); + const e = Object.keys(Mi).filter((t) => !t.startsWith("$")).sort(new Intl.Collator("en").compare); + for (const t of e) + this.cluts[t] = Mi[t]; + } + addColormap(i, e) { + this.cluts[i] = e; + } + colormaps() { + return Object.keys(this.cluts); + } + // for backward compatibility: prior to v0.34 "colormaps" used to be "colorMaps" + colorMaps() { + return this.colormaps(); + } + // returns key name if it exists, otherwise returns default "gray" + colormapFromKey(i) { + let e = this.cluts[i]; + return e !== void 0 || (e = this.cluts[i.toLowerCase()], e !== void 0) ? e : (i.length > 0 && R.warn("No color map named " + i), { + min: 0, + max: 0, + R: [0, 255], + G: [0, 255], + B: [0, 255], + A: [0, 255], + I: [0, 255] + }); + } + // not included in public docs + colormap(i = "", e = !1) { + const t = this.colormapFromKey(i); + return this.makeLut(t.R, t.G, t.B, t.A, t.I, e); + } + makeLabelLut(i, e = 255, t = 1 / 0) { + if (i.R === void 0 || i.G === void 0 || i.B === void 0) + throw new Error(`Invalid colormap table: ${i}`); + const s = i.R.length, r = i.I ?? [...Array(s).keys()]; + let a = !1; + for (let d = 0; d < r.length; d++) + r[d] > t && (a = !0, r[d] = t); + if (a && R.warn("Some colormap indices clamped to match label range."), s !== i.G.length || s !== i.B.length || s !== r.length) + throw new Error( + `colormap does not make sense: ${i} Rs ${i.R.length} Gs ${i.G.length} Bs ${i.B.length} Is ${r.length}` + ); + let n = new Uint8ClampedArray(s).fill(e); + n[0] = 0, i.A !== void 0 && (n = Uint8ClampedArray.from(i.A)); + const o = Math.min(...r), l = Math.max(...r), c = l - o + 1, h = new Uint8ClampedArray(c * 4).fill(0); + for (let d = 0; d < s; d++) { + let f = (r[d] - o) * 4; + h[f++] = i.R[d], h[f++] = i.G[d], h[f++] = i.B[d], h[f++] = n[d]; + } + const u = { + lut: h, + min: o, + max: l + }; + if (i.labels) { + const d = i.labels.length; + if (d === c) + u.labels = i.labels; + else if (d === s) { + u.labels = Array(c).fill("?"); + for (let f = 0; f < s; f++) { + const g = r[f]; + u.labels[g] = i.labels[f]; + } + } + } + return u; + } + async makeLabelLutFromUrl(i, e = 255, t = 1 / 0) { + const r = await (await fetch(i)).json(); + return this.makeLabelLut(r, e, t); + } + // not included in public docs + // The drawing colormap is a variant of the label colormap with precisely 256 colors + makeDrawLut(i) { + let e = typeof i == "object" ? i : Mi[i]; + e === void 0 && (R.warn("colormap undefined ", i), e = this.colormapFromKey("")); + const t = this.makeLabelLut(e, 255); + if (t.labels === void 0 && (t.labels = []), t.labels.length < 256) { + const n = t.labels.length; + for (let o = n; o < 256; o++) + t.labels.push(o.toString()); + } + const s = new Uint8ClampedArray(256 * 4); + let r = 0; + for (let n = 0; n < 256; n++) + s[r++] = 255, s[r++] = 0, s[r++] = 0, s[r++] = 255; + s[3] = 0; + const a = Math.min(t.lut.length, 256 * 4); + if (a > 0) + for (let n = 0; n < a; n++) + s[n] = t.lut[n]; + return { + lut: s, + labels: t.labels + }; + } + // not included in public docs + makeLut(i, e, t, s, r, a) { + const n = i.length, o = [...i], l = [...e], c = [...t]; + if (!r) { + r = new Array(n); + for (let f = 0; f < n; f++) + r[f] = f / (n - 1) * 255; + } + s || (s = new Array(n).fill(64), s[0] = 0); + let h = Uint8ClampedArray.from(s), u = Uint8ClampedArray.from(r); + if (a) + for (let f = 0; f < n; f++) + o[f] = i[n - 1 - f], l[f] = e[n - 1 - f], c[f] = t[n - 1 - f], h[f] = 255 - s[n - 1 - f], u[f] = 255 - r[n - 1 - f]; + const d = new Uint8ClampedArray(256 * 4); + if (typeof u > "u") { + u = new Uint8ClampedArray(n).fill(0); + for (let f = 0; f < n; f++) + u[f] = Math.round(f * 255 / (n - 1)); + } + typeof h > "u" && (h = new Uint8ClampedArray(n).fill(64), h[0] = 0); + for (let f = 0; f < n - 1; f++) { + const g = u[f]; + let m = u[f + 1]; + f === 0 && g !== 0 && R.warn("colormap issue: indices expected to start with 0 not ", g), f === u.length - 2 && m !== 255 && (R.warn("padding colormap: indices expected end with 255 not ", m), m = 255); + const p = m - g; + let v = g * 4; + for (let A = g; A <= m; A++) { + const x = (A - g) / p; + d[v++] = o[f] + x * (o[f + 1] - o[f]), d[v++] = l[f] + x * (l[f + 1] - l[f]), d[v++] = c[f] + x * (c[f + 1] - c[f]), d[v++] = h[f] + x * (h[f + 1] - h[f]); + } + } + if (this.gamma === 1) + return d; + for (let f = 0; f < 1020; f++) + f % 4 !== 3 && (d[f] = Math.pow(d[f] / 255, 1 / this.gamma) * 255); + return d; + } +}, oe = new Oc(), gt = class { + static getClusterBoundaryU8(i, e) { + const t = new Array(i.length).fill(!1), s = new Array(i.length).fill(!1); + for (let n = 0; n < i.length; n++) + i[n] > 0 && (s[n] = !0); + const r = e.length / 3; + let a = 0; + for (let n = 0; n < r; n++) { + const o = e[a], l = e[a + 1], c = e[a + 2]; + a += 3, !(s[o] === s[l] && s[o] === s[c] && s[l] === s[c]) && (t[o] = !0, t[l] = !0, t[c] = !0); + } + return t; + } + static async gzip(i) { + const e = new CompressionStream("gzip"), t = e.writable.getWriter(); + t.write(i).catch(console.error); + const s = t.close().catch(console.error), r = new Response(e.readable), a = new Uint8Array(await r.arrayBuffer()); + return await s, a; + } + static createMZ3(i, e, t = !1, s = null) { + const a = s instanceof Uint8Array && s.length === i.length / 3 * 4, n = a ? 7 : 3, o = e.length / 3, l = i.length / 3, c = 0, h = 16, u = o * 3 * 4, d = l * 3 * 4, f = a ? s.length : 0, g = h + u + d + f, m = new ArrayBuffer(g), p = new DataView(m); + p.setUint16(0, 23117, !0), p.setUint16(2, n, !0), p.setUint32(4, o, !0), p.setUint32(8, l, !0), p.setUint32(12, c, !0); + let v = h; + if (new Uint32Array(m, v, e.length).set(e), v += u, new Float32Array(m, v, i.length).set(i), a && (v += d, new Uint8Array(m, v, s.length).set(s)), t) + throw new Error("Call async createMZ3Async() for compression"); + return m; + } + static async createMZ3Async(i, e, t = !1, s = null) { + const r = this.createMZ3(i, e, t, s); + return t ? await this.gzip(new Uint8Array(r)) : r; + } + static createOBJ(i, e) { + let t = ""; + for (let a = 0; a < i.length; a += 3) + t += `v ${i[a]} ${i[a + 1]} ${i[a + 2]} +`; + for (let a = 0; a < e.length; a += 3) + t += `f ${e[a] + 1} ${e[a + 1] + 1} ${e[a + 2] + 1} +`; + return new TextEncoder().encode(t).buffer; + } + static createSTL(i, e) { + const t = e.length / 3, s = 84 + t * 50, r = new ArrayBuffer(s), a = new DataView(r); + for (let o = 0; o < 80; o++) + a.setUint8(o, 0); + a.setUint32(80, t, !0); + let n = 84; + for (let o = 0; o < e.length; o += 3) { + const l = e[o] * 3, c = e[o + 1] * 3, h = e[o + 2] * 3; + a.setFloat32(n, 0, !0), a.setFloat32(n + 4, 0, !0), a.setFloat32(n + 8, 0, !0), n += 12, a.setFloat32(n, i[l], !0), a.setFloat32(n + 4, i[l + 1], !0), a.setFloat32(n + 8, i[l + 2], !0), n += 12, a.setFloat32(n, i[c], !0), a.setFloat32(n + 4, i[c + 1], !0), a.setFloat32(n + 8, i[c + 2], !0), n += 12, a.setFloat32(n, i[h], !0), a.setFloat32(n + 4, i[h + 1], !0), a.setFloat32(n + 8, i[h + 2], !0), n += 12, a.setUint16(n, 0, !0), n += 2; + } + return r; + } + static downloadArrayBuffer(i, e) { + const t = new Blob([i], { type: "application/octet-stream" }), s = URL.createObjectURL(t), r = document.createElement("a"); + r.href = s, r.download = e, document.body.appendChild(r), r.style.display = "none", r.click(), setTimeout(() => { + document.body.removeChild(r), URL.revokeObjectURL(s); + }, 0); + } + static async saveMesh(i, e, t = ".mz3", s = !1) { + let r = new ArrayBuffer(0); + return /\.obj$/i.test(t) ? r = this.createOBJ(i, e) : /\.stl$/i.test(t) ? r = this.createSTL(i, e) : (/\.mz3$/i.test(t) || (t += ".mz3"), r = await this.createMZ3Async(i, e, s)), t.length > 4 && this.downloadArrayBuffer(r, t), r; + } + static getClusterBoundary(i, e) { + const t = new Uint32Array(i.buffer), s = new Array(t.length).fill(!1), r = e.length / 3; + let a = 0; + for (let n = 0; n < r; n++) { + const o = e[a], l = e[a + 1], c = e[a + 2]; + a += 3, !(t[o] === t[l] && t[o] === t[c] && t[l] === t[c]) && (s[o] = !0, s[l] = !0, s[c] = !0); + } + return s; + } + // return spatial extremes for vertices + static getExtents(i) { + if (!ArrayBuffer.isView(i) && !Array.isArray(i) || i.length < 3) + return { mxDx: 0, extentsMin: 0, extentsMax: 0 }; + let e = 0; + const t = G(i[0], i[1], i[2]), s = G(i[0], i[1], i[2]); + for (let n = 0; n < i.length; n += 3) { + const o = G(i[n], i[n + 1], i[n + 2]); + e = Math.max(e, ei(o)), Cs(t, t, o), Ds(s, s, o); + } + const r = [t[0], t[1], t[2]], a = [s[0], s[1], s[2]]; + return { mxDx: e, extentsMin: r, extentsMax: a }; + } + // determine vector orthogonal to plane defined by triangle + // triangle winding determines front/back face + static generateNormals(i, e) { + const t = []; + let s; + const r = i.length; + let a, n, o, l, c, h, u, d, f; + const g = new Float32Array(r), m = e.length; + for (s = 0; s < m; s += 3) { + u = e[s] * 3, d = e[s + 1] * 3, f = e[s + 2] * 3; + const p = [i[u], i[u + 1], i[u + 2]], v = [i[d], i[d + 1], i[d + 2]], A = [i[f], i[f + 1], i[f + 2]]; + a = v[0] - p[0], n = v[1] - p[1], o = v[2] - p[2], l = A[0] - p[0], c = A[1] - p[1], h = A[2] - p[2], t[0] = c * o - h * n, t[1] = h * a - l * o, t[2] = l * n - c * a, g[u] += t[0], g[u + 1] += t[1], g[u + 2] += t[2], g[d] += t[0], g[d + 1] += t[1], g[d + 2] += t[2], g[f] += t[0], g[f + 1] += t[1], g[f + 2] += t[2]; + } + for (s = 0; s < r; s += 3) { + t[0] = -1 * g[s], t[1] = -1 * g[s + 1], t[2] = -1 * g[s + 2]; + let p = t[0] * t[0] + t[1] * t[1] + t[2] * t[2]; + p > 0 && (p = 1 / Math.sqrt(p), t[0] *= p, t[1] *= p, t[2] *= p), g[s] = t[0], g[s + 1] = t[1], g[s + 2] = t[2]; + } + return g; + } +}, Z, ke, Ti, xs, Kt, Gn = class { + constructor(i) { + It(this, Z), It(this, ke, 0), It(this, Ti, []), It(this, xs, []), It(this, Kt), st(this, Z, new DataView(i)), this.read(); + } + async extract(i) { + const e = new Uint8Array(K(this, Z).buffer.slice(i.startsAt, i.startsAt + i.compressedSize)); + if (i.compressionMethod === 0) + return e; + if (i.compressionMethod === 8) { + const t = new DecompressionStream("deflate-raw"), s = t.writable.getWriter(); + s.write(e).catch(console.error); + const r = s.close().catch(console.error), a = new Response(t.readable), n = new Uint8Array(await a.arrayBuffer()); + return await r, n; + } + throw new Error(`Unsupported compression method: ${i.compressionMethod}`); + } + read() { + for (; !K(this, Kt) && K(this, ke) < K(this, Z).byteLength; ) { + const i = K(this, Z).getUint32(K(this, ke), !0); + if (i === 67324752) { + const e = this.readLocalFile(K(this, ke)); + e.extract = this.extract.bind(this, e), K(this, Ti).push(e); + const t = (e.generalPurpose & 8) !== 0; + if (e.startsAt = K(this, ke) + 30 + e.fileNameLength + e.extraLength, e.compressedSize === 0 && t) { + let s = e.startsAt; + for (; s + 20 <= K(this, Z).byteLength; ) { + if (K(this, Z).getUint32(s, !0) === 134695760 && K(this, Z).getUint16(s + 16, !0) === 19280) { + s += 4; + break; + } + s++; + } + e.crc = K(this, Z).getUint32(s, !0), e.compressedSize = K(this, Z).getUint32(s + 4, !0), e.uncompressedSize = K(this, Z).getUint32(s + 8, !0), st(this, ke, s + 12); + } else + st(this, ke, e.startsAt + e.compressedSize); + } else if (i === 33639248) { + const e = this.readCentralDirectory(K(this, ke)); + K(this, xs).push(e), st(this, ke, K(this, ke) + (46 + e.fileNameLength + e.extraLength + e.fileCommentLength)); + } else if (i === 101010256) { + st(this, Kt, this.readEndCentralDirectory(K(this, ke))); + break; + } else if (i === 101075792) { + st(this, Kt, this.readEndCentralDirectory64(K(this, ke))); + break; + } else { + console.error(`Unexpected ZIP signature 0x${i.toString(16).padStart(8, "0")} at index ${K(this, ke)}`); + break; + } + } + } + readLocalFile(i) { + let e = K(this, Z).getUint32(i + 18, !0), t = K(this, Z).getUint32(i + 22, !0); + const s = K(this, Z).getUint16(i + 26, !0), r = K(this, Z).getUint16(i + 28, !0), a = i + 30 + s; + if (this.readString(a, r), e === 4294967295 && t === 4294967295) { + let n = a, o = !1; + for (; n < a + r - 4; ) { + const l = K(this, Z).getUint16(n, !0), c = K(this, Z).getUint16(n + 2, !0); + if (n += 4, l === 1) + if (c >= 16) { + t = Number(K(this, Z).getBigUint64(n, !0)), n += 8, e = Number(K(this, Z).getBigUint64(n, !0)), o = !0; + break; + } else + throw new Error( + `ZIP64 extra field found but is too small (expected at least 16 bytes, got ${c}).` + ); + n += c; + } + if (!o) + throw new Error("ZIP64 format missing extra field with signature 0x0001."); + } + return { + signature: this.readString(i, 4), + version: K(this, Z).getUint16(i + 4, !0), + generalPurpose: K(this, Z).getUint16(i + 6, !0), + compressionMethod: K(this, Z).getUint16(i + 8, !0), + lastModifiedTime: K(this, Z).getUint16(i + 10, !0), + lastModifiedDate: K(this, Z).getUint16(i + 12, !0), + crc: K(this, Z).getUint32(i + 14, !0), + compressedSize: e, + uncompressedSize: t, + fileNameLength: s, + extraLength: r, + fileName: this.readString(i + 30, s), + extra: this.readString(i + 30 + s, r) + }; + } + readCentralDirectory(i) { + return { + versionCreated: K(this, Z).getUint16(i + 4, !0), + versionNeeded: K(this, Z).getUint16(i + 6, !0), + fileNameLength: K(this, Z).getUint16(i + 28, !0), + extraLength: K(this, Z).getUint16(i + 30, !0), + fileCommentLength: K(this, Z).getUint16(i + 32, !0), + diskNumber: K(this, Z).getUint16(i + 34, !0), + internalAttributes: K(this, Z).getUint16(i + 36, !0), + externalAttributes: K(this, Z).getUint32(i + 38, !0), + offset: K(this, Z).getUint32(i + 42, !0), + comments: this.readString(i + 46, K(this, Z).getUint16(i + 32, !0)) + }; + } + readEndCentralDirectory(i) { + const e = K(this, Z).getUint16(i + 20, !0); + return { + numberOfDisks: K(this, Z).getUint16(i + 4, !0), + centralDirectoryStartDisk: K(this, Z).getUint16(i + 6, !0), + numberCentralDirectoryRecordsOnThisDisk: K(this, Z).getUint16(i + 8, !0), + numberCentralDirectoryRecords: K(this, Z).getUint16(i + 10, !0), + centralDirectorySize: K(this, Z).getUint32(i + 12, !0), + centralDirectoryOffset: K(this, Z).getUint32(i + 16, !0), + commentLength: e, + comment: this.readString(i + 22, e) + }; + } + readEndCentralDirectory64(i) { + const e = Number(K(this, Z).getBigUint64(i + 0, !0)); + return { + numberOfDisks: K(this, Z).getUint32(i + 16, !0), + centralDirectoryStartDisk: K(this, Z).getUint32(i + 20, !0), + numberCentralDirectoryRecordsOnThisDisk: Number(K(this, Z).getBigUint64(i + 24, !0)), + numberCentralDirectoryRecords: Number(K(this, Z).getBigUint64(i + 32, !0)), + centralDirectorySize: Number(K(this, Z).getBigUint64(i + 40, !0)), + centralDirectoryOffset: Number(K(this, Z).getBigUint64(i + 48, !0)), + commentLength: e, + comment: "" + }; + } + readString(i, e) { + return Array.from({ length: e }, (t, s) => String.fromCharCode(K(this, Z).getUint8(i + s))).join(""); + } + get entries() { + return K(this, Ti); + } +}; +Z = /* @__PURE__ */ new WeakMap(); +ke = /* @__PURE__ */ new WeakMap(); +Ti = /* @__PURE__ */ new WeakMap(); +xs = /* @__PURE__ */ new WeakMap(); +Kt = /* @__PURE__ */ new WeakMap(); +var j = class Bt { + static arrayBufferToBase64(e) { + const t = new Uint8Array(e); + return Bt.uint8tob64(t); + } + static async decompress(e) { + const t = e[0] === 31 && e[1] === 139 && e[2] === 8 ? "gzip" : e[0] === 120 && (e[1] === 1 || e[1] === 94 || e[1] === 156 || e[1] === 218) ? "deflate" : "deflate-raw", s = new DecompressionStream(t), r = s.writable.getWriter(); + r.write(e).catch(console.error); + const a = r.close().catch(console.error), n = new Response(s.readable), o = new Uint8Array(await n.arrayBuffer()); + return await a, o; + } + static async decompressToBuffer(e) { + const t = await Bt.decompress(e); + return t.buffer.slice(t.byteOffset, t.byteOffset + t.byteLength); + } + static async readMatV4(e, t = !1) { + let s = e.byteLength; + if (s < 40) + throw new Error("File too small to be MAT v4: bytes = " + e.byteLength); + let r = new DataView(e), a = r.getUint16(0, !0), n = e; + if (a === 35615 || a === 8075) { + const g = await this.decompress(new Uint8Array(e)); + r = new DataView(g.buffer), a = r.getUint16(0, !0), n = g.buffer, s = n.byteLength; + } + const o = new TextDecoder("utf-8"), l = new Uint8Array(n); + let c = 0; + const h = {}; + function u(g) { + return Math.floor(g / 10) % 10; + } + function d(g, m, p) { + const v = new Uint8Array(l.subarray(m, p)); + return g === 1 ? new Float32Array(v.buffer) : g === 2 ? new Int32Array(v.buffer) : g === 3 ? new Int16Array(v.buffer) : g === 4 ? new Uint16Array(v.buffer) : g === 5 ? new Uint8Array(v.buffer) : new Float64Array(v.buffer); + } + function f() { + const g = r.getUint32(c, !0), m = r.getUint32(c + 4, !0), p = r.getUint32(c + 8, !0), v = r.getUint32(c + 12, !0), A = r.getUint32(c + 16, !0); + if (c += 20, v !== 0) + throw new Error("Matlab V4 reader does not support imaginary numbers"); + const x = m * p; + if (x < 1) + throw new Error("mrows * ncols must be greater than one"); + const w = new Uint8Array(l.subarray(c, c + A)); + let D = o.decode(w).trim().replaceAll("\0", ""); + t && (D = D.replaceAll(".", "_")); + const b = u(g); + let C = 8; + if (b >= 1 && b <= 2) + C = 4; + else if (b >= 3 && b <= 4) + C = 2; + else if (b === 5) + C = 1; + else if (b !== 0) + throw new Error("impossible Matlab v4 datatype"); + if (c += A, g > 50) + throw new Error("Does not appear to be little-endian V4 Matlab file"); + const E = c + x * C; + h[D] = d(b, c, E), c = E; + } + for (; c + 20 < s; ) + f(); + return h; + } + // readMatV4() + static b64toUint8(e) { + const t = atob(e), s = t.length, r = new Uint8Array(s); + for (let a = 0; a < s; a++) + r[a] = t.charCodeAt(a); + return r; + } + /* + https://gist.github.com/jonleighton/958841 + MIT LICENSE + Copyright 2011 Jon Leighton + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + static uint8tob64(e) { + let t = ""; + const s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", r = e.byteLength, a = r % 3, n = r - a; + let o, l, c, h, u; + for (let d = 0; d < n; d = d + 3) + u = e[d] << 16 | e[d + 1] << 8 | e[d + 2], o = (u & 16515072) >> 18, l = (u & 258048) >> 12, c = (u & 4032) >> 6, h = u & 63, t += s[o] + s[l] + s[c] + s[h]; + return a === 1 ? (u = e[n], o = (u & 252) >> 2, l = (u & 3) << 4, t += s[o] + s[l] + "==") : a === 2 && (u = e[n] << 8 | e[n + 1], o = (u & 64512) >> 10, l = (u & 1008) >> 4, c = (u & 15) << 2, t += s[o] + s[l] + s[c] + "="), t; + } + // https://stackoverflow.com/questions/34156282/how-do-i-save-json-to-local-text-file + static download(e, t, s) { + const r = document.createElement("a"), a = Array.isArray(e) ? e : [e], n = new Blob(a, { type: s }); + r.href = URL.createObjectURL(n), r.download = t, r.click(); + } + static readFileAsync(e) { + return new Promise((t, s) => { + const r = new FileReader(); + r.onload = () => { + t(r.result); + }, r.onerror = s, r.readAsArrayBuffer(e); + }); + } + static blobToBase64(e) { + return new Promise((t) => { + const s = new FileReader(); + s.onloadend = () => t(s.result), s.readAsDataURL(e); + }); + } + static async decompressBase64String(e) { + const t = atob(e), s = new ArrayBuffer(t.length), r = new Uint8Array(s); + for (let a = 0; a < t.length; a++) + r[a] = t.charCodeAt(a); + return Bt.decompressArrayBuffer(r); + } + static async compressToBase64String(e) { + const t = await Bt.compressStringToArrayBuffer(e); + return Bt.uint8tob64(new Uint8Array(t)); + } + /** + * Converts a string into a Uint8Array for use with compression/decompression methods (101arrowz/fflate: MIT License) + * @param str The string to encode + * @param latin1 Whether or not to interpret the data as Latin-1. This should + * not need to be true unless decoding a binary string. + * @returns The string encoded in UTF-8/Latin-1 binary + */ + static strToU8(e, t) { + if (t) { + const l = new Uint8Array(e.length); + for (let c = 0; c < e.length; ++c) + l[c] = e.charCodeAt(c); + return l; + } + const s = e.length, r = (l, c, h) => ((h == null || h > l.length) && (h = l.length), new Uint8Array(l.subarray(c, h))); + let a = new Uint8Array(e.length + (e.length >> 1)), n = 0; + const o = (l) => { + a[n++] = l; + }; + for (let l = 0; l < s; ++l) { + if (n + 5 > a.length) { + const h = new Uint8Array(n + 8 + (s - l << 1)); + h.set(a), a = h; + } + let c = e.charCodeAt(l); + c < 128 || t ? o(c) : c < 2048 ? (o(192 | c >> 6), o(128 | c & 63)) : (c > 55295 && c < 57344, c = 65536 + (c & 1047552) | e.charCodeAt(++l) & 1023, o(240 | c >> 18), o(128 | c >> 12 & 63), o(128 | c >> 6 & 63), o(128 | c & 63)); + } + return r(a, 0, n); + } + static async compress(e, t = "gzip") { + const s = new CompressionStream(t), r = s.writable.getWriter(); + r.write(e).catch(console.error); + const a = r.close().catch(console.error), o = await new Response(s.readable).arrayBuffer(); + return await a, o; + } + static async compressStringToArrayBuffer(e) { + const t = this.strToU8(e); + return await this.compress(t); + } + static isArrayBufferCompressed(e) { + if (e && e.byteLength) { + const t = new Uint8Array(e); + return (t[0] << 8 | t[1]) === 8075; + } else + return !1; + } + /** + * Converts a Uint8Array to a string (101arrowz/fflate: MIT License) + * @param dat The data to decode to string + * @param latin1 Whether or not to interpret the data as Latin-1. This should + * not need to be true unless encoding to binary string. + * @returns The original UTF-8/Latin-1 string + */ + static strFromU8(e, t) { + if (t) { + let s = ""; + for (let r = 0; r < e.length; r += 16384) + s += String.fromCharCode.apply(null, e.subarray(r, r + 16384)); + return s; + } else { + const s = (o, l, c) => ((l == null || l < 0) && (l = 0), (c == null || c > o.length) && (c = o.length), new Uint8Array(o.subarray(l, c))), r = (o) => { + for (let l = "", c = 0; ; ) { + let h = o[c++]; + const u = (h > 127) + (h > 223) + (h > 239); + if (c + u > o.length) + return { s: l, r: s(o, c - 1) }; + u ? u === 3 ? (h = ((h & 15) << 18 | (o[c++] & 63) << 12 | (o[c++] & 63) << 6 | o[c++] & 63) - 65536, l += String.fromCharCode(55296 | h >> 10, 56320 | h & 1023)) : u & 1 ? l += String.fromCharCode((h & 31) << 6 | o[c++] & 63) : l += String.fromCharCode((h & 15) << 12 | (o[c++] & 63) << 6 | o[c++] & 63) : l += String.fromCharCode(h); + } + }, { s: a, r: n } = r(e); + if (n.length) + throw new Error("Unexpected trailing bytes in UTF-8 decoding"); + return a; + } + } + static async decompressArrayBuffer(e) { + const t = await this.decompress(new Uint8Array(e)); + return this.strFromU8(t); + } + static arraysAreEqual(e, t) { + return Xa(e, t); + } + /** + * Generate a pre-filled number array. + * + * @param start - start value + * @param stop - stop value + * @param step - step value + * @returns filled number array + */ + static range(e, t, s) { + return Array.from({ length: (t - e) / s + 1 }, (r, a) => e + a * s); + } + /** + * convert spherical AZIMUTH, ELEVATION to Cartesian + * @param azimuth - azimuth number + * @param elevation - elevation number + * @returns the converted [x, y, z] coordinates + * @example + * xyz = NVUtilities.sph2cartDeg(42, 42) + */ + static sph2cartDeg(e, t) { + const s = -t * (Math.PI / 180), r = (e - 90) % 360 * (Math.PI / 180), a = [Math.cos(s) * Math.cos(r), Math.cos(s) * Math.sin(r), Math.sin(s)], n = Math.sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]); + return n <= 0 || (a[0] /= n, a[1] /= n, a[2] /= n), a; + } + static vox2mm(e, t) { + const s = xe(t); + Ne(s, s); + const r = pe(e[0], e[1], e[2], 1); + return Me(r, r, s), G(r[0], r[1], r[2]); + } +}, Ot = R, ge = class we { + // read undocumented AFNI tract.niml format streamlines + static readTRACT(e) { + const t = e.byteLength; + if (t < 20) + throw new Error("File too small to be niml.tract: bytes = " + t); + const s = new DataView(e), r = new Uint8Array(e); + let a = 0; + function n() { + for (; a < t && r[a] !== 60; ) + a++; + const m = a; + for (; a < t && r[a] !== 62; ) + a++; + return a++, a - m < 1 ? "" : new TextDecoder().decode(e.slice(m, a - 1)).trim(); + } + let o = n(); + function l(m) { + const p = o.indexOf(m); + if (p < 0) + return 0; + const v = o.indexOf('"', p) + 1, A = o.indexOf('"', v), x = o.slice(v, A); + return parseInt(x); + } + const c = l("N_tracts="); + (!o.startsWith(" ValuesArray (entries for that group) + * @param groups - ValuesArray describing groups; groups[i].id defines the ordering + * @returns ValuesArray - one entry per tag where vals is the concatenation of each group's vals in groups[] order + * + * @throws Error when: + * - groups is empty or missing + * - any group in groups is missing from dpgMap + * - any group contains duplicate entries for a tag + * - tag coverage differs between groups (missing tag in any group) + * - any entry has invalid/unconvertible vals + */ + static assembleDpgFromMap(e, t) { + if (!Array.isArray(t) || t.length === 0) + throw new Error('assembleDpgFromMap: "groups" is empty or missing; cannot assemble dpg.'); + for (let l = 0; l < t.length; l++) { + const c = String(t[l].id); + if (!e[c]) + throw new Error(`assembleDpgFromMap: missing dpgMap entry for group "${c}".`); + if (!Array.isArray(e[c])) + throw new Error(`assembleDpgFromMap: dpgMap["${c}"] is not an array.`); + } + const s = String(t[0].id), r = e[s], a = [], n = /* @__PURE__ */ new Set(); + for (const l of r) { + if (!l || typeof l.id != "string") + throw new Error(`assembleDpgFromMap: invalid entry in group "${s}".`); + if (n.has(l.id)) + throw new Error(`assembleDpgFromMap: duplicate tag "${l.id}" in group "${s}".`); + n.add(l.id), a.push(l.id); + } + if (n.size === 0) + throw new Error(`assembleDpgFromMap: no tags found in group "${s}".`); + for (let l = 1; l < t.length; l++) { + const c = String(t[l].id), h = e[c], u = /* @__PURE__ */ new Map(); + for (const d of h) { + if (!d || typeof d.id != "string") + throw new Error(`assembleDpgFromMap: invalid entry in group "${c}".`); + u.set(d.id, (u.get(d.id) || 0) + 1); + } + for (const [d, f] of u.entries()) + if (f > 1) + throw new Error(`assembleDpgFromMap: multiple entries for tag "${d}" in group "${c}".`); + if (u.size !== n.size) + throw new Error( + `assembleDpgFromMap: tag coverage mismatch for group "${c}". Expected ${n.size} tags but found ${u.size}.` + ); + for (const d of n) + if (!u.has(d)) + throw new Error(`assembleDpgFromMap: group "${c}" missing tag "${d}".`); + } + const o = []; + for (const l of a) { + const c = []; + let h = 0; + for (let f = 0; f < t.length; f++) { + const g = String(t[f].id), p = e[g].filter((A) => A.id === l); + if (p.length === 0) + throw new Error(`assembleDpgFromMap: missing tag "${l}" for group "${g}".`); + if (p.length > 1) + throw new Error(`assembleDpgFromMap: multiple entries for tag "${l}" in group "${g}".`); + const v = p[0]; + if (v.vals instanceof Float32Array) + c.push(v.vals), h += v.vals.length; + else + try { + const A = Float32Array.from(v.vals); + c.push(A), h += A.length; + } catch { + throw new Error(`assembleDpgFromMap: invalid vals for tag "${l}" in group "${g}".`); + } + } + const u = new Float32Array(h); + let d = 0; + for (const f of c) + u.set(f, d), d += f.length; + o.push({ + id: l, + vals: u + // Note: global_min/global_max/cal_min/cal_max are not computed here. + // If you want to propagate or compute them, add logic to compute per-tag aggregated values. + }); + } + return o; + } + // read TRX format tractogram + // https://github.com/tee-ar-ex/trx-spec/blob/master/specifications.md + static async readTRX(e) { + function t(m) { + const p = (m & 31744) >> 10, v = m & 1023; + return (m >> 15 ? -1 : 1) * (p ? p === 31 ? v ? NaN : 1 / 0 : Math.pow(2, p - 15) * (1 + v / 1024) : 6103515625e-14 * (v / 1024)); + } + let s = 0, r = 0, a = new Float32Array([]); + const n = [], o = {}, l = [], c = [], h = []; + let u = [], d = !1; + const f = new Gn(e); + for (let m = 0; m < f.entries.length; m++) { + const p = f.entries[m]; + if (p.uncompressedSize === 0) + continue; + const v = p.fileName.split("/"), A = v.slice(-1)[0]; + if (A.startsWith(".")) + continue; + const x = v.slice(-3)[0], w = v.slice(-2)[0], D = A.split(".")[0], b = await p.extract(); + if (A.includes("header.json")) { + const F = new TextDecoder().decode(b); + u = JSON.parse(F); + continue; + } + let C = 0, E = []; + if (A.endsWith(".uint64") || A.endsWith(".int64")) { + C = b.length / 8, E = new Uint32Array(C); + const F = new Uint32Array(b.buffer); + let M = 0; + for (let S = 0; S < C; S++) + E[S] = F[M], F[M + 1] !== 0 && (d = !0), M += 2; + } else if (A.endsWith(".uint32")) + E = new Uint32Array(b.buffer); + else if (A.endsWith(".uint16")) + E = new Uint16Array(b.buffer); + else if (A.endsWith(".uint8")) + E = new Uint8Array(b.buffer); + else if (A.endsWith(".int32")) + E = new Int32Array(b.buffer); + else if (A.endsWith(".int16")) + E = new Int16Array(b.buffer); + else if (A.endsWith(".int8")) + E = new Int8Array(b.buffer); + else if (A.endsWith(".float64")) + E = new Float64Array(b.buffer); + else if (A.endsWith(".float32")) + E = new Float32Array(b.buffer); + else if (A.endsWith(".float16")) { + C = b.length / 2, E = new Float32Array(C); + const F = new Uint16Array(b.buffer), M = new Float32Array(65536); + for (let S = 0; S < 65536; S++) + M[S] = t(S); + for (let S = 0; S < C; S++) + E[S] = M[F[S]]; + } else + continue; + if (C = E.length, w.includes("groups")) { + l.push({ + id: D, + vals: Float32Array.from(E.slice()) + }); + continue; + } + if (x.includes("dpg")) { + const F = String(w); + o[F] || (o[F] = []), o[F].push({ + id: D, + vals: Float32Array.from(E.slice()) + }); + continue; + } + if (w.includes("dpv")) { + h.push({ + id: D, + vals: Float32Array.from(E.slice()) + }); + continue; + } + if (w.includes("dps")) { + c.push({ + id: D, + vals: Float32Array.from(E.slice()) + }); + continue; + } + if (A.startsWith("offsets.")) { + s = C; + for (let F = 0; F < C; F++) + n[F] = E[F]; + } + A.startsWith("positions.3.") && (r = C, a = new Float32Array(E)); + } + if (s === 0 || r === 0) + throw new Error("Failure reading TRX format (no offsets or points)."); + if (d) + throw new Error("Too many vertices: JavaScript does not support 64 bit integers"); + let g = []; + return l.length > 0 && o && Object.keys(o).length > 0 && (g = this.assembleDpgFromMap(o, l)), n[s] = r / 3, { + pts: a, + offsetPt0: new Uint32Array(n), + dpg: g, + dps: c, + dpv: h, + groups: l, + header: u + }; + } + // readTRX() + // issue1426 MRtrix data per streamline as ASCII text + static readTXT(e, t = 0) { + const r = new TextDecoder("utf-8").decode(e).split(/\r?\n|\r/).filter((n) => n.trim().length > 0); + t <= 0 && (t = r.length); + const a = new Float32Array(t); + for (let n = 0; n < t && n < r.length; n++) { + const o = parseFloat(r[n].trim()); + a[n] = Number.isFinite(o) ? o : 0; + } + return a; + } + // read mrtrix tsf format Track Scalar Files - these are are DPV + // https://mrtrix.readthedocs.io/en/dev/getting_started/image_data.html#track-scalar-file-format-tsf + static readTSF(e, t = 0) { + const s = new Float32Array(t), r = e.byteLength; + if (r < 20) + throw new Error("File too small to be TSF: bytes = " + r); + const a = new Uint8Array(e); + let n = 0; + function o() { + for (; n < r && a[n] === 10; ) + n++; + const d = n; + for (; n < r && a[n] !== 10; ) + n++; + return n++, n - d < 1 ? "" : new TextDecoder().decode(e.slice(d, n - 1)); + } + let l = o(); + if (!l.includes("mrtrix track scalars")) + throw new Error("Not a valid TSF file"); + let c = -1; + for (; n < r && !l.includes("END"); ) + if (l = o(), l.toLowerCase().startsWith("file:") && (c = parseInt(l.split(" ").pop())), l.toLowerCase().startsWith("datatype:") && !l.endsWith("Float32LE")) + throw new Error("Only supports TSF files with Float32LE"); + if (c < 20) + throw new Error("Not a valid TSF file (missing file offset)"); + n = c; + const h = new DataView(e); + let u = 0; + for (; n + 4 <= r && u < t; ) { + const d = h.getFloat32(n, !0); + if (n += 4, isFinite(d)) + s[u++] = d; + else if (!isNaN(d)) + break; + } + return s; + } + // readTSF + // read mrtrix tck format streamlines + // https://mrtrix.readthedocs.io/en/latest/getting_started/image_data.html#tracks-file-format-tck + static readTCK(e) { + const t = e.byteLength; + if (t < 20) + throw new Error("File too small to be TCK: bytes = " + t); + const s = new Uint8Array(e); + let r = 0; + function a() { + for (; r < t && s[r] === 10; ) + r++; + const g = r; + for (; r < t && s[r] !== 10; ) + r++; + return r++, r - g < 1 ? "" : new TextDecoder().decode(e.slice(g, r - 1)); + } + let n = a(); + if (!n.includes("mrtrix tracks")) + throw new Error("Not a valid TCK file"); + let o = -1; + for (; r < t && !n.includes("END"); ) + n = a(), n.toLowerCase().startsWith("file:") && (o = parseInt(n.split(" ").pop())); + if (o < 20) + throw new Error("Not a valid TCK file (missing file offset)"); + r = o; + const l = new DataView(e); + let c = 0, h = new Uint32Array(t / 16), u = 0, d = 0, f = new Float32Array(t / 4); + for (h[0] = 0; r + 12 < t; ) { + const g = l.getFloat32(r, !0); + r += 4; + const m = l.getFloat32(r, !0); + r += 4; + const p = l.getFloat32(r, !0); + if (r += 4, isFinite(g)) + f[d++] = g, f[d++] = m, f[d++] = p, c++; + else if (h[u++] = c, !isNaN(g)) + break; + } + return f = f.slice(0, d), h = h.slice(0, u), { + pts: f, + offsetPt0: h + }; + } + // readTCK() + // not included in public docs + // read trackvis trk format streamlines + // http://trackvis.org/docs/?subsect=fileformat + static async readTRK(e) { + let t = new DataView(e), s = t.getUint32(0, !0); + if (s !== 1128354388) { + let S; + if (s === 4247762216) + throw new Error("zstd TRK decompression is not supported"); + S = await j.decompress(new Uint8Array(e)), e = S.buffer, t = new DataView(e), s = t.getUint32(0, !0); + } + const r = t.getUint32(992, !0), a = t.getUint32(996, !0); + if (r > 2 || a !== 1e3 || s !== 1128354388) + throw new Error("Not a valid TRK file"); + const n = t.getInt16(36, !0), o = []; + for (let S = 0; S < n; S++) { + const T = new Uint8Array(e.slice(38 + S * 20, 58 + S * 20)), k = new TextDecoder().decode(T).split("\0").shift(); + o.push({ + id: k.trim(), + // TODO can we guarantee this? + vals: [] + }); + } + const l = t.getFloat32(12, !0), c = t.getFloat32(16, !0), h = t.getFloat32(20, !0), u = Le( + 1 / l, + 0, + 0, + -0.5, + 0, + 1 / c, + 0, + -0.5, + 0, + 0, + 1 / h, + -0.5, + 0, + 0, + 0, + 1 + ), d = t.getInt16(238, !0), f = []; + for (let S = 0; S < d; S++) { + const T = new Uint8Array(e.slice(240 + S * 20, 260 + S * 20)), k = new TextDecoder().decode(T).split("\0").shift(); + f.push({ + id: k.trim(), + // TODO can we guarantee this? + vals: [] + }); + } + const g = ie(); + for (let S = 0; S < 16; S++) + g[S] = t.getFloat32(440 + S * 4, !0); + g[15] === 0 && (R.warn("TRK vox_to_ras not set"), pn(g)); + const m = ie(); + An(m, u, g); + let p = null, v = null; + p = new Int32Array(e.slice(a)), v = new Float32Array(p.buffer); + const A = p.length; + if (A < 1) + throw new Error("Empty TRK file."); + let x = 0, w = 0, D = new Uint32Array(p.length / 4), b = 0, C = new Float32Array(p.length), E = 0; + for (; x < A; ) { + const S = p[x]; + x = x + 1, D[b++] = w; + for (let T = 0; T < S; T++) { + const k = v[x + 0], B = v[x + 1], U = v[x + 2]; + if (x += 3, C[E++] = k * m[0] + B * m[1] + U * m[2] + m[3], C[E++] = k * m[4] + B * m[5] + U * m[6] + m[7], C[E++] = k * m[8] + B * m[9] + U * m[10] + m[11], n > 0) + for (let V = 0; V < n; V++) + o[V].vals.push(v[x]), x++; + w++; + } + if (d > 0) + for (let T = 0; T < d; T++) + f[T].vals.push(v[x]), x++; + } + const F = []; + for (let S = 0; S < f.length; S++) + F.push({ + id: f[S].id, + vals: Float32Array.from(f[S].vals) + }); + const M = []; + for (let S = 0; S < o.length; S++) + M.push({ + id: o[S].id, + vals: Float32Array.from(o[S].vals) + }); + return D[b++] = w, C = C.slice(0, E), D = D.slice(0, b), { + pts: C, + offsetPt0: D, + dps: F, + dpv: M + }; + } + // readTRK() + // read legacy VTK text format file + static readTxtVTK(e) { + const r = new TextDecoder("utf-8").decode(e).split(` +`); + if (r.length < 7 || !r[0].startsWith("# vtk DataFile")) + throw new Error("Invalid VTK image"); + if (!r[2].startsWith("ASCII")) + throw new Error("Not ASCII VTK mesh"); + let n = 3; + for (; r[n].length < 1; ) + n++; + if (!r[n].includes("POLYDATA")) + throw new Error("Not ASCII VTK polydata"); + for (n++; r[n].length < 1; ) + n++; + if (!r[n].startsWith("POINTS")) + throw new Error("Not VTK POINTS"); + let o = r[n].trim().split(/\s+/); + const l = parseInt(o[1]), c = l * 3, h = new Float32Array(l * 3); + let u = 0; + for (; u < l * 3; ) { + n++; + const m = r[n].trim().trim().split(/\s+/); + for (let p = 0; p < m.length && !(u >= c); p++) + h[u] = parseFloat(m[p]), u++; + } + const d = []; + for (n++; r[n].length < 1; ) + n++; + if (r[n].startsWith("METADATA")) { + for (; r[n].length > 1; ) + n++; + n++; + } + if (o = r[n].trim().split(/\s+/), n++, o[0].includes("LINES")) { + const g = parseInt(o[1]); + if (g < 1) + throw new Error("Corrupted VTK ASCII"); + let m = r[n].trim(); + const p = []; + let v = []; + if (m.startsWith("OFFSETS")) { + n++; + let A = 0; + for (; A < g; ) { + m = r[n].trim(), n++; + const x = m.trim().split(/\s+/); + for (let w = 0; w < x.length && (p[A] = parseInt(x[w]), A++, !(A >= g)); w++) + ; + } + v = Array.from(h); + } else { + let A = function() { + m = r[n].trim(); + const b = m.trim().split(/\s+/); + w = []; + for (let C = 0; C < b.length; C++) + w.push(parseInt(b[C])); + D = 0, n++; + }, x = 0; + p[0] = 0; + let w = [], D = 0; + A(); + for (let b = 0; b < g; b++) { + D >= w.length && A(); + const C = w[D++]; + x += C, p[b + 1] = x; + for (let E = 0; E < C; E++) { + D >= w.length && A(); + const F = w[D++] * 3; + v.push(h[F + 0]), v.push(h[F + 1]), v.push(h[F + 2]); + } + } + } + return { + pts: Float32Array.from(v), + offsetPt0: Uint32Array.from(p) + }; + } else if (o[0].includes("TRIANGLE_STRIPS")) { + const g = parseInt(o[1]); + for (let m = 0; m < g; m++) { + const p = r[n].trim(); + n++; + const v = p.trim().split(/\s+/), A = parseInt(v[0]) - 2; + let x = 1; + for (let w = 0; w < A; w++) + w % 2 ? (d.push(parseInt(v[x + 2])), d.push(parseInt(v[x + 1])), d.push(parseInt(v[x]))) : (d.push(parseInt(v[x])), d.push(parseInt(v[x + 1])), d.push(parseInt(v[x + 2]))), x += 1; + } + } else if (o[0].includes("POLYGONS")) { + const g = parseInt(o[1]); + for (let m = 0; m < g; m++) { + const p = r[n].trim(); + n++; + const v = p.trim().split(/\s+/), A = parseInt(v[0]) - 2, x = parseInt(v[1]); + let w = parseInt(v[2]); + for (let D = 0; D < A; D++) { + const b = parseInt(v[3 + D]); + d.push(x), d.push(w), d.push(b), w = b; + } + } + } else + throw new Error("Unsupported ASCII VTK datatype " + o[0]); + const f = new Uint32Array(d); + return { + positions: h, + indices: f + }; + } + // readTxtVTK() + // read mesh overlay to influence vertex colors + static async readLayer(e = "", t, s, r = 0.5, a = "warm", n = "winter", o = !1, l = null, c = null, h = 0) { + const u = { + ...Zc, + colormapInvert: !1, + colormapType: 0, + // COLORMAP_TYPE.MIN_TO_MAX + isTransparentBelowCalMin: !0, + isAdditiveBlend: !1, + colorbarVisible: !0, + colormapLabel: null + }, d = /(?:\.([^.]+))?$/; + let f = d.exec(e)[1]; + f = f.toUpperCase(), f === "GZ" && (f = d.exec(e.slice(0, -3))[1], f = f.toUpperCase()); + const g = s.vertexCount / 3; + if (s.offsetPt0) { + const v = e.split("/"); + let A = "Unknown"; + if (v.length > 1 && v.pop() && (A = A.split(".").slice(0, -1).join(".")), f === "TXT") { + const C = s.offsetPt0.length - 1, E = we.readTXT(t, C); + if (E.length !== C) + throw new Error(`TXT file has ${E.length} items, expected one per streamline (${C}).`); + s.dps || (s.dps = []); + const F = E.reduce((S, T) => Math.min(S, T)), M = E.reduce((S, T) => Math.max(S, T)); + return s.dps.push({ + id: A, + vals: Float32Array.from(E.slice()), + global_min: F, + global_max: M, + cal_min: F, + cal_max: M + }), u; + } + if (f !== "TSF") + throw new Error("readLayer for streamlines only supports TSF and TXT files."); + const x = s.pts.length / 3, w = we.readTSF(t, x); + s.dpv || (s.dpv = []); + const D = w.reduce((C, E) => Math.min(C, E)), b = w.reduce((C, E) => Math.max(C, E)); + return s.dpv.push({ + id: A, + vals: Float32Array.from(w.slice()), + global_min: D, + global_max: b, + cal_min: D, + cal_max: b + }), u; + } + if (g < 3) { + R.error("n_vert < 3 in layer"); + return; + } + if (f === "MZ3") { + const v = await we.readMZ3(t, g); + u.values = v.scalars, "colormapLabel" in v && (u.colormapLabel = v.colormapLabel); + } else if (f === "ANNOT") { + const v = we.readANNOT(t, g, !0); + v instanceof Uint32Array ? u.values = v : (u.values = v.scalars, u.colormapLabel = v.colormapLabel); + } else if (f === "CRV" || f === "CURV" || f === "THICKNESS" || f === "AREA") + u.values = we.readCURV(t, g), u.isTransparentBelowCalMin = !1; + else if (f === "GII") { + const v = await we.readGII(t, g); + u.values = v.scalars, u.colormapLabel = v.colormapLabel; + } else if (f === "MGH" || f === "MGZ") { + const v = await we.readMGH(t, g, !0); + "scalars" in v ? (u.values = v.scalars, u.colormapLabel = v.colormapLabel) : u.values = v; + } else if (f === "NII") + u.values = await we.readNII( + t, + s.pts, + s.anatomicalStructurePrimary + ); + else if (f === "SMP") + u.values = await we.readSMP(t, g); + else if (f === "STC") + u.values = we.readSTC(t, g); + else if (we.isCurv(t)) + u.values = we.readCURV(t, g), u.isTransparentBelowCalMin = !1; + else + return R.warn("Unknown layer overlay format " + e), u; + if (!u.values) { + R.error("no values in layer"); + return; + } + u.nFrame4D = u.values.length / g, u.frame4D = 0, u.outlineBorder = h; + let m = u.values[0], p = u.values[0]; + for (let v = 0; v < u.values.length; v++) + m = Math.min(m, u.values[v]), p = Math.max(p, u.values[v]); + return u.global_min = m, u.global_max = p, u.cal_min = l || 0, l || (u.cal_min = m), u.cal_max = c || 0, c || (u.cal_max = p), u.cal_minNeg = NaN, u.cal_maxNeg = NaN, u.opacity = r, u.colormap = a, u.colormapNegative = n, u.useNegativeCmap = o, u; + } + // readLayer() + // read brainvoyager smp format file + // https://support.brainvoyager.com/brainvoyager/automation-development/84-file-formats/40-the-format-of-smp-files + static async readSMP(e, t) { + const s = e.byteLength; + let r = new DataView(e), a = r.getUint16(0, !0); + if (a > 5) { + const u = await j.decompress(new Uint8Array(e)); + r = new DataView(u.buffer), a = r.getUint16(0, !0), e = u.buffer; + } + a > 5 && R.error("Unsupported or invalid BrainVoyager SMP version " + a); + const n = r.getUint32(2, !0); + n !== t && R.error("SMP file has " + n + " vertices, background mesh has " + t); + const o = r.getUint16(6, !0), l = new Float32Array(n * o); + let c = 9; + function h() { + const u = c; + for (; c < s && r.getUint8(c) !== 0; ) + c++; + return c++, new TextDecoder().decode(e.slice(u, c - 1)); + } + h(); + for (let u = 0; u < o; u++) { + const d = {}; + d.mapType = r.getUint32(c, !0), c += 4, a >= 3 && d.mapType === 3 && (d.nLags = r.getUint32(c, !0), c += 4, d.mnLag = r.getUint32(c, !0), c += 4, d.mxLag = r.getUint32(c, !0), c += 4, d.ccOverlay = r.getUint32(c, !0), c += 4), d.clusterSize = r.getUint32(c, !0), c += 4, d.clusterCheck = r.getUint8(c), c += 1, d.critThresh = r.getFloat32(c, !0), c += 4, d.maxThresh = r.getFloat32(c, !0), c += 4, a >= 4 && (d.includeValuesGreaterThreshMax = r.getUint32(c, !0), c += 4), d.df1 = r.getUint32(c, !0), c += 4, d.df2 = r.getUint32(c, !0), c += 4, a >= 5 ? (d.posNegFlag = r.getUint32(c, !0), c += 4) : d.posNegFlag = 3, d.cortexBonferroni = r.getUint32(c, !0), c += 4, d.posMinRGB = [0, 0, 0], d.posMaxRGB = [0, 0, 0], d.negMinRGB = [0, 0, 0], d.negMaxRGB = [0, 0, 0], a >= 2 && (d.posMinRGB[0] = r.getUint8(c), c++, d.posMinRGB[1] = r.getUint8(c), c++, d.posMinRGB[2] = r.getUint8(c), c++, d.posMaxRGB[0] = r.getUint8(c), c++, d.posMaxRGB[1] = r.getUint8(c), c++, d.posMaxRGB[2] = r.getUint8(c), c++, a >= 4 && (d.negMinRGB[0] = r.getUint8(c), c++, d.negMinRGB[1] = r.getUint8(c), c++, d.negMinRGB[2] = r.getUint8(c), c++, d.negMaxRGB[0] = r.getUint8(c), c++, d.negMaxRGB[1] = r.getUint8(c), c++, d.negMaxRGB[2] = r.getUint8(c), c++), d.enableSMPColor = r.getUint8(c), c++, a >= 4 && (d.lut = h()), d.colorAlpha = r.getFloat32(c, !0), c += 4), d.name = h(); + const f = new Float32Array(e, c, n); + l.set(f, u * n), c += n * 4; + } + return l; + } + // readSMP() + // read mne stc format file, not to be confused with brainvoyager stc format + // https://github.com/mne-tools/mne-python/blob/main/mne/source_estimate.py#L211-L365 + static readSTC(e, t) { + const s = new DataView(e), r = s.getInt32(8, !1); + if (r !== t) + throw new Error("Overlay has " + r + " vertices, expected " + t); + let a = 12 + r * 4; + const n = s.getUint32(a, !1); + a += 4; + const o = new Float32Array(n * r); + for (let l = 0; l < n * r; l++) + o[l] = s.getFloat32(a, !1), a += 4; + return o; + } + // readSTC() + static isCurv(e) { + const t = new DataView(e), s = t.getUint8(0), r = t.getUint8(1), a = t.getUint8(2); + return s !== 255 || r !== 255 || a !== 255 ? (Ot.debug("Unable to recognize file type: does not appear to be FreeSurfer format."), !1) : !0; + } + // read freesurfer curv big-endian format + // https://github.com/bonilhamusclab/MRIcroS/blob/master/%2BfileUtils/%2Bpial/readPial.m + // http://www.grahamwideman.com/gw/brain/fs/surfacefileformats.htm + static readCURV(e, t) { + const s = new DataView(e), r = s.getUint8(0), a = s.getUint8(1), n = s.getUint8(2), o = s.getUint32(3, !1), l = s.getUint32(11, !1); + if ((r !== 255 || a !== 255 || n !== 255) && Ot.debug("Unable to recognize file type: does not appear to be FreeSurfer format."), t !== o) + throw new Error("CURV file has different number of vertices ( " + o + ")than mesh (" + t + ")"); + if (e.byteLength < 15 + 4 * o * l) + throw new Error("CURV file smaller than specified"); + const c = new Float32Array(l * o); + let h = 15; + for (let g = 0; g < l * o; g++) + c[g] = s.getFloat32(h, !1), h += 4; + let u = c[0], d = c[0]; + for (let g = 0; g < c.length; g++) + u = Math.min(u, c[g]), d = Math.max(d, c[g]); + const f = 1 / (d - u); + for (let g = 0; g < c.length; g++) + c[g] = 1 - (c[g] - u) * f; + return c; + } + // readCURV() + // read freesurfer Annotation file provides vertex colors + // https://surfer.nmr.mgh.harvard.edu/fswiki/LabelsClutsAnnotationFiles + static readANNOT(e, t, s = !1) { + const r = new DataView(e), a = r.getUint32(0, !1), n = this.decimateLayerVertices(a, t); + if (t !== n) + throw new Error("ANNOT file has different number of vertices than mesh"); + if (e.byteLength < 4 + 8 * a) + throw new Error("ANNOT file smaller than specified"); + let o = 0; + const l = new Uint32Array(a); + for (let x = 0; x < a; x++) { + const w = r.getUint32(o += 4, !1); + l[w] = r.getUint32(o += 4, !1); + } + if (!s) + return l; + let c = 0; + try { + c = r.getInt32(o += 4, !1); + } catch { + return l; + } + if (c !== 1 || r.getInt32(o += 4, !1) > 0) + return l; + const d = r.getInt32(o += 4, !1), f = r.getInt32(o += 4, !1); + o += f; + const g = r.getInt32(o += 4, !1); + if (g < 1) + return l; + const m = { + R: Array(d).fill(0), + G: Array(d).fill(0), + B: Array(d).fill(0), + A: Array(d).fill(0), + I: Array(d).fill(0), + labels: Array(d).fill("") + }; + for (let x = 0; x < g; x++) { + const w = r.getInt32(o += 4, !1), D = r.getInt32(o += 4, !1); + o += 4; + let b = ""; + for (let S = 0; S < D; S++) { + const T = r.getUint8(o++); + if (T === 0) + break; + b += String.fromCharCode(T); + } + o -= 4; + const C = r.getInt32(o += 4, !1), E = r.getInt32(o += 4, !1), F = r.getInt32(o += 4, !1), M = r.getInt32(o += 4, !1); + if (w < 0 || w >= d) { + R.warn("annot entry out of range"); + continue; + } + m.R[w] = C, m.G[w] = E, m.B[w] = F, m.A[w] = M, m.I[w] = (M << 24) + (F << 16) + (E << 8) + C, m.labels[w] = b; + } + const p = new Float32Array(a); + p.fill(-1); + let v = 0; + for (let x = 0; x < t; x++) { + const w = l[x]; + for (let D = 0; D < d; D++) + if (m.I[D] === w) { + p[x] = D; + break; + } + p[x] < 0 && (v++, p[x] = 0); + } + v > 0 && R.error(`annot vertex colors do not match ${v} of ${a} vertices.`); + for (let x = 0; x < d; x++) + m.I[x] = x; + const A = oe.makeLabelLut(m); + return { + scalars: p, + colormapLabel: A + }; + } + // readANNOT() + // read BrainNet viewer format + // https://www.nitrc.org/projects/bnv/ + static readNV(e) { + const t = e.byteLength, s = new Uint8Array(e); + let r = 0; + function a() { + for (; r < t && s[r] === 10; ) + r++; + const d = r; + for (; r < t && s[r] !== 10; ) + r++; + return r++, r - d < 1 ? "" : new TextDecoder().decode(e.slice(d, r - 1)); + } + let n = 0, o = 0, l = 0, c = 0, h, u; + for (; r < t; ) { + const d = a(); + if (d.startsWith("#")) + continue; + const f = d.trim().split(/\s+/); + if (n < 1) { + n = parseInt(f[0]), h = new Float32Array(n * 3); + continue; + } + if (l < n * 3) { + h[l] = parseFloat(f[0]), h[l + 1] = parseFloat(f[1]), h[l + 2] = parseFloat(f[2]), l += 3; + continue; + } + if (o < 1) { + o = parseInt(f[0]), u = new Uint32Array(o * 3); + continue; + } + if (c >= o * 3) + break; + u[c + 2] = parseInt(f[0]) - 1, u[c + 1] = parseInt(f[1]) - 1, u[c + 0] = parseInt(f[2]) - 1, c += 3; + } + return { + positions: h, + indices: u + }; + } + // readNV() + // read ASCII Patch File format + // https://afni.nimh.nih.gov/pub/dist/doc/htmldoc/demos/Bootcamp/CD.html#cd + // http://www.grahamwideman.com/gw/brain/fs/surfacefileformats.htm + static readASC(e) { + const t = e.byteLength, s = new Uint8Array(e); + let r = 0; + function a() { + for (; r < t && s[r] === 10; ) + r++; + const f = r; + for (; r < t && s[r] !== 10; ) + r++; + return r++, r - f < 1 ? "" : new TextDecoder().decode(e.slice(f, r - 1)); + } + let n = a(); + n.startsWith("#!ascii") || R.warn("Invalid ASC mesh"), n = a(); + let o = n.trim().split(/\s+/); + const l = parseInt(o[0]), c = parseInt(o[1]), h = new Float32Array(l * 3); + let u = 0; + for (let f = 0; f < l; f++) + n = a(), o = n.trim().split(/\s+/), h[u] = parseFloat(o[0]), h[u + 1] = parseFloat(o[1]), h[u + 2] = parseFloat(o[2]), u += 3; + const d = new Uint32Array(c * 3); + u = 0; + for (let f = 0; f < c; f++) + n = a(), o = n.trim().split(/\s+/), d[u] = parseInt(o[0]), d[u + 1] = parseInt(o[1]), d[u + 2] = parseInt(o[2]), u += 3; + return { + positions: h, + indices: d + }; + } + // readASC() + // read legacy VTK format + static readVTK(e) { + const t = e.byteLength; + if (t < 20) + throw new Error("File too small to be VTK: bytes = " + e.byteLength); + const s = new Uint8Array(e); + let r = 0; + function a(m = !0) { + if (m) + for (; r < t && s[r] === 10; ) + r++; + const p = r; + for (; r < t && s[r] !== 10; ) + r++; + return r++, r - p < 1 ? "" : new TextDecoder().decode(e.slice(p, r - 1)); + } + let n = a(); + if (!n.startsWith("# vtk DataFile")) + throw new Error("Invalid VTK mesh"); + if (n = a(!1), n = a(), n.startsWith("ASCII")) + return we.readTxtVTK(e); + if (!n.startsWith("BINARY")) + throw new Error("Invalid VTK image, expected ASCII or BINARY " + n); + if (n = a(), !n.includes("POLYDATA")) + throw new Error("Only able to read VTK POLYDATA " + n); + n = a(), (!n.includes("POINTS") || !n.includes("double") && !n.includes("float")) && R.warn("Only able to read VTK float or double POINTS" + n); + const o = n.includes("double"); + let l = n.trim().split(/\s+/); + const h = parseInt(l[1]) * 3, u = new Float32Array(h), d = new DataView(e); + if (o) + for (let m = 0; m < h; m++) + u[m] = d.getFloat64(r, !1), r += 8; + else + for (let m = 0; m < h; m++) + u[m] = d.getFloat32(r, !1), r += 4; + n = a(), l = n.trim().split(/\s+/); + const f = []; + if (l[0].includes("LINES")) { + const m = parseInt(l[1]), p = r; + if (n = a(), n.startsWith("OFFSETS")) { + let w = !1; + n.includes("int64") && (w = !0); + const D = new Uint32Array(m); + if (w) { + let C = !1; + for (let E = 0; E < m; E++) { + let F = d.getInt32(r, !1); + F !== 0 && (C = !0), r += 4, F = d.getInt32(r, !1), r += 4, D[E] = F; + } + C && R.warn("int32 overflow: JavaScript does not support int64"); + } else + for (let C = 0; C < m; C++) { + const E = d.getInt32(r, !1); + r += 4, D[C] = E; + } + return { + pts: u, + offsetPt0: D + }; + } + r = p; + let v = 0; + const A = [], x = []; + A.push(v); + for (let w = 0; w < m; w++) { + const D = d.getInt32(r, !1); + r += 4, v += D, A.push(v); + for (let b = 0; b < D; b++) { + const C = d.getInt32(r, !1) * 3; + r += 4, x.push(u[C + 0]), x.push(u[C + 1]), x.push(u[C + 2]); + } + } + return { + pts: Float32Array.from(x), + offsetPt0: Uint32Array.from(A) + }; + } else if (l[0].includes("TRIANGLE_STRIPS")) { + const m = parseInt(l[1]); + for (let p = 0; p < m; p++) { + const v = d.getInt32(r, !1) - 2; + r += 4; + for (let A = 0; A < v; A++) + A % 2 ? (f.push(d.getInt32(r + 8, !1)), f.push(d.getInt32(r + 4, !1)), f.push(d.getInt32(r, !1))) : (f.push(d.getInt32(r, !1)), f.push(d.getInt32(r + 4, !1)), f.push(d.getInt32(r + 8, !1))), r += 4; + r += 8; + } + } else if (l[0].includes("POLYGONS")) { + const m = parseInt(l[1]), p = r, v = a(); + if (v.startsWith("OFFSETS")) { + let A = v.includes("int64"); + const x = new Uint32Array(m); + let w = !1; + for (let E = 0; E < m; E++) + A && (d.getInt32(r, !1) !== 0 && (w = !0), r += 4), x[E] = d.getInt32(r, !1), r += 4; + if (!Number.isSafeInteger(m) || m >= 2147483648 || w) + throw new Error("values exceed 2GB limit"); + const D = a(); + if (!D.startsWith("CONNECTIVITY")) + throw new Error("Expected CONNECTIVITY after OFFSETS"); + A = D.includes("int64"); + const b = x[m - 1], C = new Uint32Array(b); + for (let E = 0; E < b; E++) + A && (r += 4), C[E] = d.getInt32(r, !1), r += 4; + for (let E = 0; E < m; E++) { + const F = E === 0 ? 0 : x[E - 1], M = x[E]; + for (let S = 1; S < M - F - 1; S++) + f.push(C[F]), f.push(C[F + S]), f.push(C[F + S + 1]); + } + } else { + r = p; + for (let A = 0; A < m; A++) { + const x = d.getInt32(r, !1) - 2; + if (A === 0 && x > 65535) + throw new Error("Invalid VTK binary polygons using little-endian data (MRtrix)"); + r += 4; + const w = d.getInt32(r, !1); + r += 4; + let D = d.getInt32(r, !1); + r += 4; + for (let b = 0; b < x; b++) { + const C = d.getInt32(r, !1); + r += 4, f.push(w, D, C), D = C; + } + } + } + } else + throw new Error("Unsupported binary VTK datatype " + l[0]); + const g = new Uint32Array(f); + return { + positions: u, + indices: g + }; + } + // readVTK() + static readWRL(e) { + const t = new TextDecoder("utf-8").decode(e), s = /coord\s+Coordinate\s*\{\s*point\s*\[([\s\S]*?)\]/, r = /coordIndex\s*\[([\s\S]*?)\]/, a = /color\s+Color\s*\{\s*color\s*\[([\s\S]*?)\]/, n = s.exec(t), o = r.exec(t), l = a.exec(t); + if (!n || !o) + throw new Error("Invalid WRL file: Could not find vertices or indices."); + const c = new Float32Array( + n[1].trim().split(/[\s,]+/).map(Number) + ); + let h = null; + if (l) { + h = new Float32Array( + l[1].trim().split(/[\s,]+/).map(Number) + ); + const d = c.length / 3; + h.length !== d * 3 && (console.warn(`Unexpected color count: expected ${d * 3}, got ${h.length}`), h = null); + } + const u = new Uint32Array( + o[1].trim().split(/[\s,]+/).map(Number).filter((d) => d !== -1) + ); + return { positions: c, indices: u, colors: h }; + } + // readWRL() + // read brainsuite DFS format + // http://brainsuite.org/formats/dfs/ + static readDFS(e) { + const t = new DataView(e), s = t.getUint32(0, !0), r = t.getUint16(4, !0); + (s !== 1599292996 || r !== 17740) && R.warn("Not a little-endian brainsuite DFS mesh"); + const a = t.getUint32(12, !0), n = t.getUint32(24, !0), o = t.getUint32(28, !0), l = t.getUint32(48, !0); + let c = a; + const h = new Uint32Array(e, c, n * 3); + c += n * 3 * 4; + const u = new Float32Array(e, c, o * 3); + for (let f = 0; f < o * 3; f += 3) { + const g = u[f]; + u[f] = u[f + 1], u[f + 1] = g; + } + let d; + return l >= 0 && (d = new Float32Array(e, l, o * 3)), { + positions: u, + indices: h, + colors: d + }; + } + // read surfice MZ3 format + // https://github.com/neurolabusc/surf-ice/tree/master/mz3 + static async readMZ3(e, t = 0) { + if (e.byteLength < 20) + throw new Error("File too small to be mz3: bytes = " + e.byteLength); + let s = new DataView(e), r = e, a = s.getUint16(0, !0); + if (a === 35615 || a === 8075) { + const F = await j.decompress(new Uint8Array(e)); + s = new DataView(F.buffer), a = s.getUint16(0, !0), r = F.buffer; + } + const n = s.getUint16(2, !0), o = s.getUint32(4, !0); + let l = s.getUint32(8, !0); + const c = s.getUint32(12, !0); + if (Ot.debug("MZ3 magic %d attr %d face %d vert %d skip %d", a, n, o, l, c), a !== 23117) + throw new Error("Invalid MZ3 file"); + const h = (n & 1) !== 0, u = (n & 2) !== 0, d = (n & 4) !== 0; + let f = (n & 8) !== 0; + const g = (n & 16) !== 0, m = (n & 32) !== 0, p = (n & 64) !== 0; + if (Ot.debug( + `isFace=${h} isVert=${u} isRGBA=${d} isSCALAR=${f} isDOUBLE=${g} isAOMAP=${m} isLOOKUP=${p}` + ), n > 127) + throw new Error("Unsupported future version of MZ3 file"); + let v = 4; + g && (v = 8); + let A = 0; + if (t > 0 && !h && o < 1 && !d && (f = !0), f) { + const F = t || l, M = 16 + c + (h ? o * 12 : 0) + (u ? F * 12 : 0) + (d ? F * 4 : 0), S = Math.floor((r.byteLength - M) / v); + l !== t && S % t === 0 && (l = t), A = Math.floor(S / l), A < 1 && (R.warn("Corrupt MZ3: file reports NSCALAR but not enough bytes"), f = !1); + } + if (l < 3 && t < 3) + throw new Error("Not a mesh MZ3 file (maybe scalar)"); + t > 0 && t !== l && R.warn("Layer has " + l + "vertices, but background mesh has " + t); + let x = 16 + c; + const w = new DataView(r); + let D = null; + if (h) { + D = new Uint32Array(o * 3); + for (let F = 0; F < o * 3; F++) + D[F] = w.getUint32(x, !0), x += 4; + } + let b = null; + if (u) { + b = new Float32Array(l * 3); + for (let F = 0; F < l * 3; F++) + b[F] = w.getFloat32(x, !0), x += 4; + } + let C = null; + if (d) { + C = new Float32Array(l * 3); + for (let F = 0; F < l; F++) { + for (let M = 0; M < 3; M++) + C[F * 3 + M] = w.getUint8(x++) / 255; + x++; + } + } + let E = new Float32Array(); + if (f && A > 0) + if (g) { + const F = new Float64Array(A * l); + for (let M = 0; M < A * l; M++) + F[M] = w.getFloat64(x, !0), x += 8; + E = Float32Array.from(F); + } else { + E = new Float32Array(A * l); + for (let F = 0; F < A * l; F++) + E[F] = w.getFloat32(x, !0), x += 4; + } + if (t > 0 && p && f) { + const F = new TextDecoder("utf-8"), M = new Uint8Array(r, 16, c), S = F.decode(M), T = JSON.parse(S), k = oe.makeLabelLut(T); + return { scalars: E, colormapLabel: k }; + } + if (t > 0 && d && f) { + let F = E[0]; + for (let T = 0; T < l; T++) + F = Math.max(F, E[T]); + const M = { R: [], G: [], B: [], A: [], I: [], labels: [] }; + for (let T = 0; T <= F; T++) + for (let k = 0; k < l; k++) + if (T === E[k]) { + const B = k * 3; + M.I.push(T), M.R.push(C[B] * 255), M.G.push(C[B + 1] * 255), M.B.push(C[B + 2] * 255), M.A.push(255), M.labels.push(`${T}`); + break; + } + const S = oe.makeLabelLut(M); + return { scalars: E, colormapLabel: S }; + } + return t > 0 ? { scalars: E } : { positions: b, indices: D, scalars: E, colors: C }; + } + // read PLY format + // https://en.wikipedia.org/wiki/PLY_(file_format) + static readPLY(e) { + const t = e.byteLength, s = new Uint8Array(e); + let r = 0; + function a() { + for (; r < t && s[r] === 10; ) + r++; + const E = r; + for (; r < t && s[r] !== 10; ) + r++; + return r++, r - E < 1 ? "" : new TextDecoder().decode(e.slice(E, r - 1)); + } + let n = a(); + if (!n.startsWith("ply")) + throw new Error("Not a valid PLY file"); + n = a(); + const o = n.includes("ascii"); + function l(E) { + if (E === "char" || E === "uchar" || E === "int8" || E === "uint8") + return 1; + if (E === "short" || E === "ushort" || E === "int16" || E === "uint16") + return 2; + if (E === "int" || E === "uint" || E === "int32" || E === "uint32" || E === "float" || E === "float32") + return 4; + if (E === "double") + return 8; + throw new Error("Unknown data type: " + E); + } + const c = n.includes("binary_little_endian"); + let h = 0, u = !1, d = 0, f = 0, g = 0, m = 0, p = 0, v = 0, A = 0; + for (; r < t && !n.startsWith("end_header"); ) { + if (n = a(), n.startsWith("comment")) + continue; + let E = n.split(/\s/); + if (n.startsWith("element vertex")) + for (h = parseInt(E[E.length - 1]), n = a(), E = n.split(/\s/); n.startsWith("property"); ) { + const F = E[1]; + E[2] === "x" && F.startsWith("double") ? u = !0 : E[2] === "x" && !F.startsWith("float") && R.error("Error: expect ply xyz to be float or double: " + n), d += l(F), n = a(), E = n.split(/\s/); + } + if (n.startsWith("element face")) + for (A = parseInt(E[E.length - 1]), n = a(), E = n.split(/\s/); n.startsWith("property"); ) { + if (E[1] === "list") + g = l(E[2]), m = l(E[3]), f += g + 3 * m; + else { + const F = l(E[1]); + f += F, m === 0 && (p += F, v++); + } + n = a(), E = n.split(/\s/); + } + } + if (o) { + A < 1 && R.error(`Malformed ply format: faces ${A} `); + const E = new Float32Array(h * 3); + let F = 0; + for (let T = 0; T < h; T++) { + n = a(); + const k = n.split(/\s/); + E[F] = parseFloat(k[0]), E[F + 1] = parseFloat(k[1]), E[F + 2] = parseFloat(k[2]), F += 3; + } + let M = new Uint32Array(A * 3), S = 0; + for (let T = 0; T < A; T++) { + n = a(); + const k = n.split(/\s/), B = parseInt(k[v]) - 2; + if (B < 1) + break; + if (S + B * 3 > M.length) { + const N = new Uint32Array(M.length + M.length); + N.set(M), M = N.slice(); + } + const U = parseInt(k[v + 1]); + let V = parseInt(k[v + 2]); + for (let N = 0; N < B; N++) { + const P = parseInt(k[v + 3 + N]); + M[S + 0] = U, M[S + 1] = V, M[S + 2] = P, V = P, S += 3; + } + } + return M.length !== S && (M = M.slice(0, S)), { + positions: E, + indices: M + }; + } + (d < 12 || g < 1 || m < 1 || A < 1) && R.warn( + `Malformed ply format: stride ${d} count ${g} iBytes ${m} iStrideBytes ${f} iPadBytes ${p} faces ${A}` + ); + const x = new DataView(e); + let w; + if (r % 4 === 0 && d === 12 && c) + w = new Float32Array(e, r, h * 3), r += h * d; + else { + w = new Float32Array(h * 3); + let E = 0; + for (let F = 0; F < h; F++) + u ? (w[E] = x.getFloat64(r, c), w[E + 1] = x.getFloat64(r + 8, c), w[E + 2] = x.getFloat64(r + 16, c)) : (w[E] = x.getFloat32(r, c), w[E + 1] = x.getFloat32(r + 4, c), w[E + 2] = x.getFloat32(r + 8, c)), E += 3, r += d; + } + const D = new Uint32Array(A * 3); + let b = !0, C = 0; + if (g === 1 && m === 4 && f === 13) + for (let E = 0; E < A; E++) { + const F = x.getUint8(r); + r += g, F !== 3 && (b = !1), D[C] = x.getUint32(r, c), r += 4, D[C + 1] = x.getUint32(r, c), r += 4, D[C + 2] = x.getUint32(r, c), r += 4, C += 3; + } + else { + let E = r; + for (let F = 0; F < A; F++) { + r = E + p; + let M = 0; + g === 1 ? M = x.getUint8(r) : g === 2 ? M = x.getUint16(r, c) : g === 4 && (M = x.getUint32(r, c)), r += g, M !== 3 && (b = !1); + for (let S = 0; S < 3; S++) + m === 1 ? D[C] = x.getUint8(r) : m === 2 ? D[C] = x.getUint16(r, c) : m === 4 && (D[C] = x.getUint32(r, c)), C++, r += m; + E += f; + } + } + return b || R.warn("Only able to read PLY meshes limited to triangles."), { + positions: w, + indices: D + }; + } + // readPLY() + // FreeSurfer can convert meshes to ICO/TRI format text files + // https://github.com/dfsp-spirit/freesurferformats/blob/434962608108c75d4337d5e7a5096e3bd4ee6ee6/R/read_fs_surface.R#L1090 + // detect TRI format that uses same extension + // http://paulbourke.net/dataformats/tri/ + static readICO(e) { + const r = new TextDecoder("utf-8").decode(e).split(` +`); + let a = r[0].trim().split(/\s+/); + a.length > 1 && R.warn("This is not a valid FreeSurfer ICO/TRI mesh."); + const n = parseInt(a[0]), o = new Float32Array(n * 3); + let l = 1; + for (let u = 0; u < n; u++) { + const d = r[l].trim().split(/\s+/); + l++; + let f = parseInt(d[0]) - 1; + const g = parseFloat(d[1]), m = parseFloat(d[2]), p = parseFloat(d[3]); + if (f < 0 || f >= n) { + R.error("ICO vertices corrupted"); + break; + } + f *= 3, o[f] = g, o[f + 1] = m, o[f + 2] = p; + } + a = r[l].trim().split(/\s+/), l++; + const c = parseInt(a[0]), h = new Uint32Array(c * 3); + for (let u = 0; u < c; u++) { + const d = r[l].trim().split(/\s+/); + l++; + let f = parseInt(d[0]) - 1; + const g = parseInt(d[1]) - 1, m = parseInt(d[2]) - 1, p = parseInt(d[3]) - 1; + if (f < 0 || f >= c) { + R.error("ICO indices corrupted"); + break; + } + f *= 3, h[f] = g, h[f + 1] = m, h[f + 2] = p; + } + for (let u = 0; u < h.length; u += 3) { + const d = h[u]; + h[u] = h[u + 1], h[u + 1] = d; + } + return { + positions: o, + indices: h + }; + } + // readICO() + // While BYU and FreeSurfer GEO are related + // - BYU can have multiple parts + // - BYU faces not always triangular + // http://www.grahamwideman.com/gw/brain/fs/surfacefileformats.htm#GeoFile + // http://www.eg-models.de/formats/Format_Byu.html + // https://github.com/dfsp-spirit/freesurferformats/blob/dafaf88a601dac90fa3c9aae4432f003f5344546/R/read_fs_surface.R#L924 + // https://github.com/dfsp-spirit/freesurferformats/blob/434962608108c75d4337d5e7a5096e3bd4ee6ee6/R/read_fs_surface.R#L1144 + // n.b. AFNI uses the '.g' extension for this format 'ConvertSurface -i_gii L.surf.gii -o_byu L' + static readGEO(e, t = !1) { + const a = new TextDecoder("utf-8").decode(e).split(` +`), n = a[0].trim().split(/\s+/), o = parseInt(n[0]); + let l = parseInt(n[1]), c = parseInt(n[2]); + const h = parseInt(n[3]); + (o > 1 || h !== c * 3) && R.warn("Multi-part BYU/GEO header or not a triangular mesh."); + const u = []; + l *= 3; + let d = 0, f = 2; + for (; d < l; ) { + const A = a[f].trim().split(/\s+/); + f++; + for (let x = 0; x < A.length && (u.push(parseFloat(A[x])), d++, !(d >= l)); x++) + ; + } + const g = []; + c *= 3; + let m = 0; + for (; m < c; ) { + const A = a[f].trim().split(/\s+/); + f++; + for (let x = 0; x < A.length && (g.push(Math.abs(parseInt(A[x])) - 1), m++, !(m >= c)); x++) + ; + } + if (t) + for (let A = 0; A < g.length; A += 3) { + const x = g[A]; + g[A] = g[A + 1], g[A + 1] = x; + } + const p = new Float32Array(u), v = new Uint32Array(g); + return { + positions: p, + indices: v + }; + } + // readGEO() + // read OFF format + // https://en.wikipedia.org/wiki/OFF_(file_format) + static readOFF(e) { + const r = new TextDecoder("utf-8").decode(e).split(` +`), a = [], n = []; + let o = 0; + r[o].includes("OFF") ? o++ : R.warn("File does not start with OFF"); + let l = r[o].trim().split(/\s+/); + const c = parseInt(l[0]), h = parseInt(l[1]); + o++; + for (let f = 0; f < c; f++) + l = r[o].trim().split(/\s+/), a.push(parseFloat(l[0])), a.push(parseFloat(l[1])), a.push(parseFloat(l[2])), o++; + for (let f = 0; f < h; f++) + l = r[o].trim().split(/\s+/), parseInt(l[0]) !== 3 && R.warn("Only able to read OFF files with triangular meshes"), n.push(parseInt(l[1])), n.push(parseInt(l[2])), n.push(parseInt(l[3])), o++; + const u = new Float32Array(a), d = new Uint32Array(n); + return { + positions: u, + indices: d + }; + } + // readOFF() + static readOBJMNI(e) { + const r = new TextDecoder("utf-8").decode(e).trim().split(/\s*,\s*|\s+/); + (r.length < 1 || r[0] !== "P") && R.warn("This is not a valid MNI OBJ mesh."); + let a = 6; + const n = parseInt(r[a++]), o = n * 3, l = new Float32Array(o); + for (let g = 0; g < o; g++) + l[g] = parseFloat(r[a++]); + a += o; + const c = parseInt(r[a++]), h = parseInt(r[a++]); + (c < 1 || h < 0 || h > 2) && R.warn("This is not a valid MNI OBJ mesh."); + let u = 1; + h === 1 ? u = c : h === 1 && (u = n), a += u * 4, a += c; + const d = c * 3, f = new Uint32Array(d); + for (let g = 0; g < d; g++) + f[g] = parseInt(r[a++]); + return { + positions: l, + indices: f + }; + } + // readOBJMNI() + static async readOBJ(e) { + const t = new Uint8Array(e, 0, 2); + t[0] === 31 && t[1] === 139 && (e = await j.decompressToBuffer(new Uint8Array(e))); + const r = new TextDecoder("utf-8").decode(e); + if (r[0] === "P") + return this.readOBJMNI(e); + const a = r.split(` +`), n = a.length, o = [], l = []; + for (let f = 0; f < n; f++) { + const g = a[f]; + if (g[0] === "v" && g[1] === " ") { + const m = g.trim().split(/\s+/); + o.push(parseFloat(m[1])), o.push(parseFloat(m[2])), o.push(parseFloat(m[3])); + } + if (g[0] === "f") { + const m = g.trim().split(/\s+/), p = m.length - 3; + if (p < 1) + break; + let v = m[1].split("/"); + const A = parseInt(v[0]) - 1; + v = m[2].split("/"); + let x = parseInt(v[0]) - 1; + for (let w = 0; w < p; w++) { + v = m[3 + w].split("/"); + const D = parseInt(v[0]) - 1; + l.push(A), l.push(x), l.push(D), x = D; + } + } + } + const c = new Float32Array(o), h = new Uint32Array(l); + let u = h[0], d = h[0]; + for (let f = 1; f < h.length; f++) + h[f] < u && (u = h[f]), h[f] > d && (d = h[f]); + if (d - u + 1 > c.length / 3) + throw new Error("Not a valid OBJ file"); + for (let f = 0; f < h.length; f++) + h[f] -= u; + return { + positions: c, + indices: h + }; + } + // readOBJ() + // read FreeSurfer big endian format + static readFreeSurfer(e) { + const t = new Uint8Array(e); + if (t[0] === 35 && t[1] === 33 && t[2] === 97) + return we.readASC(e); + const s = new DataView(e), r = s.getUint32(0, !1), a = s.getUint32(4, !1); + (r !== 4294966883 || a !== 1919246708) && Ot.debug("Unable to recognize file type: does not appear to be FreeSurfer format."); + let n = 0; + for (; s.getUint8(n) !== 10; ) + n++; + n += 2; + let o = s.getUint32(n, !1); + n += 4; + let l = s.getUint32(n, !1); + n += 4, o *= 3; + const c = new Float32Array(o); + for (let f = 0; f < o; f++) + c[f] = s.getFloat32(n, !1), n += 4; + l *= 3; + const h = new Uint32Array(l); + for (let f = 0; f < l; f++) + h[f] = s.getUint32(n, !1), n += 4; + const u = s.getUint32(n, !1); + n += 4; + let d = u === 20; + if (!d) { + const f = s.getUint32(n, !1); + n += 4; + const g = s.getUint32(n, !1); + n += 4, d = u === 2 && f === 0 && g === 20; + } + if (!d) + R.warn("Unknown FreeSurfer Mesh extension code."); + else { + const g = new TextDecoder().decode(e.slice(n)).trim().split(` +`); + for (let m = 0; m < g.length; m++) { + if (!g[m].startsWith("cras")) + continue; + const v = g[m].split("=")[1].trim().split(" ").map(Number), A = Math.floor(c.length / 3); + let x = 0; + for (let w = 0; w < A; w++) + c[x] += v[0], x++, c[x] += v[1], x++, c[x] += v[2], x++; + } + } + return { + positions: c, + indices: h + }; + } + // readFreeSurfer() + // read brainvoyager SRF format + // https://support.brainvoyager.com/brainvoyager/automation-development/84-file-formats/344-users-guide-2-3-the-format-of-srf-files + static async readSRF(e) { + const t = new Uint8Array(e); + if (t[0] === 35 && t[1] === 33 && t[2] === 97) + return we.readASC(e); + t[0] === 31 && t[1] === 139 && (e = (await j.decompress(new Uint8Array(e))).buffer); + const s = new DataView(e), r = s.getFloat32(0, !0), a = s.getUint32(8, !0), n = s.getUint32(12, !0), o = s.getFloat32(16, !0), l = s.getFloat32(20, !0), c = s.getFloat32(24, !0), h = new Float32Array(a * 3); + let u = 28, d = 1; + for (let b = 0; b < a; b++) + h[d] = -s.getFloat32(u, !0) + o, d += 3, u += 4; + d = 2; + for (let b = 0; b < a; b++) + h[d] = -s.getFloat32(u, !0) + l, d += 3, u += 4; + d = 0; + for (let b = 0; b < a; b++) + h[d] = -s.getFloat32(u, !0) + c, d += 3, u += 4; + u = 28 + 24 * a; + const f = s.getFloat32(u, !0), g = s.getFloat32(u + 4, !0), m = s.getFloat32(u + 8, !0), p = s.getFloat32(u + 16, !0), v = s.getFloat32(u + 20, !0), A = s.getFloat32(u + 24, !0); + u += 32; + const x = new Float32Array(a * 3), w = new Uint32Array(e, u, a); + d = 0; + for (let b = 0; b < a; b++) { + const C = w[b]; + C > 1056964608 && (x[d + 0] = (C >> 16 & 255) / 255, x[d + 1] = (C >> 8 & 255) / 255, x[d + 2] = (C & 255) / 255), C === 0 && (x[d + 0] = f, x[d + 1] = g, x[d + 2] = m), C === 1 && (x[d + 0] = p, x[d + 1] = v, x[d + 2] = A), d += 3; + } + u += a * 4; + for (let b = 0; b < a; b++) { + const C = s.getUint32(u, !0); + u += 4 + 4 * C; + } + const D = new Uint32Array(n * 3); + for (let b = 0; b < n * 3; b++) + D[b] = s.getInt32(u, !0), u += 4; + return r !== 4 && R.warn("Not valid SRF"), { + positions: h, + indices: D, + colors: x + }; + } + // readSRF() + // read STL ASCII format file + // http://paulbourke.net/dataformats/stl/ + static readTxtSTL(e) { + const r = new TextDecoder("utf-8").decode(e).split(` +`); + if (!r[0].startsWith("solid")) + throw new Error("Not a valid STL file"); + const a = []; + for (let c = 1; c < r.length; c++) { + if (!r[c].includes("vertex")) + continue; + const h = r[c].trim().split(/\s+/); + for (let u = 1; u < h.length; u++) + a.push(parseFloat(h[u])); + } + const n = Math.floor(a.length / 3); + if (n * 3 !== a.length) + throw new Error("Unable to parse ASCII STL file."); + const o = new Float32Array(a), l = new Uint32Array(n); + for (let c = 0; c < n; c++) + l[c] = c; + return { + positions: o, + indices: l + }; + } + // readTxtSTL() + // read STL format, nb this format does not reuse vertices + // https://en.wikipedia.org/wiki/STL_(file_format) + static readSTL(e) { + if (e.byteLength < 134) + throw new Error("File too small to be STL: bytes = " + e.byteLength); + const t = new DataView(e); + if (t.getUint32(0, !0) === 1768714099) + return we.readTxtSTL(e); + const r = t.getUint32(80, !0), a = 3 * r; + if (e.byteLength < 84 + r * 50) + throw new Error("STL file too small to store triangles = " + r); + const n = new Uint32Array(a), o = new Float32Array(a * 3); + let l = 96, c = 0; + for (let h = 0; h < r; h++) { + for (let u = 0; u < 9; u++) + o[c] = t.getFloat32(l, !0), c += 1, l += 4; + l += 14; + } + for (let h = 0; h < a; h++) + n[h] = h; + return { + positions: o, + indices: n + }; + } + // readSTL() + static decimateLayerVertices(e, t) { + if (e % t === 0) + return e; + const s = 12, r = Math.round(Math.log((e - 2) / (s - 2)) / Math.log(4)), a = Math.round(Math.log((t - 2) / (s - 2)) / Math.log(4)), n = Math.pow(4, r) * (s - 2) + 2, o = Math.pow(4, a) * (s - 2) + 2; + return n !== e || o !== t ? e : t; + } + // read NIfTI2 format with embedded CIfTI + // this variation very specific to connectome workbench + // https://brainder.org/2015/04/03/the-nifti-2-file-format/ + static async readNII2(e, t = 0, s = "") { + let r = new Float32Array(); + const a = e.byteLength; + let n = !0; + const o = new DataView(e); + let l = o.getUint16(0, n); + if (l === 469893120 && (n = !1, l = o.getUint16(0, n)), l !== 540) + throw new Error("Not a valid NIfTI-2 dataset"); + const c = Number(o.getBigInt64(168, n)), h = o.getFloat64(176, n), u = o.getFloat64(184, n); + (h !== 1 || u !== 0) && R.warn("ignoring scale slope and intercept"); + const d = o.getUint32(504, n), f = o.getUint16(12, n); + if (f !== 2 && f !== 4 && f !== 8 && f !== 16) + throw new Error("Unsupported NIfTI datatype " + f); + let g = 1; + const m = [1, 1, 1, 1, 1, 1, 1, 1]; + for (let p = 1; p < 8; p++) + m[p] = Math.max(Number(o.getBigInt64(16 + p * 8, n)), 1), g *= m[p]; + if (d >= 3e3 && d <= 3099 && c > 580) { + let p = function() { + for (; F < a && E[F] === 10; ) + F++; + const V = F; + for (; F < a && E[F] !== 10; ) + F++; + return F++, F - V < 1 ? "" : new TextDecoder().decode(e.slice(V, F - 1)).trim(); + }, v = function() { + let V = p(); + if (!V.startsWith("<") || V.endsWith(">")) + return V; + for (; F < a && !V.endsWith(">"); ) + V += p(); + return V; + }, A = function(V, N = !1) { + const P = M.indexOf(V); + if (P < 0) + return 1; + const L = M.indexOf('"', P) + 1, q = M.indexOf('"', L), X = M.slice(L, q); + return N ? X : parseInt(X); + }, x = 0, w = 0, D = 0, b = "", C = new Uint32Array(); + const E = new Uint8Array(e); + let F = 552, M; + const S = m[5], T = new Float32Array(t * S); + for (; F < a && (M = v(), !M.includes("")); ) + if (M.includes("") || (M = v()), !M.startsWith("") || !M.endsWith("")) + return R.warn("Unable to find CIfTI "), T; + M = M.slice(15, -16); + const L = M.trim().split(/\s+/); + L.length < w && R.error("Error parsing VertexIndices"), C = new Uint32Array(w); + for (let q = 0; q < w; q++) + C[q] = parseInt(L[q]); + } + if (D === 0 || C.length === 0) + return R.warn("Unable to find CIfTI structure that matches the mesh."), T; + if (f !== 16) + return R.warn("Only able to read float32 CIfTI (only known datatype)."), T; + const k = new Float32Array(w * S), B = c + S * x * 4; + for (let V = 0; V < w * S; V++) + k[V] = o.getFloat32(B + V * 4, n); + let U = 0; + for (let V = 0; V < w; V++) + for (let N = 0; N < S; N++) + T[C[V] + N * t] = k[U], U++; + return R.debug( + "CIfTI diagnostics", + D, + b, + x, + w, + x, + s + ), T; + } + if (g = this.decimateLayerVertices(g, t), g % t !== 0) + throw new Error("Vertices in layer (" + g + ") is not a multiple of number of vertices (" + t + ")"); + if (n) + f === 16 ? r = new Float32Array(e, c, g) : f === 8 ? r = new Int32Array(e, c, g) : f === 4 && (r = new Int16Array(e, c, g)); + else if (f === 16) { + r = new Float32Array(g); + for (let p = 0; p < g; p++) + r[p] = o.getFloat32(c + p * 4, n); + } else if (f === 8) { + r = new Int32Array(g); + for (let p = 0; p < g; p++) + r[p] = o.getInt32(c + p * 4, n); + } else if (f === 4) { + r = new Int16Array(g); + for (let p = 0; p < g; p++) + r[p] = o.getInt16(c + p * 2, n); + } + return f === 2 && (r = new Uint8Array(e, c, g)), r; + } + // readNII2() + // read NIfTI1/2 as vertex colors + // https://brainder.org/2012/09/23/the-nifti-file-format/#:~:text=In%20the%20nifti%20format%2C%20the,seventh%2C%20are%20for%20other%20uses. + static async readNII(e, t, s = "") { + const r = t.length / 3; + let a = new Float32Array(), n = !0, o = new DataView(e), l = o.getUint16(0, n); + if (l === 540 || l === 469893120) + return we.readNII2(e, r, s); + if (l === 23553 && (n = !1, l = o.getUint16(0, n)), l !== 348) { + const w = await j.decompress(new Uint8Array(e)); + if (o = new DataView(w.buffer), e = w.buffer, l = o.getUint16(0, n), l === 540 || l === 469893120) + return we.readNII2(e, r, s); + l === 23553 && (n = !1, l = o.getUint16(0, n)); + } + l !== 348 && R.error("Not a valid NIfTI image."); + const c = o.getFloat32(108, n), h = o.getFloat32(112, n), u = o.getFloat32(116, n), d = o.getUint16(252, n), f = o.getUint16(254, n), g = o.getUint16(70, n); + if (g !== 2 && g !== 4 && g !== 8 && g !== 16) + throw new Error("Unsupported NIfTI datatype " + g); + const m = ie(); + for (let w = 0; w < 12; w++) + m[w] = o.getFloat32(280 + w * 4, n); + let p = 1; + const v = new Array(8); + for (let w = 0; w < 8; w++) + v[w] = o.getUint16(40 + w * 2, n), !(w < 1) && (p *= v[w] || 1); + let A = !1, x = this.decimateLayerVertices(p, r); + if (x % r !== 0) + if (v[0] >= 3 && v[1] > 1 && v[2] > 1 && v[3] > 1) + A = !0, x = v[1] * v[2] * v[3]; + else + throw new Error("Voxels in layer (" + p + ") is not a multiple of number of vertices (" + r + ")"); + if (p = x, n) + g === 16 ? a = new Float32Array(e, c, p) : g === 8 ? a = new Int32Array(e, c, p) : g === 4 && (a = new Int16Array(e, c, p)); + else if (g === 16) { + a = new Float32Array(p); + for (let w = 0; w < p; w++) + a[w] = o.getFloat32(c + w * 4, n); + } else if (g === 8) { + a = new Int32Array(p); + for (let w = 0; w < p; w++) + a[w] = o.getInt32(c + w * 4, n); + } else if (g === 4) { + a = new Int16Array(p); + for (let w = 0; w < p; w++) + a[w] = o.getInt16(c + w * 2, n); + } + if (g === 2 && (a = new Uint8Array(e, c, p)), h !== 1 || u !== 0) { + const w = new Float32Array(p); + for (let D = 0; D < p; D++) + w[D] = a[D] * h + u; + a = w; + } + for (let w = 0; w < p; w++) + isNaN(a[w]) && (a[w] = 0); + if (A) { + let w = function(U, V) { + const N = [0, 0, 0]; + return N[0] = V[0] * U[0] + V[1] * U[1] + V[2] * U[2] + V[3], N[1] = V[4] * U[0] + V[5] * U[1] + V[6] * U[2] + V[7], N[2] = V[8] * U[0] + V[9] * U[1] + V[10] * U[2] + V[11], N; + }; + const D = new Float32Array(p); + R.warn("Sampling voxel intensities at mesh vertices (assumes precise alignment)."), (d > f || f <= 0) && R.warn(`Requires valid sform (sform_code = ${f})`); + const b = ie(); + Re(b, m); + const C = v[1], E = v[2], F = v[3], M = C * E; + let S = 0, T = 0, k = 0; + for (; T < r; ) { + const U = [t[S], t[S + 1], t[S + 2]], V = w(U, b); + S += 3, T += 1; + const N = Math.floor(V[0]), P = Math.floor(V[1]), L = Math.floor(V[2]), q = Math.ceil(V[0]), X = Math.ceil(V[1]), O = Math.ceil(V[2]); + if (N < 0 || q >= C || P < 0 || X >= E || L < 0 || O >= F) + continue; + const Y = N + P * C + L * M, _ = new Float32Array(8); + _[0] = a[Y], _[1] = a[Y + 1], _[2] = a[Y + C], _[3] = a[Y + C + 1], _[4] = a[Y + M], _[5] = a[Y + M + 1], _[6] = a[Y + M + C], _[7] = a[Y + M + C + 1]; + let W = _[0], Q = W; + for (let J = 1; J < 8; J++) + W = Math.max(W, _[J]), Q = Math.min(Q, _[J]); + Math.abs(Q) > W && (W = Q), D[T - 1] = W, k++; + } + const B = k / r; + B < 0.1 && R.warn(`${k} of ${r} vertices in range (${(B * 100).toFixed(1)}%)`), a = D; + } + return a; + } + // readNII(); + // read MGH format as vertex colors (not voxel-based image) + // https://surfer.nmr.mgh.harvard.edu/fswiki/FsTutorial/MghFormat + static async readMGH(e, t = 0, s = !1) { + let r = new DataView(e), a = e; + if (r.getUint8(0) === 31 && r.getUint8(1) === 139) { + const C = await j.decompress(new Uint8Array(e)); + a = new ArrayBuffer(C.byteLength), new Uint8Array(a).set(new Uint8Array(C)), r = new DataView(C.buffer); + } + const n = r.getInt32(0, !1), o = Math.max(1, r.getInt32(4, !1)), l = Math.max(1, r.getInt32(8, !1)), c = Math.max(1, r.getInt32(12, !1)), h = Math.max(1, r.getInt32(16, !1)), u = r.getInt32(20, !1); + let d = 284; + const f = !1; + (n !== 1 || u < 0 || u > 4) && R.warn("Not a valid MGH file"); + let g = o * l * c * h, m = []; + if (g = this.decimateLayerVertices(g, t), g % t !== 0) + return R.warn("Vertices in layer (" + g + ") is not a multiple of number of vertices (" + t + ")"), m; + if (u === 3) { + m = new Float32Array(g); + for (let C = 0; C < g; C++) + m[C] = r.getFloat32(d + C * 4, f); + } else if (u === 1) { + m = new Int32Array(g); + for (let C = 0; C < g; C++) + m[C] = r.getInt32(d + C * 4, f); + } else if (u === 4) { + m = new Int16Array(g); + for (let C = 0; C < g; C++) + m[C] = r.getInt16(d + C * 2, f); + } else u === 0 && (m = new Uint8Array(e, d, g)); + if (!s) + return m; + let p = 4; + u === 4 && (p = 2), u === 0 && (p = 1), d += p * g, d += 16; + const v = 1, A = 2, x = 20, w = 30, D = a.byteLength; + let b; + for (; d < D - 8; ) { + const C = r.getInt32(d += 4, f); + let E = 0; + switch (C) { + case w: + E = r.getInt32(d += 4, f) - 1; + break; + case x: + // these don't take lengths at all + case A: + E = 0; + break; + case v: + E = 0; + { + let F = r.getInt32(d += 4, f); + if (F > 0) + return R.warn("unsupported CTABreadFromBinaryV1"), m; + if (F = -F, F !== 2) + return R.warn("CTABreadFromBinary: unknown version"), m; + const M = r.getInt32(d += 4, f); + if (M < 0) + return R.warn("CTABreadFromBinaryV2: nentries was ", M), m; + const S = r.getInt32(d += 4, f); + d += S; + const T = r.getInt32(d += 4, f); + if (T < 0) + return m; + const k = { R: [], G: [], B: [], A: [], I: [], labels: [] }; + for (let B = 0; B < T; B++) { + const U = r.getInt32(d += 4, f), V = r.getInt32(d += 4, f); + let N = d + 4, P = ""; + for (let Y = 0; Y < V; Y++) { + const _ = r.getUint8(N++); + if (_ === 0) + break; + P += String.fromCharCode(_); + } + d += V; + const L = r.getInt32(d += 4, f), q = r.getInt32(d += 4, f), X = r.getInt32(d += 4, f), O = 255 - r.getInt32(d += 4, f); + k.I.push(U), k.R.push(L), k.G.push(q), k.B.push(X), k.A.push(O), k.labels.push(P); + } + b = oe.makeLabelLut(k); + } + break; + default: + E = r.getInt32(d += 8, f); + } + d += E; + } + return { + scalars: m, + colormapLabel: b + // TODO can we guarantee this? + }; + } + // readMGH() + // read X3D format mesh + // https://en.wikipedia.org/wiki/X3D + static readX3D(e) { + const t = e.byteLength; + if (t < 20) + throw new Error("File too small to be X3D: bytes = " + t); + const s = new Uint8Array(e); + let r = 0; + function a() { + for (; r < t && s[r] !== 60; ) + r++; + const x = r; + for (; r < t && s[r] !== 62; ) + r++; + const w = r; + return new TextDecoder().decode(e.slice(x, w + 1)).trim(); + } + let n = a(); + function o(x) { + const w = n.indexOf(x + "="); + if (w < 0) + return ""; + const D = n[w + x.length + 1], b = n.indexOf(D, w) + 1, C = n.indexOf(D, b); + return n.slice(b, C); + } + function l(x) { + const w = n.indexOf(x + "="); + if (w < 0) + return 1; + const D = n[w + x.length + 1], b = n.indexOf(D, w) + 1, C = n.indexOf(D, b); + let E = n.slice(b, C).trim(); + E = E.replace(/,\s*$/, ""); + const F = E.trim().split(/\s*,\s*|\s+/); + if (F.length < 2) + return parseFloat(E); + let M = new Array(F.length), S = 0; + for (let T = 0; T < F.length; T++) { + const k = parseFloat(F[T]); + isFinite(k) && (M[S] = k, S++); + } + return M = M.slice(0, S), M; + } + n.includes("xml version") || R.warn("Not a X3D image"); + let c = [], h = [], u = [], d = [], f = [0, 0, 0, 0], g = [0, 0, 0, 0], m = [255, 255, 255, 255], p = [255, 255, 255, 255]; + const v = {}; + function A() { + if (!n.endsWith("/>")) + if (n.startsWith("")) + for (; r < t && !n.endsWith(""); ) + n += a(); + else + for (; r < t && !n.endsWith("/>"); ) + n += a(); + const x = o("USE"); + if (x.length > 1) { + x in v ? m = v[x] : R.warn("Unable to find DEF for " + x); + return; + } + const w = l("diffuseColor"); + if (w.length < 3) + return; + m[0] = Math.round(w[0] * 255), m[1] = Math.round(w[1] * 255), m[2] = Math.round(w[2] * 255); + const D = o("DEF"); + D.length < 1 || (v[D] = m); + } + for (; r < t; ) + if (n = a(), m = p.slice(), n.startsWith("= 0 ? (h.push(D[S] + C), h.push(D[E - 1] + C), h.push(D[E - 0] + C), E += 1) : (E += 3, S = E - 2); + } else + for (; E < D.length; ) + D[E] >= 0 ? (h.push(D[E - 2] + C), h.push(D[E - 1] + C), h.push(D[E - 0] + C), E += 1) : E += 3; + c = [...c, ...b]; + const F = Math.floor(b.length / 3), M = Array(F).fill(m).flat(); + if (d.length === F * 3) { + let S = 0, T = 0; + for (let k = 0; k < F; k++) { + for (let B = 0; B < 3; B++) + M[T] = Math.round(d[S] * 255), S++, T++; + T++; + } + } + u = [...u, ...M]; + } else if (w < 0) + xt.makeColoredSphere(c, h, u, x, f, m); + else { + const C = ie(); + Ba(C, g[3], [g[0], g[1], g[2]]); + const E = pe(0, -w * 0.5, 0, 1), F = pe(0, +w * 0.5, 0, 1); + Me(E, E, C), Me(F, F, C), lr(E, E, f), lr(F, F, f); + const M = G(E[0], E[1], E[2]), S = G(F[0], F[1], F[2]); + xt.makeColoredCylinder(c, h, u, M, S, x, m); + } + } + return { + positions: Float32Array.from(c), + indices: Uint32Array.from(h), + rgba255: Uint8Array.from(u) + }; + } + // readX3D() + // read GIfTI format mesh + // https://www.nitrc.org/projects/gifti/ + static async readGII(e, t = 0) { + let s = e.byteLength; + if (s < 20) + throw new Error("File too small to be GII: bytes = " + s); + let r = new TextDecoder("ascii").decode(e); + if (r[0].charCodeAt(0) === 31) { + const T = await j.decompress(new Uint8Array(e)); + e = T.buffer, r = new TextDecoder("ascii").decode(T.buffer); + } + let a = 0; + function n() { + let T = !0, k = a; + for (; T; ) { + for (; a < s && r[a] !== "<"; ) + a++; + for (k = a; a < s && r[a] !== ">"; ) + a++; + if (T = r[a - 1] === "/", k + 1 < s && r[k + 1] === "/" && (a += 1, T = !0), a >= s) + break; + } + const B = new TextDecoder().decode(e.slice(k + 1, a)).trim(), U = B.split(" ")[0].trim(), V = a; + let N = a, P = a; + if (r[k + 1] !== "?" && r[k + 1] !== "!") { + const L = ""; + N = r.indexOf(L, V), P = N + L.length - 1; + } + return { + name: B, + startPos: k, + contentStartPos: V, + contentEndPos: N, + endPos: P + }; + } + let o = n(); + if (!o.name.startsWith("?xml")) + throw new Error("readGII: Invalid XML file"); + for (; !o.name.startsWith("GIFTI") && o.endPos < s; ) + o = n(); + if (!o.name.startsWith("GIFTI") || o.contentStartPos === o.contentEndPos) + throw new Error("readGII: XML file does not include GIFTI tag"); + s = o.contentEndPos; + let l = new Float32Array(), c = new Uint32Array(), h = new Float32Array(), u = "", d = !1, f = !1, g = !1, m = !1, p = [1, 1, 1]; + const v = [0, 0, 0]; + let A = 0, x = !1, w = !1, D = 0, b = !1; + o.endPos = o.contentStartPos; + let C = ""; + function E(T, k = !1) { + const B = C.indexOf(T); + if (B < 0) + return 1; + const U = C.indexOf('"', B) + 1, V = C.indexOf('"', U), N = C.slice(U, V); + return k ? parseFloat(N) : parseInt(N); + } + function F(T) { + const k = C.indexOf(T); + if (k < 0) + return ""; + const B = k + T.length, U = C.indexOf("]", B); + return C.slice(B, U); + } + const M = { R: [], G: [], B: [], A: [], I: [], labels: [] }; + for (; o.endPos < s && o.name.length > 1; ) { + if (o = n(), o.name.startsWith("Label Key") && (C = o.name, M.I.push(E("Key=")), M.R.push(Math.round(255 * E("Red=", !0))), M.G.push(Math.round(255 * E("Green=", !0))), M.B.push(Math.round(255 * E("Blue=", !0))), M.A.push(Math.round(255 * E("Alpha", !0))), C = new TextDecoder().decode(e.slice(o.contentStartPos + 1, o.contentEndPos)).trim(), M.labels.push(F(" "u") { + let k = function(B) { + const U = atob(B), V = U.length, N = new Uint8Array(V); + for (let P = 0; P < V; P++) + N[P] = U.charCodeAt(P); + return N; + }; + if (x) { + const B = k(C.slice()); + T = await j.decompress(new Uint8Array(B)); + } else + T = k(C.slice()); + } else if (x) { + const k = Buffer.from(C.slice(), "base64"); + T = await j.decompress(new Uint8Array(k)); + } else + T = Buffer.from(C.slice(), "base64"); + if (f) { + if (A !== 16 && R.warn("expect positions as FLOAT32"), l = new Float32Array(T.buffer), m) { + const k = l.slice(), B = k.length / 3; + let U = 0; + for (let V = 0; V < B; V++) + for (let N = 0; N < 3; N++) + l[U] = k[N * B + V], U++; + } + } else if (d) { + if (A !== 8 && R.warn("expect indices as INT32"), c = new Uint32Array(T.buffer), m) { + const k = c.slice(), B = k.length / 3; + let U = 0; + for (let V = 0; V < B; V++) + for (let N = 0; N < 3; N++) + c[U] = k[N * B + V], U++; + } + } else { + let k = function(U, V) { + const N = U.length, P = new Float32Array(N + V.length); + return P.set(U), P.set(V, N), P; + }; + D = p[0] * p[1] * p[2], t !== 0 && D % t !== 0 && R.warn("Number of vertices in scalar overlay (" + D + ") does not match mesh (" + t + ")"); + let B; + if (A === 2) { + const U = new Uint8Array(T.buffer); + B = Float32Array.from(U); + } else if (A === 8) { + const U = new Int32Array(T.buffer); + B = Float32Array.from(U); + } else if (A === 16) + B = new Float32Array(T.buffer); + else if (A === 32) { + const U = new Float64Array(T.buffer); + B = Float32Array.from(U); + } else + throw new Error(`Invalid dataType: ${A}`); + h = k(h, B); + } + continue; + } + if (o.name.trim() === "DataSpace" && (C = new TextDecoder().decode(e.slice(o.contentStartPos + 1, o.contentEndPos)).trim(), C.includes("NIFTI_XFORM_SCANNER_ANAT") && (b = !0)), o.name.trim() === "MD" && (C = new TextDecoder().decode(e.slice(o.contentStartPos + 1, o.contentEndPos)).trim(), C.includes("AnatomicalStructurePrimary") && C.includes("CDATA[") && (u = F(" 1 && (M.A.some((k) => k > 0) || M.A.fill(255), S = oe.makeLabelLut(M)), t > 0) + return { scalars: h, colormapLabel: S, anatomicalStructurePrimary: u }; + if (l.length > 2 && !b && (v[0] !== 0 || v[1] !== 0 || v[2] !== 0)) { + D = Math.floor(l.length / 3); + let T = 0; + for (let k = 0; k < D; k++) + l[T] += v[0], T++, l[T] += v[1], T++, l[T] += v[2], T++; + } + return { + positions: l, + indices: c, + scalars: h, + colormapLabel: S, + anatomicalStructurePrimary: u + }; + } + // readGII() +}, Ys = class { + /** + * @param text - The text of the label + * @param style - The style of the label + * @param points - An array of points label for label lines + */ + constructor(i, e, t, s, r) { + I(this, "text"), I(this, "style"), I(this, "points"), I(this, "anchor"), I(this, "onClick"), this.text = i, this.style = e, this.points = t, this.anchor = s || 0, this.onClick = r; + } +}, ws = () => { + const i = new ArrayBuffer(2); + return new DataView(i).setInt16(0, 256, !0), new Int16Array(i)[0] === 256; +}, Yn = /* @__PURE__ */ ((i) => (i[i.UNKNOWN = 0] = "UNKNOWN", i[i.NII = 1] = "NII", i[i.DCM = 2] = "DCM", i[i.DCM_MANIFEST = 3] = "DCM_MANIFEST", i[i.MIH = 4] = "MIH", i[i.MIF = 5] = "MIF", i[i.NHDR = 6] = "NHDR", i[i.NRRD = 7] = "NRRD", i[i.MHD = 8] = "MHD", i[i.MHA = 9] = "MHA", i[i.MGH = 10] = "MGH", i[i.MGZ = 11] = "MGZ", i[i.V = 12] = "V", i[i.V16 = 13] = "V16", i[i.VMR = 14] = "VMR", i[i.HEAD = 15] = "HEAD", i[i.DCM_FOLDER = 16] = "DCM_FOLDER", i[i.SRC = 17] = "SRC", i[i.FIB = 18] = "FIB", i[i.BMP = 19] = "BMP", i[i.ZARR = 20] = "ZARR", i[i.NPY = 21] = "NPY", i[i.NPZ = 22] = "NPZ", i[i.HDR = 23] = "HDR", i))(Yn || {}), $ = Object.freeze({ + ...Yn, + parse: (i) => { + let e = 0; + switch (i.toUpperCase()) { + case "": + case "DCM": + e = 2; + break; + case "TXT": + e = 3; + break; + case "FZ": + case "GQI": + case "QSDR": + case "FIB": + e = 18; + break; + case "HDR": + case "NII": + e = 1; + break; + case "MIH": + e = 4; + break; + case "MIF": + e = 5; + break; + case "NHDR": + e = 6; + break; + case "NRRD": + e = 7; + break; + case "MHD": + e = 8; + break; + case "MHA": + e = 9; + break; + case "MGH": + e = 10; + break; + case "MGZ": + e = 11; + break; + case "NPY": + e = 21; + break; + case "NPZ": + e = 22; + break; + case "SRC": + e = 17; + break; + case "V": + e = 12; + break; + case "V16": + e = 13; + break; + case "VMR": + e = 14; + break; + case "HEAD": + e = 15; + break; + case "PNG": + case "BMP": + case "GIF": + case "JPG": + case "JPEG": + e = 19; + break; + case "ZARR": + e = 20; + break; + } + return e; + } +}), Si = (i, e = "", t = "", s = "gray", r = 1, a = NaN, n = NaN, o = !0, l = 0.02, c = !1, h = !1, u = "", d = 0, f = $.UNKNOWN, g = NaN, m = NaN, p = !0, v = !1, A = null) => ({ + url: i, + urlImageData: e, + name: t, + colormap: s, + colorMap: s, + opacity: r, + cal_min: a, + cal_max: n, + trustCalMinMax: o, + percentileFrac: l, + ignoreZeroVoxels: c, + useQFormNotSForm: h, + colormapNegative: u, + imageType: f, + cal_minNeg: g, + cal_maxNeg: m, + colorbarVisible: p, + frame4D: d, + alphaThreshold: v, + colormapLabel: A +}); +function Sr(i, e = 80) { + i = i.replace(/[`$]/g, ""); + const t = [], s = Math.min(e, i.length); + for (let r = 0; r < s; r++) { + const a = i.charCodeAt(r); + t.push(a & 255); + } + return t; +} +function Vi(i, e = !1, t = !1) { + let a = !0; + t && (a = i.littleEndian); + const n = new Uint8Array(348), o = new DataView(n.buffer); + o.setInt32(0, 348, a), o.setUint8(38, 114), o.setUint8(39, i.dim_info); + for (let c = 0; c < 8; c++) + o.setUint16(40 + 2 * c, i.dims[c], a); + o.setFloat32(56, i.intent_p1, a), o.setFloat32(60, i.intent_p2, a), o.setFloat32(64, i.intent_p3, a), o.setInt16(68, i.intent_code, a), e ? (o.setInt16(70, 2, a), o.setInt16(72, 8, a)) : (o.setInt16(70, i.datatypeCode, a), o.setInt16(72, i.numBitsPerVoxel, a)), o.setInt16(74, i.slice_start, a); + for (let c = 0; c < 8; c++) + o.setFloat32(76 + 4 * c, i.pixDims[c], a); + e ? (o.setFloat32(108, 352, a), o.setFloat32(112, 1, a), o.setFloat32(116, 0, a)) : (o.setFloat32(108, i.vox_offset, a), o.setFloat32(112, i.scl_slope, a), o.setFloat32(116, i.scl_inter, a)), o.setInt16(120, i.slice_end, a), o.setUint8(122, i.slice_code), i.xyzt_units === 0 ? o.setUint8(123, 10) : o.setUint8(123, i.xyzt_units), e ? (o.setFloat32(124, 0, a), o.setFloat32(128, 0, a)) : (o.setFloat32(124, i.cal_max, a), o.setFloat32(128, i.cal_min, a)), o.setFloat32(132, i.slice_duration, a), o.setFloat32(136, i.toffset, a), n.set(Sr(i.description), 148), n.set(Sr(i.aux_file), 228), o.setInt16(252, i.qform_code, a), i.sform_code < 1 || i.sform_code < 1 ? o.setInt16(254, 1, a) : o.setInt16(254, i.sform_code, a), o.setFloat32(256, i.quatern_b, a), o.setFloat32(260, i.quatern_c, a), o.setFloat32(264, i.quatern_d, a), o.setFloat32(268, i.qoffset_x, a), o.setFloat32(272, i.qoffset_y, a), o.setFloat32(276, i.qoffset_z, a); + const l = i.affine.flat(); + for (let c = 0; c < 12; c++) + o.setFloat32(280 + 4 * c, l[c], a); + return o.setInt32(344, 3222382, !0), n; +} +function zc(i, e = !0) { + const t = Math.round(i.length / 3), s = G(0, 0, 0), r = le(), a = le(); + let n = 0, o = 1; + e && (o = 2); + for (let u = 0; u < o; u++) { + n = 0; + for (let f = 0; f < t; f++) { + const g = G(i[f * 3], i[f * 3 + 1], i[f * 3 + 2]); + f === 0 && (nr(r, g), nr(a, g)), Cs(r, r, g), Ds(a, a, g), de(g, g, s); + const m = ei(g); + n = Math.max(n, m); + } + if (u + 1 >= o) + break; + let d = !0; + for (let f = 0; f < 3; ++f) + r[f] > s[f] && (d = !1), a[f] < s[f] && (d = !1); + if (d) + break; + La(s, r, a, 0.5), R.debug("origin moved inside volume: ", s); + } + const l = [r[0], r[1], r[2]], c = [a[0], a[1], a[2]]; + return { min: l, max: c, furthestVertexFromOrigin: n, origin: s }; +} +function Ir(i) { + const e = [!1, !1, !1, !1], t = [!1, !1, !1, !1]; + for (let s = 0; s < 4; s++) + for (let r = 0; r < 4; r++) + if (isNaN(i[s][r])) + return !1; + for (let s = 0; s < 3; s++) + for (let r = 0; r < 3; r++) + i[s][r] !== 0 && (e[s] = !0, t[r] = !0); + for (let s = 0; s < 3; s++) + if (!e[s] || !t[s]) + return !1; + return !0; +} +async function $i(i) { + const e = i.getReader(), { done: t, value: s } = await e.read(); + if (t) + return e.releaseLock(), new ReadableStream({ + start(n) { + n.close(); + } + }); + if (!s || s.length < 2) + return e.releaseLock(), new ReadableStream({ + start(n) { + s && n.enqueue(s), n.close(); + } + }); + const r = s[0] === 31 && s[1] === 139, a = new ReadableStream({ + async start(n) { + try { + for (n.enqueue(s); ; ) { + const { done: o, value: l } = await e.read(); + if (o) { + n.close(), e.releaseLock(); + break; + } + n.enqueue(l); + } + } catch (o) { + n.error(o), e.releaseLock(); + } + } + }); + return r ? a.pipeThrough(new DecompressionStream("gzip")) : a; +} +function _n(i = [256, 256, 256], e = [1, 1, 1], t = [1, 0, 0, -128, 0, 1, 0, -128, 0, 0, 1, -128, 0, 0, 0, 1], s = 2) { + const r = new H(); + r.littleEndian = !0, r.dims = [3, 1, 1, 1, 0, 0, 0, 0], r.dims[0] = Math.max(3, i.length); + for (let n = 0; n < i.length; n++) + r.dims[n + 1] = i[n]; + r.pixDims = [1, 1, 1, 1, 1, 0, 0, 0]; + for (let n = 0; n < i.length; n++) + r.pixDims[n + 1] = e[n]; + if (t.length === 16) { + let n = 0; + for (let o = 0; o < 4; o++) + for (let l = 0; l < 4; l++) + r.affine[o][l] = t[n], n++; + } + let a = 8; + return s === 256 || s === 2 ? a = 8 : s === 512 || s === 4 ? a = 16 : s === 16 || s === 768 || s === 8 || s === 2304 ? a = 32 : s === 64 ? a = 64 : R.warn("Unsupported NIfTI datatypeCode for header creation: " + s), r.datatypeCode = s, r.numBitsPerVoxel = a, r.scl_inter = 0, r.scl_slope = 1, r.sform_code = 2, r.magic = "n+1", r.vox_offset = 352, r; +} +function Gc(i = [256, 256, 256], e = [1, 1, 1], t = [1, 0, 0, -128, 0, 1, 0, -128, 0, 0, 1, -128, 0, 0, 0, 1], s = 2, r = new Uint8Array()) { + const a = _n(i, e, t, s), n = Vi(a, !1); + a.vox_offset = Math.max(352, n.length); + const o = Vi(a, !1); + if (r.length < 1) + return o; + const l = Math.max(0, a.vox_offset - o.length), c = new Uint8Array(l), h = new Uint8Array(r.buffer, r.byteOffset, r.byteLength), u = a.vox_offset + h.length, d = new Uint8Array(u); + return d.set(o, 0), d.set(c, o.length), d.set(h, a.vox_offset), d; +} +function qn(i, e = null) { + if (!i.hdr) + throw new Error("NVImage header is not defined for toUint8Array"); + if (!i.img && e === null) + throw new Error("NVImage image data is not defined for toUint8Array"); + const t = e !== null, s = JSON.parse(JSON.stringify(i.hdr)), r = i.extensions && i.extensions.length > 0, a = new Uint8Array(4); + a[0] = r ? 1 : 0; + let n = new Uint8Array(0); + if (r) { + const p = []; + let v = 0; + for (const x of i.extensions) { + const w = new Uint8Array(x.edata), D = new Uint8Array(8 + w.length), b = new DataView(D.buffer); + b.setInt32(0, x.esize, !0), b.setInt32(4, x.ecode, !0), D.set(w, 8), p.push(D), v += D.length; + } + n = new Uint8Array(v); + let A = 0; + for (const x of p) + n.set(x, A), A += x.length; + } + const o = 348; + s.vox_offset = Math.max(352, o + a.length + n.length), t && (s.datatypeCode = 2, s.numBitsPerVoxel = 8, s.scl_slope = 1, s.scl_inter = 0); + const l = Vi(s, t); + let c; + if (t) { + const p = e, v = i.permRAS; + if (v && (v[0] !== 1 || v[1] !== 2 || v[2] !== 3)) { + R.debug("Reorienting drawing bytes back to native space for saving..."); + const A = i.hdr.dims, x = A[1] * A[2] * A[3], w = i.dimsRAS ? i.dimsRAS[1] * i.dimsRAS[2] * i.dimsRAS[3] : x; + if (p.length !== w) + console.warn( + `Drawing length (${p.length}) does not match expected RAS voxel count (${w}). Cannot reorient drawing reliably.` + ), c = p; + else if (!i.img2RASstep || !i.img2RASstart || !i.dimsRAS) + console.warn( + "Missing RAS transformation info (img2RASstep, img2RASstart, dimsRAS). Cannot reorient drawing reliably." + ), c = p; + else { + const D = i.img2RASstep, b = i.img2RASstart, C = i.dimsRAS, E = new Uint8Array(x); + E.fill(0); + const F = p; + let M = 0; + for (let S = 0; S < C[3]; S++) { + const T = b[2] + S * D[2]; + for (let k = 0; k < C[2]; k++) { + const B = b[1] + k * D[1]; + for (let U = 0; U < C[1]; U++) { + const N = b[0] + U * D[0] + B + T; + N >= 0 && N < x ? E[N] = F[M] : M < F.length && console.warn( + `Calculated native index ${N} is out of bounds [0..${x - 1}] during drawing reorientation.` + ), M++; + } + } + } + c = E; + } + } else + c = p; + } else { + if (!i.img) + throw new Error("NVImage image data is null when trying to save non-drawing."); + c = new Uint8Array(i.img.buffer, i.img.byteOffset, i.img.byteLength); + } + const h = l.length + a.length + n.length, u = Math.max(0, s.vox_offset - h), d = new Uint8Array(u), f = s.vox_offset + c.length, g = new Uint8Array(f); + let m = 0; + return g.set(l, m), m += l.length, g.set(a, m), m += a.length, g.set(n, m), m += n.length, g.set(d, m), m += d.length, g.set(c, s.vox_offset), g; +} +async function Hn(i, e, t = null) { + const s = qn(i, t); + if (e.toLowerCase().endsWith(".gz")) + try { + const a = await j.compress(s, "gzip"); + return new Uint8Array(a); + } catch (a) { + return R.error("Compression failed:", a), R.warn("Returning uncompressed data due to compression error."), s; + } + else + return s; +} +async function Yc(i, e = "", t = null) { + const s = await Hn(i, e, t); + if (!e) + return R.debug("saveToDisk: empty file name, returning data as Uint8Array rather than triggering download"), s; + try { + const r = new Blob([s.buffer], { + type: "application/octet-stream" + // Standard type for binary download + }), a = URL.createObjectURL(r), n = document.createElement("a"); + n.setAttribute("href", a), n.setAttribute("download", e), n.style.visibility = "hidden", document.body.appendChild(n), n.click(), document.body.removeChild(n), setTimeout(() => URL.revokeObjectURL(a), 100); + } catch (r) { + R.error("Failed to trigger download:", r); + } + return s; +} +function Wn(i, e, t, s, r = 0, a = !1) { + if (!i.hdr) + throw new Error("getValue: NVImage header is not defined."); + if (!a && !i.img) + throw new Error("getValue: NVImage image data is not defined."); + if (a && !i.imaginary) + return R.warn("getValue: Attempted to read imaginary data, but none exists."), [0]; + const n = i.hdr.dims[1], o = i.hdr.dims[2], l = i.hdr.dims[3], c = i.permRAS.slice(); + if (c[0] !== 1 || c[1] !== 2 || c[2] !== 3) { + const A = pe(e, t, s, 1); + Me(A, A, i.toRASvox), e = A[0], t = A[1], s = A[2]; + } + e = Math.max(0, Math.min(Math.round(e), n - 1)), t = Math.max(0, Math.min(Math.round(t), o - 1)), s = Math.max(0, Math.min(Math.round(s), l - 1)), r = Math.max(0, r); + let h = e + t * n + s * n * o; + if (i.hdr.datatypeCode === 2304) + return i.img ? (h *= 4, h + 3 >= i.img.length ? (R.warn(`getValue: Calculated index ${h} out of bounds for RGBA data.`), [0]) : [i.img[h], i.img[h + 1], i.img[h + 2], i.img[h + 3]]) : [0]; + if (i.hdr.datatypeCode === 128) + return i.img ? (h *= 3, h + 2 >= i.img.length ? (R.warn(`getValue: Calculated index ${h} out of bounds for RGB data.`), [0]) : [i.img[h], i.img[h + 1], i.img[h + 2]]) : [0]; + const u = n * o * l, d = r * u, f = h + d, g = a ? i.imaginary : i.img; + if (f < 0 || f >= g.length) + return [0]; + const m = g[f], p = isNaN(i.hdr.scl_slope) || i.hdr.scl_slope === 0 ? 1 : i.hdr.scl_slope, v = isNaN(i.hdr.scl_inter) ? 0 : i.hdr.scl_inter; + return [p * m + v]; +} +function _c(i, e, t, s, r = 0, a = !1) { + const n = Wn(i, e, t, s, r, a); + return n.length < 3 ? n[0] : n[0] * 0.2126 + n[1] * 0.7152 + n[2] * 0.0722; +} +function qc(i, e = [-1, 0, 0], t = [0, 0, 0], s = "same") { + const r = [new Uint8Array(), [0, 0, 0]]; + if (!i.hdr || !i.img || !i.dimsRAS || !i.img2RASstep || !i.img2RASstart) + return R.error("getVolumeData: Missing required NVImage properties (hdr, img, dimsRAS, img2RASstep/start)."), r; + if (e = e.slice(0, 3), t = t.slice(0, 3), Math.min(...e) < 0 || Math.min(...t) < 0) + return R.warn("getVolumeData: Invalid start or end coordinates provided."), r; + const a = i.dimsRAS.slice(1, 4); + for (let p = 0; p < 3; p++) + if (e[p] = Math.max(0, Math.min(Math.round(e[p]), a[p] - 1)), t[p] = Math.max(0, Math.min(Math.round(t[p]), a[p] - 1)), t[p] < e[p]) { + const v = t[p]; + t[p] = e[p], e[p] = v; + } + const n = [ + t[0] - e[0] + 1, + t[1] - e[1] + 1, + t[2] - e[2] + 1 + ], o = n[0] * n[1] * n[2]; + if (o <= 0) + return R.warn("getVolumeData: Calculated slab size is zero or negative."), r; + let l = i.img.constructor; + s === "uint8" ? l = Uint8Array : s === "int16" ? l = Int16Array : s === "uint16" ? l = Uint16Array : s === "float32" || s === "scaled" || s === "normalized" || s === "windowed" ? l = Float32Array : s === "float64" ? l = Float64Array : s !== "same" && R.warn(`getVolumeData: Unsupported dataType '${s}'. Using 'same'.`); + let c; + try { + c = new l(o); + } catch (p) { + return R.error(`getVolumeData: Failed to create output array for dataType '${s}'.`, p), r; + } + const h = i.img2RASstep, u = i.img2RASstart, d = i.img; + let f = 0; + for (let p = e[2]; p <= t[2]; p++) { + const v = u[2] + p * h[2]; + for (let A = e[1]; A <= t[1]; A++) { + const x = u[1] + A * h[1]; + for (let w = e[0]; w <= t[0]; w++) { + const b = u[0] + w * h[0] + x + v; + let C = 0; + b >= 0 && b < d.length && (C = d[b]), c[f++] = C; + } + } + } + const g = isNaN(i.hdr.scl_slope) || i.hdr.scl_slope === 0 ? 1 : i.hdr.scl_slope, m = isNaN(i.hdr.scl_inter) ? 0 : i.hdr.scl_inter; + if (s === "scaled" || s === "normalized" || s === "windowed") { + c instanceof Float32Array || (R.warn(`getVolumeData: Converting output to Float32 for scaling type '${s}'.`), c = Float32Array.from(c)); + for (let p = 0; p < c.length; p++) + c[p] = c[p] * g + m; + } + if (s === "normalized" || s === "windowed") { + let p = i.cal_min, v = i.cal_max; + s === "normalized" && (p = i.global_min, v = i.global_max); + const A = v - p, x = A === 0 ? 0 : 1 / A; + for (let w = 0; w < c.length; w++) + c[w] = (c[w] - p) * x, c[w] = Math.max(0, Math.min(c[w], 1)); + } + return [c, n]; +} +function Hc(i, e = [-1, 0, 0], t = [0, 0, 0], s = new Uint8Array()) { + if (!i.hdr || !i.img || !i.dimsRAS || !i.img2RASstep || !i.img2RASstart) { + R.error("setVolumeData: Missing required NVImage properties (hdr, img, dimsRAS, img2RASstep/start)."); + return; + } + if (s.length < 1) { + R.warn("setVolumeData: Input slabData is empty."); + return; + } + if (e = e.slice(0, 3), t = t.slice(0, 3), Math.min(...e) < 0 || Math.min(...t) < 0) { + R.warn("setVolumeData: Invalid start or end coordinates provided."); + return; + } + const r = i.dimsRAS.slice(1, 4); + for (let u = 0; u < 3; u++) + if (e[u] = Math.max(0, Math.min(Math.round(e[u]), r[u] - 1)), t[u] = Math.max(0, Math.min(Math.round(t[u]), r[u] - 1)), t[u] < e[u]) { + const d = t[u]; + t[u] = e[u], e[u] = d; + } + const a = [ + t[0] - e[0] + 1, + t[1] - e[1] + 1, + t[2] - e[2] + 1 + ], n = a[0] * a[1] * a[2]; + if (n <= 0) { + R.warn("setVolumeData: Calculated slab size is zero or negative."); + return; + } + if (s.length < n) { + R.error( + `setVolumeData: Input slabData length (${s.length}) is less than the calculated slab size (${n}).` + ); + return; + } + const o = i.img2RASstep, l = i.img2RASstart, c = i.img; + let h = 0; + for (let u = e[2]; u <= t[2]; u++) { + const d = l[2] + u * o[2]; + for (let f = e[1]; f <= t[1]; f++) { + const g = l[1] + f * o[1]; + for (let m = e[0]; m <= t[0]; m++) { + const v = l[0] + m * o[0] + g + d; + v >= 0 && v < c.length && (c[v] = s[h]), h++; + } + } + } +} +var Kn = {}; +Yi(Kn, { + isFreeSurferLabelImage: () => jn, + optimizeFreeSurferLabels: () => Xn, + readMgh: () => Wc +}); +function Br(i, e, t, s = 1) { + const r = e + t; + let a = e; + const n = []; + for (; a + 12 <= r; ) { + const o = i.getInt32(a, !1), l = i.getInt32(a + 8, !1); + if (a += 12, l <= 0 || a + l > r) + break; + if (o !== s) { + a += l; + continue; + } + let c = l, h = a; + if (s === 1) { + if (a + 4 > r) + break; + c = i.getInt32(a, !1), h += 4; + } + if (c > 1 && h + c <= r) { + const u = new Uint8Array(i.buffer, h, c), d = new TextDecoder("utf-8").decode(u.slice(0, -1)); + n.push(d); + } + a += l; + } + return n.join(` + +`); +} +function Xn(i, e) { + if (i.intent_code = 1002, i.datatypeCode !== 16 && i.datatypeCode !== 8) + return e; + let t = new Float32Array(e); + if (i.datatypeCode === 8 && (t = new Int32Array(e)), ws()) { + const n = new Uint32Array(e); + for (let o = 0; o < n.length; o++) { + const l = n[o]; + n[o] = (l & 255) << 24 | (l & 65280) << 8 | (l & 16711680) >>> 8 | (l & 4278190080) >>> 24; + } + } + i.littleEndian = ws(); + let s = !0, r = 1 / 0, a = -1 / 0; + for (let n = 0; n < t.length; n++) { + const o = t[n]; + Number.isFinite(o) && (Number.isInteger(o) || (s = !1), o < r && (r = o), o > a && (a = o)); + } + if (!s || r < 0 || a > 2147483647) + return R.warn(`FreeSurfer Labels must be integers in INT32 range. range ${r}..${a}`), e; + if (a > 32767) { + i.datatypeCode = 8; + const n = new Int32Array(t.length); + for (let o = 0; o < t.length; o++) + n[o] = Math.trunc(t[o]); + return n.buffer; + } else if (a > 255) { + i.datatypeCode = 4, i.numBitsPerVoxel = 16; + const n = new Int16Array(t.length); + for (let o = 0; o < t.length; o++) + n[o] = Math.trunc(t[o]); + return n.buffer; + } else { + i.datatypeCode = 2, i.numBitsPerVoxel = 8; + const n = new Uint8Array(t.length); + for (let o = 0; o < t.length; o++) + n[o] = Math.trunc(t[o]); + return n.buffer; + } +} +function jn(i, e, t) { + const s = i.byteLength - e.vox_offset; + if (s < t) + return R.error(`MGH image data size mismatch: expected ${t}, found ${s}`), !1; + if (s === t) + return !1; + const r = e.vox_offset + t + 20, a = i.byteLength - r; + return a <= 12 ? !1 : Br(new DataView(i), r, a).toLowerCase().endsWith("lut.txt") ? !0 : Br(new DataView(i), r, a, 3).includes("mri_label2vol"); +} +async function Wc(i, e) { + i.hdr || (R.debug("readMgh called before nvImage.hdr was initialized. Creating default."), i.hdr = new H()); + const t = i.hdr; + t.littleEndian = !1; + let s = e, r = new DataView(s); + if (s.byteLength >= 2 && r.getUint8(0) === 31 && r.getUint8(1) === 139) + try { + s = await j.decompressToBuffer(new Uint8Array(e)), r = new DataView(s); + } catch (N) { + return R.error("Failed to decompress MGZ file.", N), null; + } + if (s.byteLength < 284) + return R.error("File too small to be a valid MGH/MGZ header."), null; + const a = r.getInt32(0, !1), n = r.getInt32(4, !1), o = r.getInt32(8, !1), l = r.getInt32(12, !1), c = r.getInt32(16, !1), h = r.getInt32(20, !1), u = r.getFloat32(30, !1), d = r.getFloat32(34, !1), f = r.getFloat32(38, !1), g = r.getFloat32(42, !1), m = r.getFloat32(46, !1), p = r.getFloat32(50, !1), v = r.getFloat32(54, !1), A = r.getFloat32(58, !1), x = r.getFloat32(62, !1), w = r.getFloat32(66, !1), D = r.getFloat32(70, !1), b = r.getFloat32(74, !1), C = r.getFloat32(78, !1), E = r.getFloat32(82, !1), F = r.getFloat32(86, !1); + if (a !== 1 && R.warn(`Unexpected MGH version: ${a}.`), n <= 0 || o <= 0 || l <= 0) + return R.error(`Invalid MGH dimensions: ${n}x${o}x${l}`), null; + switch (h) { + case 0: + t.numBitsPerVoxel = 8, t.datatypeCode = 2; + break; + case 4: + t.numBitsPerVoxel = 16, t.datatypeCode = 4; + break; + case 1: + t.numBitsPerVoxel = 32, t.datatypeCode = 8; + break; + case 3: + t.numBitsPerVoxel = 32, t.datatypeCode = 16; + break; + default: + return R.error(`Unsupported MGH data type: ${h}`), null; + } + t.dims[1] = n, t.dims[2] = o, t.dims[3] = l, t.dims[4] = Math.max(1, c), t.dims[0] = t.dims[4] > 1 ? 4 : 3, t.pixDims[1] = Math.abs(u), t.pixDims[2] = Math.abs(d), t.pixDims[3] = Math.abs(f), t.pixDims[4] = 0, t.sform_code = 1, t.qform_code = 0, t.sform_code = 1; + const M = Le( + g * t.pixDims[1], + v * t.pixDims[2], + w * t.pixDims[3], + 0, + m * t.pixDims[1], + A * t.pixDims[2], + D * t.pixDims[3], + 0, + p * t.pixDims[1], + x * t.pixDims[2], + b * t.pixDims[3], + 0, + 0, + 0, + 0, + 1 + ), S = [t.dims[1] / 2, t.dims[2] / 2, t.dims[3] / 2, 1], T = [0, 0, 0, 0]; + for (let N = 0; N < 3; N++) { + T[N] = 0; + for (let P = 0; P < 3; P++) + T[N] = T[N] + M[P + N * 4] * S[P]; + } + t.affine = [ + [M[0], M[1], M[2], C - T[0]], + [M[4], M[5], M[6], E - T[1]], + [M[8], M[9], M[10], F - T[2]], + [0, 0, 0, 1] + ], t.vox_offset = 284, t.magic = "n+1"; + const k = t.numBitsPerVoxel / 8, U = n * o * l * t.dims[4] * k, V = s.slice(t.vox_offset, t.vox_offset + U); + return jn(s, t, U) ? Xn(t, V) : V; +} +var Zn = {}; +Yi(Zn, { + readNifti: () => Kc +}); +async function Kc(i, e, t) { + let s = e, r = null; + try { + if (Ss(s) && (R.debug(`Decompressing NIfTI file: ${i.name}`), s = await Co(s), R.debug(`Decompression complete for: ${i.name}`)), !s || s.byteLength === 0) + throw new Error("Buffer became invalid after decompression attempt."); + if (i.hdr = await Ht(s, t != null), Eo(i.hdr) && (i.extensions = i.hdr.extensions), i.hdr === null) + throw new Error(`Failed to read NIfTI header: ${i.name}`); + if (i.hdr.cal_min === 0 && i.hdr.cal_max === 255 && i.hdr.datatypeCode !== 2 && (R.debug(`Resetting suspicious cal_min/max (0/255) for non-uint8 NIfTI: ${i.name}`), i.hdr.cal_min = 0, i.hdr.cal_max = 0), r = t ? gr(i.hdr, t) : gr(i.hdr, s), r === null) + throw new Error(`nifti-reader-js readImage returned null for ${i.name}`); + return r; + } catch (a) { + return R.error(`Error processing NIfTI file ${i.name}:`, a), i.hdr = null, null; + } +} +var Qn = {}; +Yi(Qn, { + readNrrd: () => Xc +}); +async function Xc(i, e, t = null) { + i.hdr || (R.warn("readNrrd called before nvImage.hdr was initialized. Creating default."), i.hdr = new H()); + const s = i.hdr; + s.pixDims = [1, 1, 1, 1, 1, 0, 0, 0]; + const r = e.byteLength; + let a = null; + const n = new Uint8Array(e); + for (let b = 1; b < r; b++) + if (n[b - 1] === 10 && n[b] === 10) { + const C = e.slice(0, b - 1); + a = new TextDecoder().decode(C), s.vox_offset = b + 1; + break; + } + if (a === null) + return R.error("readNrrd: could not extract txt"), null; + const o = a.split(` +`); + if (!o[0].startsWith("NRRD")) + return R.error("Invalid NRRD image (magic signature missing)"), null; + const l = o.length; + let c = !1, h = !1, u = !1; + const d = rt(NaN, 0, 0, 0, 1, 0, 0, 0, 1), f = G(0, 0, 0); + let g = ms(); + for (let b = 1; b < l; b++) { + let C = o[b]; + if ((C.length === 0 || C[0] === "#") && (C.startsWith("#") || C.trim().length === 0)) + continue; + C = C.toLowerCase(); + const E = C.split(":"); + if (E.length < 2) + continue; + const F = E[0].trim(); + let M = E[1].trim(); + switch (M = M.replaceAll(")", " "), M = M.replaceAll("(", " "), M = M.trim(), F) { + case "data file": + u = !0; + break; + case "encoding": + if (M.includes("raw")) + c = !1; + else if (M.includes("gz")) + c = !0; + else + return R.error("Unsupported NRRD encoding"), null; + break; + case "type": + switch (M) { + case "uchar": + case "unsigned char": + case "uint8": + case "uint8_t": + s.numBitsPerVoxel = 8, s.datatypeCode = 2; + break; + case "signed char": + case "int8": + case "int8_t": + s.numBitsPerVoxel = 8, s.datatypeCode = 256; + break; + case "short": + case "short int": + case "signed short": + case "signed short int": + case "int16": + case "int16_t": + s.numBitsPerVoxel = 16, s.datatypeCode = 4; + break; + case "ushort": + case "unsigned short": + case "unsigned short int": + case "uint16": + case "uint16_t": + s.numBitsPerVoxel = 16, s.datatypeCode = 512; + break; + case "int": + case "signed int": + case "int32": + case "int32_t": + s.numBitsPerVoxel = 32, s.datatypeCode = 8; + break; + case "uint": + case "unsigned int": + case "uint32": + case "uint32_t": + s.numBitsPerVoxel = 32, s.datatypeCode = 768; + break; + case "float": + s.numBitsPerVoxel = 32, s.datatypeCode = 16; + break; + case "double": + s.numBitsPerVoxel = 64, s.datatypeCode = 64; + break; + default: + return R.error("Unsupported NRRD data type: " + M), null; + } + break; + case "spacings": + { + const S = M.split(/[ ,]+/); + for (let T = 0; T < S.length; T++) + s.pixDims[T + 1] = parseFloat(S[T]); + } + break; + case "sizes": + { + const S = M.split(/[ ,]+/); + s.dims[0] = S.length; + for (let T = 0; T < S.length; T++) + s.dims[T + 1] = parseInt(S[T]); + } + break; + case "endian": + M.includes("little") ? s.littleEndian = !0 : M.includes("big") && (s.littleEndian = !1); + break; + case "space directions": + { + const S = M.split(/[ ,]+/); + if (S.length === 9) + for (let T = 0; T < 9; T++) + d[T] = parseFloat(S[T]); + } + break; + case "space origin": + { + const S = M.split(/[ ,]+/); + S.length === 3 && (f[0] = parseFloat(S[0]), f[1] = parseFloat(S[1]), f[2] = parseFloat(S[2])); + } + break; + case "space units": + M.includes("microns") && (h = !0); + break; + case "space": + M.includes("right-anterior-superior") || M.includes("ras") ? g = rt(1, 0, 0, 0, 1, 0, 0, 0, 1) : M.includes("left-anterior-superior") || M.includes("las") ? g = rt(-1, 0, 0, 0, 1, 0, 0, 0, 1) : M.includes("left-posterior-superior") || M.includes("lps") ? g = rt(-1, 0, 0, 0, -1, 0, 0, 0, 1) : R.warn("Unsupported NRRD space value:", M); + break; + default: + R.warn("Unknown:", F); + break; + } + } + if (!isNaN(d[0])) { + s.sform_code = 2, h && (Ra(d, d, 1e-3), f[0] *= 1e-3, f[1] *= 1e-3, f[2] *= 1e-3), g[0] < 0 && (f[0] = -f[0]), g[4] < 0 && (f[1] = -f[1]), g[8] < 0 && (f[2] = -f[2]), gn(d, g, d); + const b = Le( + d[0], + d[3], + d[6], + f[0], + d[1], + d[4], + d[7], + f[1], + d[2], + d[5], + d[8], + f[2], + 0, + 0, + 0, + 1 + ); + if (!i.vox2mm) + return null; + const C = i.vox2mm([0, 0, 0], b), E = i.vox2mm([1, 0, 0], b); + de(E, E, C); + const F = i.vox2mm([0, 1, 0], b); + de(F, F, C); + const M = i.vox2mm([0, 0, 1], b); + de(M, M, C), s.pixDims[1] = ot(E), s.pixDims[2] = ot(F), s.pixDims[3] = ot(M), s.affine = [ + [b[0], b[1], b[2], b[3]], + [b[4], b[5], b[6], b[7]], + [b[8], b[9], b[10], b[11]], + [0, 0, 0, 1] + ]; + } + let m = null; + const p = u ? t : e, v = u ? 0 : s.vox_offset; + if (u && !p) + return R.warn("Missing data: NRRD header describes detached data file but only one URL provided"), null; + if (!p || v >= p.byteLength) + return R.error(`NRRD data offset (${v}) invalid for buffer length (${p?.byteLength ?? 0})`), null; + let A = p.slice(v); + if (c) + try { + R.debug("Decompressing NRRD data..."), A = await j.decompressToBuffer(new Uint8Array(A)), R.debug("Decompression complete."); + } catch (b) { + return R.error("Failed to decompress NRRD data.", b), null; + } + const x = s.numBitsPerVoxel / 8, D = s.dims.slice(1, s.dims[0] + 1).reduce((b, C) => b * Math.max(1, C), 1) * x; + return A.byteLength < D ? (R.error(`NRRD image data size mismatch: expected ${D}, found ${A.byteLength}`), null) : (A.byteLength > D && (R.warn(`NRRD has extra ${A.byteLength - D} bytes after expected image data. Truncating.`), A = A.slice(0, D)), m = A, s.datatypeCode ? s.numBitsPerVoxel ? m : (R.error("NRRD parsing failed to set numBitsPerVoxel."), null) : (R.error("NRRD parsing failed to set datatypeCode."), null)); +} +var Se = class Ii { + constructor(e = null, t = "", s = "gray", r = 1, a = null, n = NaN, o = NaN, l = !0, c = 0.02, h = !1, u = !1, d = "", f = 0, g = $.UNKNOWN, m = NaN, p = NaN, v = !0, A = null, x = 0) { + I(this, "name"), I(this, "id"), I(this, "url"), I(this, "headers"), I(this, "_colormap"), I(this, "_opacity"), I(this, "percentileFrac"), I(this, "ignoreZeroVoxels"), I(this, "trustCalMinMax"), I(this, "colormapNegative"), I(this, "colormapLabel"), I(this, "colormapInvert"), I(this, "nFrame4D"), I(this, "frame4D"), I(this, "nTotalFrame4D"), I(this, "cal_minNeg"), I(this, "cal_maxNeg"), I(this, "colorbarVisible", !0), I(this, "modulationImage", null), I(this, "modulateAlpha", 0), I(this, "series", []), I(this, "nVox3D"), I(this, "oblique_angle"), I(this, "maxShearDeg"), I(this, "useQFormNotSForm"), I(this, "colormapType"), I(this, "pixDims"), I(this, "matRAS"), I(this, "pixDimsRAS"), I(this, "obliqueRAS"), I(this, "dimsRAS"), I(this, "permRAS"), I(this, "img2RASstep"), I(this, "img2RASstart"), I(this, "toRAS"), I(this, "toRASvox"), I(this, "frac2mm"), I(this, "frac2mmOrtho"), I(this, "extentsMinOrtho"), I(this, "extentsMaxOrtho"), I(this, "mm2ortho"), I(this, "hdr", null), I(this, "extensions"), I(this, "imageType"), I(this, "img"), I(this, "imaginary"), I(this, "v1"), I(this, "fileObject"), I(this, "dims"), I(this, "onColormapChange", () => { + }), I(this, "onOpacityChange", () => { + }), I(this, "mm000"), I(this, "mm100"), I(this, "mm010"), I(this, "mm001"), I(this, "cal_min"), I(this, "cal_max"), I(this, "robust_min"), I(this, "robust_max"), I(this, "global_min"), I(this, "global_max"), I(this, "urlImgData"), I(this, "isManifest"), I(this, "limitFrames4D"), this.init( + e, + t, + s, + r, + a, + n, + o, + l, + c, + h, + u, + d, + f, + g, + m, + p, + v, + A, + x + ); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + init(e = null, t = "", s = "", r = 1, a = null, n = NaN, o = NaN, l = !0, c = 0.02, h = !1, u = !1, d = "", f = 0, g = $.UNKNOWN, m = NaN, p = NaN, v = !0, A = null, x = 0, w = null) { + const D = s === ""; + if (D && (s = "gray"), this.name = t, this.imageType = g, this.id = gs(), this._colormap = s, this._opacity = r > 1 ? 1 : r, this.percentileFrac = c, this.ignoreZeroVoxels = h, this.trustCalMinMax = l, this.colormapNegative = d, this.colormapLabel = A, this.frame4D = f, this.cal_minNeg = m, this.cal_maxNeg = p, this.colorbarVisible = v, this.colormapType = x, this.useQFormNotSForm = u, !e) + return; + if (D && this.hdr && this.hdr.intent_code === 1002 && (s = "random", this._colormap = s), this.hdr && typeof this.hdr.magic == "number" && (this.hdr.magic = "n+1"), this.nFrame4D = 1, this.hdr) + for (let F = 4; F < 7; F++) + this.hdr.dims[F] > 1 && (this.nFrame4D *= this.hdr.dims[F]); + if (this.frame4D = Math.min(this.frame4D, this.nFrame4D - 1), this.nTotalFrame4D = this.nFrame4D, !this.hdr || !w) + return; + this.nVox3D = this.hdr.dims[1] * this.hdr.dims[2] * this.hdr.dims[3]; + const b = this.nVox3D * (this.hdr.numBitsPerVoxel / 8), C = w.byteLength / b; + C !== this.nFrame4D && (C > 0 && C * b === w.byteLength ? R.debug("Loading the first " + C + " of " + this.nFrame4D + " volumes") : R.warn("This header does not match voxel data", this.hdr, w.byteLength), this.nFrame4D = C), (this.hdr.intent_code === 1007 || this.hdr.intent_code === 2003) && this.nFrame4D === 3 && this.hdr.datatypeCode === 16 && (w = this.float32V1asRGBA(new Float32Array(w)).buffer), (this.hdr.pixDims[1] === 0 || this.hdr.pixDims[2] === 0 || this.hdr.pixDims[3] === 0) && R.error("pixDims not plausible", this.hdr), (isNaN(this.hdr.scl_slope) || this.hdr.scl_slope === 0) && (this.hdr.scl_slope = 1), isNaN(this.hdr.scl_inter) && (this.hdr.scl_inter = 0); + let E = Ir(this.hdr.affine); + if (u || !E || this.hdr.qform_code > this.hdr.sform_code) { + R.debug("spatial transform based on QForm"); + const F = this.hdr.quatern_b, M = this.hdr.quatern_c, S = this.hdr.quatern_d, T = Math.sqrt(1 - (Math.pow(F, 2) + Math.pow(M, 2) + Math.pow(S, 2))), k = this.hdr.pixDims[0] === 0 ? 1 : this.hdr.pixDims[0], B = [ + [T * T + F * F - M * M - S * S, 2 * F * M - 2 * T * S, 2 * F * S + 2 * T * M], + [2 * F * M + 2 * T * S, T * T + M * M - F * F - S * S, 2 * M * S - 2 * T * F], + [2 * F * S - 2 * T * M, 2 * M * S + 2 * T * F, T * T + S * S - M * M - F * F] + ], U = this.hdr.affine; + for (let V = 0; V < 3; V += 1) + for (let N = 0; N < 3; N += 1) + U[V][N] = B[V][N] * this.hdr.pixDims[N + 1], N === 2 && (U[V][N] *= k); + U[0][3] = this.hdr.qoffset_x, U[1][3] = this.hdr.qoffset_y, U[2][3] = this.hdr.qoffset_z, this.hdr.affine = U; + } + if (E = Ir(this.hdr.affine), !E) { + R.debug("Defective NIfTI: spatial transform does not make sense"); + let F = this.hdr.pixDims[1], M = this.hdr.pixDims[2], S = this.hdr.pixDims[3]; + (isNaN(F) || F === 0) && (F = 1), (isNaN(M) || M === 0) && (M = 1), (isNaN(S) || S === 0) && (S = 1), this.hdr.pixDims[1] = F, this.hdr.pixDims[2] = M, this.hdr.pixDims[3] = S; + const T = [ + [F, 0, 0, 0], + [0, M, 0, 0], + [0, 0, S, 0], + [0, 0, 0, 1] + ]; + this.hdr.affine = T; + } + if (this.hdr.datatypeCode !== 128 && this.hdr.datatypeCode !== 2304 && this.hdr.littleEndian !== ws() && this.hdr.numBitsPerVoxel > 8) { + if (this.hdr.numBitsPerVoxel === 16) { + const F = new Uint16Array(w); + for (let M = 0; M < F.length; M++) { + const S = F[M]; + F[M] = ((S & 255) << 8 | S >> 8 & 255) << 16 >> 16; + } + } else if (this.hdr.numBitsPerVoxel === 32) { + const F = new Uint32Array(w); + for (let M = 0; M < F.length; M++) { + const S = F[M]; + F[M] = (S & 255) << 24 | (S & 65280) << 8 | S >> 8 & 65280 | S >> 24 & 255; + } + } else if (this.hdr.numBitsPerVoxel === 64) { + const F = this.hdr.numBitsPerVoxel / 8, M = new Uint8Array(w); + for (let S = 0; S < M.length; S += F) { + let T = F - 1; + for (let k = 0; k < T; k++) { + const B = M[S + k]; + M[S + k] = M[S + T], M[S + T] = B, T--; + } + } + } + } + switch (this.hdr.datatypeCode) { + case 2: + this.img = new Uint8Array(w); + break; + case 4: + this.img = new Int16Array(w); + break; + case 16: + this.img = new Float32Array(w); + break; + case 64: + this.img = new Float64Array(w); + break; + case 128: + this.img = new Uint8Array(w); + break; + case 512: + this.img = new Uint16Array(w); + break; + case 2304: + this.img = new Uint8Array(w); + break; + case 256: { + const F = new Int8Array(w), M = F.length; + this.img = new Int16Array(M); + for (let S = 0; S < M; S++) + this.img[S] = F[S]; + this.hdr.datatypeCode = 4, this.hdr.numBitsPerVoxel = 16; + break; + } + case 1: { + const F = this.hdr.dims[1] * this.hdr.dims[2] * Math.max(1, this.hdr.dims[3]) * Math.max(1, this.hdr.dims[4]), M = new Uint8Array(w); + this.img = new Uint8Array(F); + const S = new Uint8Array(8); + for (let k = 0; k < 8; k++) + S[k] = Math.pow(2, k); + let T = -1; + for (let k = 0; k < F; k++) { + const B = k % 8; + B === 0 && T++, (M[T] & S[B]) !== 0 && (this.img[k] = 1); + } + this.hdr.datatypeCode = 2, this.hdr.numBitsPerVoxel = 8; + break; + } + case 768: { + const F = new Uint32Array(w), M = F.length; + this.img = new Float64Array(M); + for (let S = 0; S < M - 1; S++) + this.img[S] = F[S]; + this.hdr.datatypeCode = 64; + break; + } + case 8: { + const F = new Int32Array(w), M = F.length; + this.img = new Float64Array(M); + for (let S = 0; S < M - 1; S++) + this.img[S] = F[S]; + this.hdr.datatypeCode = 64; + break; + } + case 1024: { + const F = new BigInt64Array(w), M = F.length; + this.img = new Float64Array(M); + for (let S = 0; S < M - 1; S++) + this.img[S] = Number(F[S]); + this.hdr.datatypeCode = 64; + break; + } + case 32: { + const F = new Float32Array(w), M = Math.floor(F.length / 2); + this.imaginary = new Float32Array(M), this.img = new Float32Array(M); + let S = 0; + for (let T = 0; T < M - 1; T++) + this.img[T] = F[S], this.imaginary[T] = F[S + 1], S += 2; + this.hdr.datatypeCode = 16; + break; + } + default: + throw new Error("datatype " + this.hdr.datatypeCode + " not supported"); + } + this.calculateRAS(), isNaN(n) || (this.hdr.cal_min = n), isNaN(o) || (this.hdr.cal_max = o), this.calMinMax(); + } + static async new(e = null, t = "", s = "", r = 1, a = null, n = NaN, o = NaN, l = !0, c = 0.02, h = !1, u = !1, d = "", f = 0, g = $.UNKNOWN, m = NaN, p = NaN, v = !0, A = null, x = 0, w) { + const D = new Ii(), b = /(?:\.([^.]+))?$/; + let C = b.exec(t)[1] || ""; + C = C.toUpperCase(), C === "GZ" && (C = b.exec(t.slice(0, -3))[1], C = C.toUpperCase()); + let E = null; + if (g === $.UNKNOWN && (g = $.parse(C)), e instanceof ArrayBuffer && e.byteLength >= 2 && g === $.DCM) { + const F = new Uint8Array(e); + (F[0] === 92 && F[1] === 1 || F[1] === 92 && F[0] === 1) && (g = $.NII); + } + switch (D.imageType = g, g) { + case $.DCM_FOLDER: + case $.DCM_MANIFEST: + case $.DCM: + return; + case $.FIB: + [E, D.v1] = await D.readFIB(e); + break; + case $.MIH: + case $.MIF: + E = await D.readMIF(e, a); + break; + case $.NHDR: + case $.NRRD: + if (E = await Qn.readNrrd(D, e), E === null) + throw new Error(`Failed to parse NHDR/NRRD file ${t}`); + break; + case $.MHD: + case $.MHA: + E = await D.readMHA(e, a); + break; + case $.MGH: + case $.MGZ: + if (E = await Kn.readMgh(D, e), E === null) + throw new Error(`Failed to parse MGH/MGZ file ${t}`); + break; + case $.SRC: + E = await D.readSRC(e); + break; + case $.V: + E = D.readECAT(e); + break; + case $.V16: + E = D.readV16(e); + break; + case $.VMR: + E = D.readVMR(e); + break; + case $.HEAD: + E = await D.readHEAD(e, a); + break; + case $.BMP: + E = await D.readBMP(e); + break; + case $.NPY: + E = await D.readNPY(e); + break; + case $.NPZ: + E = await D.readNPZ(e); + break; + case $.ZARR: + E = await D.readZARR(e, w); + break; + case $.NII: + if (E = await Zn.readNifti(D, e, a), E === null) + throw new Error(`Failed to parse NIfTI file ${t}.`); + break; + default: + throw new Error("Image type not supported"); + } + return D.init( + e, + t, + s, + r, + a, + n, + o, + l, + c, + h, + u, + d, + f, + g, + m, + p, + v, + A, + x, + E + ), D; + } + // not included in public docs + // detect difference between voxel grid and world space + // https://github.com/afni/afni/blob/25e77d564f2c67ff480fa99a7b8e48ec2d9a89fc/src/thd_coords.c#L717 + computeObliqueAngle(e) { + const t = xe(e); + Ne(t, e); + const s = Math.sqrt(t[0] * t[0] + t[1] * t[1] + t[2] * t[2]), r = Math.max(Math.max(Math.abs(t[0]), Math.abs(t[1])), Math.abs(t[2])) / s, a = Math.sqrt(t[4] * t[4] + t[5] * t[5] + t[6] * t[6]), n = Math.max(Math.max(Math.abs(t[4]), Math.abs(t[5])), Math.abs(t[6])) / a, o = Math.sqrt(t[8] * t[8] + t[9] * t[9] + t[10] * t[10]), l = Math.max(Math.max(Math.abs(t[8]), Math.abs(t[9])), Math.abs(t[10])) / o, c = Math.min(Math.min(r, n), l); + let h = Math.abs(Math.acos(c) * 180 / 3.141592653); + return h > 0.01 ? R.warn("Warning voxels not aligned with world space: " + h + ` degrees from plumb. +`) : h = 0, h; + } + float32V1asRGBA(e) { + e.length !== this.nVox3D * 3 && R.warn("float32V1asRGBA() expects " + this.nVox3D * 3 + "voxels, got ", +e.length); + const t = e.slice(); + this.hdr.datatypeCode = 2304, this.nFrame4D = 1; + for (let l = 4; l < 7; l++) + this.hdr.dims[l] = 1; + this.hdr.dims[0] = 3; + const s = new Uint8Array(this.nVox3D * 4); + let r = 1; + for (let l = 0; l < this.nVox3D * 3; l++) + isNaN(t[l]) || (r = Math.max(r, Math.abs(t[l]))); + const a = 255 / r, n = this.nVox3D * 2; + let o = 0; + for (let l = 0; l < this.nVox3D; l++) { + const c = t[l], h = t[l + this.nVox3D], u = t[l + n]; + s[o] = Math.abs(c * a), s[o + 1] = Math.abs(h * a), s[o + 2] = Math.abs(u * a); + const d = +(c > 0) * 1, f = +(h > 0) * 2, g = +(u > 0) * 4; + let m = 248 + d + f + g; + Math.abs(c) + Math.abs(h) + Math.abs(u) < 0.1 && (m = 0), s[o + 3] = m, o += 4; + } + return s; + } + loadImgV1(e = !1, t = !1, s = !1) { + let r = this.v1; + if (!r && this.nFrame4D === 3 && this.img.constructor === Float32Array && (r = this.img.slice()), !r) + return R.warn("Image does not have V1 data"), !1; + if (e) + for (let a = 0; a < this.nVox3D; a++) + r[a] = -r[a]; + if (t) + for (let a = this.nVox3D; a < 2 * this.nVox3D; a++) + r[a] = -r[a]; + if (s) + for (let a = 2 * this.nVox3D; a < 3 * this.nVox3D; a++) + r[a] = -r[a]; + return this.img = this.float32V1asRGBA(r), !0; + } + // not included in public docs + // detect difference between voxel grid and world space + calculateOblique() { + if (!this.matRAS) + throw new Error("matRAS not defined"); + if (this.pixDimsRAS === void 0) + throw new Error("pixDimsRAS not defined"); + if (!this.dimsRAS) + throw new Error("dimsRAS not defined"); + this.oblique_angle = this.computeObliqueAngle(this.matRAS); + const e = this.vox2mm([0, 0, 0], this.matRAS), t = this.vox2mm([1 / this.pixDimsRAS[1], 0, 0], this.matRAS), s = this.vox2mm([0, 1 / this.pixDimsRAS[2], 0], this.matRAS), r = this.vox2mm([0, 0, 1 / this.pixDimsRAS[3]], this.matRAS); + de(t, t, e), de(s, s, e), de(r, r, e); + const a = Le( + t[0], + t[1], + t[2], + 0, + s[0], + s[1], + s[2], + 0, + r[0], + r[1], + r[2], + 0, + 0, + 0, + 0, + 1 + ); + this.obliqueRAS = xe(a); + const n = Math.abs(90 - Ki(t, s) * (180 / Math.PI)), o = Math.abs(90 - Ki(t, r) * (180 / Math.PI)), l = Math.abs(90 - Ki(s, r) * (180 / Math.PI)); + this.maxShearDeg = Math.max(Math.max(n, o), l), this.maxShearDeg > 0.1 && R.warn("Warning: voxels are rhomboidal, maximum shear is %f degrees.", this.maxShearDeg); + const c = pe(this.dimsRAS[1], this.dimsRAS[2], this.dimsRAS[3], 1), h = xe(this.matRAS); + Ne(h, h); + const u = pe(-0.5, -0.5, -0.5, 0); + St(h, h, G(u[0], u[1], u[2])), h[0] *= c[0], h[1] *= c[0], h[2] *= c[0], h[4] *= c[1], h[5] *= c[1], h[6] *= c[1], h[8] *= c[2], h[9] *= c[2], h[10] *= c[2], this.frac2mm = xe(h); + const d = this.pixDimsRAS[1], f = this.pixDimsRAS[2], g = this.pixDimsRAS[3], m = xe(h); + m[0] = d * c[0], m[1] = 0, m[2] = 0, m[4] = 0, m[5] = f * c[1], m[6] = 0, m[8] = 0, m[9] = 0, m[10] = g * c[2]; + const p = this.mm2vox([0, 0, 0], !0); + m[12] = (-p[0] - 0.5) * d, m[13] = (-p[1] - 0.5) * f, m[14] = (-p[2] - 0.5) * g, this.frac2mmOrtho = xe(m), this.extentsMinOrtho = [m[12], m[13], m[14]], this.extentsMaxOrtho = [m[0] + m[12], m[5] + m[13], m[10] + m[14]], this.mm2ortho = ie(), Re(this.mm2ortho, a); + } + // not included in public docs + // convert AFNI head/brik space to NIfTI format + // https://github.com/afni/afni/blob/d6997e71f2b625ac1199460576d48f3136dac62c/src/thd_niftiwrite.c#L315 + THD_daxes_to_NIFTI(e, t, s) { + const r = this.hdr; + if (r === null) + throw new Error("HDR is not set"); + r.sform_code = 2; + const a = "xxyyzzg"; + let n = -1, o = -1, l = -1; + const c = ["x", "y", "z"]; + c[0] = a[s[0]], c[1] = a[s[1]], c[2] = a[s[2]]; + const h = e.slice(0, 3), u = t.slice(0, 3); + for (let d = 0; d < 3; d++) + c[d] === "x" ? n = d : c[d] === "y" ? o = d : l = d; + n < 0 || o < 0 || l < 0 || n === o || n === l || o === l || (r.pixDims[1] = Math.abs(h[0]), r.pixDims[2] = Math.abs(h[1]), r.pixDims[3] = Math.abs(h[2]), r.affine = [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ], r.affine[0][n] = -h[n], r.affine[1][o] = -h[o], r.affine[2][l] = h[l], r.affine[0][3] = -u[n], r.affine[1][3] = -u[o], r.affine[2][3] = u[l]); + } + // not included in public docs + // determine spacing voxel centers (rows, columns, slices) + SetPixDimFromSForm() { + if (!this.hdr) + throw new Error("hdr not defined"); + const e = this.hdr.affine, t = Le( + e[0][0], + e[0][1], + e[0][2], + e[0][3], + e[1][0], + e[1][1], + e[1][2], + e[1][3], + e[2][0], + e[2][1], + e[2][2], + e[2][3], + e[3][0], + e[3][1], + e[3][2], + e[3][3] + ), s = this.vox2mm([0, 0, 0], t), r = this.vox2mm([1, 0, 0], t); + de(r, r, s); + const a = this.vox2mm([0, 1, 0], t); + de(a, a, s); + const n = this.vox2mm([0, 0, 1], t); + de(n, n, s), this.hdr.pixDims[1] = ot(r), this.hdr.pixDims[2] = ot(a), this.hdr.pixDims[3] = ot(n); + } + // not included in public docs + // read DICOM format image and treat it like a NIfTI + // ----------------- + // readDICOM(buf: ArrayBuffer | ArrayBuffer[]): ArrayBuffer { + // this.series = new daikon.Series() + // // parse DICOM file + // if (Array.isArray(buf)) { + // for (let i = 0; i < buf.length; i++) { + // const dataview = new DataView(buf[i]) + // const image = daikon.Series.parseImage(dataview) + // if (image === null) { + // log.error(daikon.Series.parserError) + // } else if (image.hasPixelData()) { + // // if it's part of the same series, add it + // if (this.series.images.length === 0 || image.getSeriesId() === this.series.images[0].getSeriesId()) { + // this.series.addImage(image) + // } + // } // if hasPixelData + // } // for i + // } else { + // // not a dicom folder drop + // const image = daikon.Series.parseImage(new DataView(buf)) + // if (image === null) { + // log.error(daikon.Series.parserError) + // } else if (image.hasPixelData()) { + // // if it's part of the same series, add it + // if (this.series.images.length === 0 || image.getSeriesId() === this.series.images[0].getSeriesId()) { + // this.series.addImage(image) + // } + // } + // } + // // order the image files, determines number of frames, etc. + // this.series.buildSeries() + // // output some header info + // this.hdr = new nifti.NIFTI1() + // const hdr = this.hdr + // hdr.scl_inter = 0 + // hdr.scl_slope = 1 + // if (this.series.images[0].getDataScaleIntercept()) { + // hdr.scl_inter = this.series.images[0].getDataScaleIntercept() + // } + // if (this.series.images[0].getDataScaleSlope()) { + // hdr.scl_slope = this.series.images[0].getDataScaleSlope() + // } + // hdr.dims = [3, 1, 1, 1, 0, 0, 0, 0] + // hdr.pixDims = [1, 1, 1, 1, 1, 0, 0, 0] + // hdr.dims[1] = this.series.images[0].getCols() + // hdr.dims[2] = this.series.images[0].getRows() + // hdr.dims[3] = this.series.images[0].getNumberOfFrames() + // if (this.series.images.length > 1) { + // if (hdr.dims[3] > 1) { + // log.debug('To Do: multiple slices per file and multiple files (XA30 DWI)') + // } + // hdr.dims[3] = this.series.images.length + // } + // const rc = this.series.images[0].getPixelSpacing() // TODO: order? + // hdr.pixDims[1] = rc[0] + // hdr.pixDims[2] = rc[1] + // if (this.series.images.length > 1) { + // // Multiple slices. The depth of a pixel is the physical distance between offsets. This is not the same as slice + // // spacing for tilted slices (skew). + // const p0 = vec3.fromValues(...(this.series.images[0].getImagePosition() as [number, number, number])) + // const p1 = vec3.fromValues(...(this.series.images[1].getImagePosition() as [number, number, number])) + // const n = vec3.fromValues(0, 0, 0) + // vec3.subtract(n, p0, p1) + // hdr.pixDims[3] = vec3.length(n) + // } else { + // // Single slice. Use the slice thickness as pixel depth. + // hdr.pixDims[3] = this.series.images[0].getSliceThickness() + // } + // hdr.pixDims[4] = this.series.images[0].getTR() / 1000.0 // msec -> sec + // const dt = this.series.images[0].getDataType() // 2=int,3=uint,4=float, + // const bpv = this.series.images[0].getBitsAllocated() + // hdr.numBitsPerVoxel = bpv + // this.hdr.littleEndian = this.series.images[0].littleEndian + // if (bpv === 8 && dt === 2) { + // hdr.datatypeCode = NiiDataType.DT_INT8 + // } else if (bpv === 8 && dt === 3) { + // hdr.datatypeCode = NiiDataType.DT_UINT8 + // } else if (bpv === 16 && dt === 2) { + // hdr.datatypeCode = NiiDataType.DT_INT16 + // } else if (bpv === 16 && dt === 3) { + // hdr.datatypeCode = NiiDataType.DT_UINT16 + // } else if (bpv === 32 && dt === 2) { + // hdr.datatypeCode = NiiDataType.DT_INT32 + // } else if (bpv === 32 && dt === 3) { + // hdr.datatypeCode = NiiDataType.DT_UINT32 + // } else if (bpv === 32 && dt === 4) { + // hdr.datatypeCode = NiiDataType.DT_FLOAT32 + // } else if (bpv === 64 && dt === 4) { + // hdr.datatypeCode = NiiDataType.DT_FLOAT64 + // } else if (bpv === 1) { + // hdr.datatypeCode = NiiDataType.DT_BINARY + // } else { + // log.warn('Unsupported DICOM format: ' + dt + ' ' + bpv) + // } + // const voxelDimensions = hdr.pixDims.slice(1, 4) + // const m = getBestTransform( + // this.series.images[0].getImageDirections(), + // voxelDimensions, + // this.series.images[0].getImagePosition() + // ) + // if (m) { + // hdr.sform_code = 1 + // hdr.affine = [ + // [m[0][0], m[0][1], m[0][2], m[0][3]], + // [m[1][0], m[1][1], m[1][2], m[1][3]], + // [m[2][0], m[2][1], m[2][2], m[2][3]], + // [0, 0, 0, 1] + // ] + // } + // let data + // let length = this.series.validatePixelDataLength(this.series.images[0]) + // const buffer = new Uint8Array(new ArrayBuffer(length * this.series.images.length)) + // // implementation copied from: + // // https://github.com/rii-mango/Daikon/blob/bbe08bad9758dfbdf31ca22fb79048c7bad85706/src/series.js#L496 + // for (let i = 0; i < this.series.images.length; i++) { + // if (this.series.isMosaic) { + // data = this.series.getMosaicData(this.series.images[i], this.series.images[i].getPixelDataBytes()) + // } else { + // data = this.series.images[i].getPixelDataBytes() + // } + // length = this.series.validatePixelDataLength(this.series.images[i]) + // this.series.images[i].clearPixelData() + // buffer.set(new Uint8Array(data, 0, length), length * i) + // } // for images.length + // return buffer.buffer + // } // readDICOM() + // ----------------------- + // not included in public docs + // read ECAT7 format image + // https://github.com/openneuropet/PET2BIDS/tree/28aae3fab22309047d36d867c624cd629c921ca6/ecat_validation/ecat_info + readECAT(e) { + this.hdr = new H(); + const t = this.hdr; + t.dims = [3, 1, 1, 1, 0, 0, 0, 0], t.pixDims = [1, 1, 1, 1, 1, 0, 0, 0]; + const s = new DataView(e), r = s.getInt32(0, !1), a = s.getInt16(50, !1); + if (r !== 1296127058 || a < 1 || a > 14) + throw new Error("Not a valid ECAT file"); + let n = 512, o = 0; + const l = []; + let c = new Float32Array(); + for (; ; ) { + const h = s.getInt32(n, !1), u = s.getInt32(n + 12, !1); + if (h + u !== 31) + break; + let d = n + 20, f = 0, g = 0; + for (; f < 31 && (g = s.getInt32(d, !1), d += 16, g !== 0); ) { + f++; + let m = g * 512; + const p = m - 512, v = s.getUint16(p, !1); + t.dims[1] = s.getUint16(p + 4, !1), t.dims[2] = s.getUint16(p + 6, !1), t.dims[3] = s.getUint16(p + 8, !1); + const A = s.getFloat32(p + 26, !1); + t.pixDims[1] = s.getFloat32(p + 34, !1) * 10, t.pixDims[2] = s.getFloat32(p + 38, !1) * 10, t.pixDims[3] = s.getFloat32(p + 42, !1) * 10, t.pixDims[4] = s.getUint32(p + 46, !1) / 1e3, l.push(t.pixDims[4]); + const x = t.dims[1] * t.dims[2] * t.dims[3], w = new Float32Array(x); + if (v === 1) + for (let b = 0; b < x; b++) + w[b] = s.getUint8(m) * A, m++; + else if (v === 6) + for (let b = 0; b < x; b++) + w[b] = s.getUint16(m, !1) * A, m += 2; + else if (v === 7) + for (let b = 0; b < x; b++) + w[b] = s.getUint32(m, !1) * A, m += 4; + else + R.warn("Unknown ECAT data type " + v); + const D = c.slice(0); + c = new Float32Array(D.length + w.length), c.set(D), c.set(w, D.length), o++; + } + if (g === 0) + break; + n += 512; + } + if (t.dims[4] = o, t.pixDims[4] = l[0], o > 1) { + t.dims[0] = 4; + let h = !1; + for (let u = 0; u < o; u++) + l[u] !== l[0] && (h = !0); + h && R.warn("Frame durations vary"); + } + return t.sform_code = 1, t.affine = [ + [-t.pixDims[1], 0, 0, (t.dims[1] - 2) * 0.5 * t.pixDims[1]], + [0, -t.pixDims[2], 0, (t.dims[2] - 2) * 0.5 * t.pixDims[2]], + [0, 0, -t.pixDims[3], (t.dims[3] - 2) * 0.5 * t.pixDims[3]], + [0, 0, 0, 1] + ], t.numBitsPerVoxel = 32, t.datatypeCode = 16, c.buffer; + } + // readECAT() + readV16(e) { + this.hdr = new H(); + const t = this.hdr; + t.dims = [3, 1, 1, 1, 0, 0, 0, 0], t.pixDims = [1, 1, 1, 1, 1, 0, 0, 0]; + const s = new DataView(e); + return t.dims[1] = s.getUint16(0, !0), t.dims[2] = s.getUint16(2, !0), t.dims[3] = s.getUint16(4, !0), 2 * t.dims[1] * t.dims[2] * t.dims[3] + 6 !== e.byteLength && R.warn("This does not look like a valid BrainVoyager V16 file"), t.numBitsPerVoxel = 16, t.datatypeCode = 512, R.warn("Warning: V16 files have no spatial transforms"), t.affine = [ + [0, 0, -t.pixDims[1], (t.dims[1] - 2) * 0.5 * t.pixDims[1]], + [-t.pixDims[2], 0, 0, (t.dims[2] - 2) * 0.5 * t.pixDims[2]], + [0, -t.pixDims[3], 0, (t.dims[3] - 2) * 0.5 * t.pixDims[3]], + [0, 0, 0, 1] + ], t.littleEndian = !0, e.slice(6); + } + // readV16() + async readNPY(e) { + function t(w) { + return { + "|b1": 1, + // Boolean + " w === n[D])) + throw new Error("Not a valid NPY file: Magic number mismatch"); + const o = r.getUint16(8, !0), l = new TextDecoder("utf-8").decode(e.slice(10, 10 + o)), c = l.match(/'shape': \((.*?)\)/); + if (!c) + throw new Error("Invalid NPY header: Shape not found"); + const h = c[1].split(",").map((w) => w.trim()).filter((w) => w !== "").map(Number), u = l.match(/'descr': '([^']+)'/); + if (!u) + throw new Error("Invalid NPY header: Data type not found"); + const d = u[1], f = h.reduce((w, D) => w * D, 1), g = 10 + o, m = e.slice(g, g + f * t(d)), p = h.length > 0 ? h[h.length - 1] : 1, v = h.length > 1 ? h[h.length - 2] : 1, A = h.length > 2 ? h[h.length - 3] : 1; + this.hdr = new H(); + const x = this.hdr; + return x.dims = [3, p, v, A, 0, 0, 0, 0], x.pixDims = [1, 1, 1, 1, 1, 0, 0, 0], x.affine = [ + [x.pixDims[1], 0, 0, -(x.dims[1] - 2) * 0.5 * x.pixDims[1]], + [0, -x.pixDims[2], 0, (x.dims[2] - 2) * 0.5 * x.pixDims[2]], + [0, 0, -x.pixDims[3], (x.dims[3] - 2) * 0.5 * x.pixDims[3]], + [0, 0, 0, 1] + ], x.numBitsPerVoxel = t(d) * 8, x.datatypeCode = s(d), m; + } + async readNPZ(e) { + const t = new Gn(e); + for (let s = 0; s < t.entries.length; s++) { + const r = t.entries[s]; + if (r.fileName.toLowerCase().endsWith(".npy")) { + const a = await r.extract(); + return await this.readNPY(a.buffer); + } + } + } + async imageDataFromArrayBuffer(e) { + return new Promise((t, s) => { + const r = new Blob([e]), a = URL.createObjectURL(r), n = new Image(); + n.crossOrigin = "Anonymous", n.src = a, n.onload = () => { + URL.revokeObjectURL(a); + const o = document.createElement("canvas"); + o.width = n.width, o.height = n.height; + const l = o.getContext("2d"); + if (!l) { + s(new Error("Failed to get 2D context")); + return; + } + l.drawImage(n, 0, 0), t(l.getImageData(0, 0, n.width, n.height)); + }, n.onerror = (o) => { + URL.revokeObjectURL(a), s(o); + }; + }); + } + async readBMP(e) { + const t = await this.imageDataFromArrayBuffer(e), { width: s, height: r, data: a } = t; + this.hdr = new H(); + const n = this.hdr; + n.dims = [3, s, r, 1, 0, 0, 0, 0], n.pixDims = [1, 1, 1, 1, 1, 0, 0, 0], n.affine = [ + [n.pixDims[1], 0, 0, -(n.dims[1] - 2) * 0.5 * n.pixDims[1]], + [0, -n.pixDims[2], 0, (n.dims[2] - 2) * 0.5 * n.pixDims[2]], + [0, 0, -n.pixDims[3], (n.dims[3] - 2) * 0.5 * n.pixDims[3]], + [0, 0, 0, 1] + ], n.numBitsPerVoxel = 8, n.datatypeCode = 2304; + let o = !0; + for (let l = 0; l < a.length; l += 4) + if (a[l] !== a[l + 1] || a[l] !== a[l + 2]) { + o = !1; + break; + } + if (o) { + n.datatypeCode = 2; + const l = new Uint8Array(s * r); + for (let c = 0, h = 0; c < a.length; c += 4, h++) + l[h] = a[c]; + return l.buffer; + } + return a.buffer; + } + async readZARR(e, t) { + let { width: s, height: r, depth: a = 1, data: n } = t ?? {}, o = s * r * a * 3, l = o === n.length; + if (l || (o = s * r * a, a === 3 && (l = !0, a = 1)), o !== n.length) + throw new Error(`Expected RGB ${s}×${r}×${a}×3 = ${o}, but ZARR length ${n.length}`); + this.hdr = new H(); + const c = this.hdr; + if (c.dims = [3, s, r, a, 1, 1, 1, 1], c.pixDims = [1, 1, 1, 1, 0, 0, 0, 0], c.affine = [ + [c.pixDims[1], 0, 0, -(c.dims[1] - 2) * 0.5 * c.pixDims[1]], + [0, -c.pixDims[2], 0, (c.dims[2] - 2) * 0.5 * c.pixDims[2]], + [0, 0, -c.pixDims[3], (c.dims[3] - 2) * 0.5 * c.pixDims[3]], + [0, 0, 0, 1] + ], !l) { + if (c.numBitsPerVoxel = 8, c.datatypeCode = 2, n instanceof Uint8Array) { + const g = new ArrayBuffer(n.length); + return new Uint8Array(g).set(n), g; + } + return n; + } + c.numBitsPerVoxel = 24, c.datatypeCode = 128; + function h(g, m, p, v) { + const A = m * p, x = new Uint8Array(A * v * 3), w = new Array(v); + for (let C = 0; C < v; C++) + w[C] = A * 3 * C; + let D = 0, b = 0; + for (let C = 0; C < A; C++) { + for (let E = 0; E < v; E++) + x[w[E] + b] = g[D++], x[w[E] + b + 1] = g[D++], x[w[E] + b + 2] = g[D++]; + b += 3; + } + return x; + } + const u = h(n, c.dims[1], c.dims[2], c.dims[3]), d = new ArrayBuffer(u.length); + return new Uint8Array(d).set(u), d; + } + // not included in public docs + // read brainvoyager format VMR image + // https://support.brainvoyager.com/brainvoyager/automation-development/84-file-formats/343-developer-guide-2-6-the-format-of-vmr-files + readVMR(e) { + this.hdr = new H(); + const t = this.hdr; + t.dims = [3, 1, 1, 1, 0, 0, 0, 0], t.pixDims = [1, 1, 1, 1, 1, 0, 0, 0]; + const s = new DataView(e), r = s.getUint16(0, !0); + r !== 4 && R.warn("Not a valid version 4 VMR image"), t.dims[1] = s.getUint16(2, !0), t.dims[2] = s.getUint16(4, !0), t.dims[3] = s.getUint16(6, !0); + const a = t.dims[1] * t.dims[2] * t.dims[3]; + if (r >= 4) { + let n = 8 + a; + const o = s.getUint32(n + 88, !0); + if (n = n + 92, o > 0) { + const l = e.byteLength; + for (let c = 0; c < o; c++) { + for (; n < l && s.getUint8(n) !== 0; ) + n++; + for (n++, n += 4; n < l && s.getUint8(n) !== 0; ) + n++; + n++; + const h = s.getUint32(n, !0); + n += 4; + for (let u = 0; u < h; u++) + n += 4; + } + } + t.pixDims[1] = s.getFloat32(n + 2, !0), t.pixDims[2] = s.getFloat32(n + 6, !0), t.pixDims[3] = s.getFloat32(n + 10, !0); + } + return R.warn("Warning: VMR spatial transform not implemented"), t.affine = [ + [0, 0, -t.pixDims[1], (t.dims[1] - 2) * 0.5 * t.pixDims[1]], + [-t.pixDims[2], 0, 0, (t.dims[2] - 2) * 0.5 * t.pixDims[2]], + [0, -t.pixDims[3], 0, (t.dims[3] - 2) * 0.5 * t.pixDims[3]], + [0, 0, 0, 1] + ], R.debug(t), t.numBitsPerVoxel = 8, t.datatypeCode = 2, e.slice(8, 8 + a); + } + // readVMR() + // not included in public docs + // read DSI-Studio FIB format image + // https://dsi-studio.labsolver.org/doc/cli_data.html + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async readFIB(e) { + this.hdr = new H(); + const t = this.hdr; + t.littleEndian = !1, t.dims = [3, 1, 1, 1, 0, 0, 0, 0], t.pixDims = [1, 1, 1, 1, 1, 0, 0, 0]; + const s = await j.readMatV4(e, !0); + if (!("dimension" in s) || !("dti_fa" in s)) + throw new Error("Not a valid DSIstudio FIB file"); + const r = "index0" in s && "index1" in s && "index2" in s && "odf_vertices" in s; + t.numBitsPerVoxel = 32, t.datatypeCode = 16, t.dims[1] = s.dimension[0], t.dims[2] = s.dimension[1], t.dims[3] = s.dimension[2], t.dims[4] = 1, t.pixDims[1] = s.voxel_size[0], t.pixDims[2] = s.voxel_size[1], t.pixDims[3] = s.voxel_size[2], t.sform_code = 1; + const a = (t.dims[1] - 1) * 0.5 * t.pixDims[1], n = (t.dims[2] - 1) * 0.5 * t.pixDims[2], o = (t.dims[3] - 1) * 0.5 * t.pixDims[3]; + t.affine = [ + [t.pixDims[1], 0, 0, -a], + [0, -t.pixDims[2], 0, n], + [0, 0, t.pixDims[2], -o], + [0, 0, 0, 1] + ], t.littleEndian = !0; + const l = t.dims[1] * t.dims[2] * t.dims[3], c = l * Math.ceil(t.numBitsPerVoxel / 8), h = c * t.dims[4], u = new Uint8Array(new ArrayBuffer(l * 4 * 3)); + if (r) { + const m = t.dims[1] * t.dims[2] * t.dims[3], p = new Float32Array(m), v = new Float32Array(m), A = new Float32Array(m), x = s.index0, w = s.odf_vertices; + for (let D = 0; D < m; D++) { + const b = x[D] * 3; + p[D] = w[b + 0], v[D] = w[b + 1], A[D] = -w[b + 2]; + } + u.set(new Uint8Array(p.buffer, p.byteOffset, p.byteLength), 0 * c), u.set(new Uint8Array(v.buffer, v.byteOffset, v.byteLength), 1 * c), u.set(new Uint8Array(A.buffer, A.byteOffset, A.byteLength), 2 * c); + } + "report" in s && (t.description = new TextDecoder().decode(s.report.subarray(0, Math.min(79, s.report.byteLength)))); + const d = new Uint8Array(new ArrayBuffer(h)), f = Float32Array.from(s.dti_fa); + if ("mask" in s) { + let m = 1; + "dti_fa_slope" in s && (m = s.dti_fa_slope[0]); + let p = 1; + "dti_fa_inter" in s && (p = s.dti_fa_inter[0]); + const v = t.dims[1] * t.dims[2] * t.dims[3], A = s.mask, x = new Float32Array(v); + let w = 0; + for (let D = 0; D < v; D++) + A[D] !== 0 && (x[D] = f[w] * m + p, w++); + return [x.buffer, new Float32Array(u.buffer)]; + } + const g = new Uint8Array(f.buffer, f.byteOffset, f.byteLength); + return d.set(g, 0), [d.buffer, new Float32Array(u.buffer)]; + } + // readFIB() + // not included in public docs + // read DSI-Studio SRC format image + // https://dsi-studio.labsolver.org/doc/cli_data.html + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async readSRC(e) { + this.hdr = new H(); + const t = this.hdr; + t.littleEndian = !1, t.dims = [3, 1, 1, 1, 0, 0, 0, 0], t.pixDims = [1, 1, 1, 1, 1, 0, 0, 0]; + const s = await j.readMatV4(e); + if (!("dimension" in s) || !("image0" in s)) + throw new Error("Not a valid DSIstudio SRC file"); + let r = 0, a = 0; + for (const [f, g] of Object.entries(s)) + if (f.startsWith("image")) { + if (r === 0 ? a = g.length : a !== g.length && (a = -1), g.constructor !== Uint16Array) + throw new Error("DSIstudio SRC files always use Uint16 datatype"); + r++; + } + if (a < 1 || r < 1) + throw new Error("SRC file not valid DSI Studio data. The image(s) should have the same length"); + t.numBitsPerVoxel = 16, t.datatypeCode = 512, t.dims[1] = s.dimension[0], t.dims[2] = s.dimension[1], t.dims[3] = s.dimension[2], t.dims[4] = r, t.dims[4] > 1 && (t.dims[0] = 4), t.pixDims[1] = s.voxel_size[0], t.pixDims[2] = s.voxel_size[1], t.pixDims[3] = s.voxel_size[2], t.sform_code = 1; + const n = (t.dims[1] - 1) * 0.5 * t.pixDims[1], o = (t.dims[2] - 1) * 0.5 * t.pixDims[2], l = (t.dims[3] - 1) * 0.5 * t.pixDims[3]; + t.affine = [ + [t.pixDims[1], 0, 0, -n], + [0, -t.pixDims[2], 0, o], + [0, 0, t.pixDims[2], -l], + [0, 0, 0, 1] + ], t.littleEndian = !0; + const c = t.dims[1] * t.dims[2] * t.dims[3] * (t.numBitsPerVoxel / 8), h = c * t.dims[4], u = new Uint8Array(new ArrayBuffer(h)); + let d = 0; + for (let f = 0; f < r; f++) { + const g = s[`image${f}`], m = new Uint8Array(g.buffer, g.byteOffset, g.byteLength); + u.set(m, d), d += c; + } + return "report" in s && (t.description = new TextDecoder().decode(s.report.subarray(0, Math.min(79, s.report.byteLength)))), u.buffer; + } + // readSRC() + // not included in public docs + // read AFNI head/brik format image + async readHEAD(e, t) { + this.hdr = new H(); + const s = this.hdr; + s.dims[0] = 3, s.pixDims = [1, 1, 1, 1, 1, 0, 0, 0]; + let r = [0, 0, 0], a = [0, 0, 0], n = [1, 1, 1]; + const l = new TextDecoder().decode(e).split(/\r?\n/), c = (e.byteLength + 8) % 16, h = e.byteLength + (16 - c); + R.debug(e.byteLength, "len", h); + const u = new ArrayBuffer(h); + new Uint8Array(u).set(new Uint8Array(e)); + const d = new Sn(h + 8, 42, u, !0); + s.addExtension(d), s.extensionCode = 42, s.extensionFlag[0] = 1, s.extensionSize = h + 8; + const f = l.length; + let g = 0, m = !1; + for (; g < f; ) { + let v = l[g]; + if (g++, !v.startsWith("type")) + continue; + const A = v.includes("integer-attribute"), x = v.includes("float-attribute"); + if (v = l[g], g++, !v.startsWith("name")) + continue; + let w = v.split("= "); + const D = w[1]; + v = l[g], g++, w = v.split("= "); + let b = parseInt(w[1]); + if (!(b < 1)) { + if (v = l[g], g++, w = v.trim().split(/\s+/), x || A) { + for (; w.length < b; ) { + v = l[g], g++; + const C = v.trim().split(/\s+/); + w.push(...C); + } + for (let C = 0; C < b; C++) + w[C] = parseFloat(w[C]); + } + switch (D) { + case "BYTEORDER_STRING": + w[0].includes("LSB_FIRST") ? s.littleEndian = !0 : w[0].includes("MSB_FIRST") && (s.littleEndian = !1); + break; + case "BRICK_TYPES": + { + s.dims[4] = b; + const C = parseInt(w[0]); + C === 0 ? (s.numBitsPerVoxel = 8, s.datatypeCode = 2) : C === 1 ? (s.numBitsPerVoxel = 16, s.datatypeCode = 4) : C === 3 ? (s.numBitsPerVoxel = 32, s.datatypeCode = 16) : R.warn("Unknown BRICK_TYPES ", C); + } + break; + case "IJK_TO_DICOM_REAL": + if (b < 12) + break; + m = !0, s.sform_code = 2, s.affine = [ + [-w[0], -w[1], -w[2], -w[3]], + [-w[4], -w[5], -w[6], -w[7]], + // TODO don't reuse items for numeric values + [w[8], w[9], w[10], w[11]], + [0, 0, 0, 1] + ]; + break; + case "DATASET_DIMENSIONS": + b = Math.max(b, 3); + for (let C = 0; C < b; C++) + s.dims[C + 1] = w[C]; + break; + case "ORIENT_SPECIFIC": + r = w; + break; + case "ORIGIN": + a = w; + break; + case "DELTA": + n = w; + break; + case "TAXIS_FLOATS": + s.pixDims[4] = w[0]; + break; + default: + R.warn("Unknown:", D); + } + } + } + m ? this.SetPixDimFromSForm() : this.THD_daxes_to_NIFTI(n, a, r); + const p = s.numBitsPerVoxel / 8 * s.dims[1] * s.dims[2] * s.dims[3] * s.dims[4]; + if (!t) + throw new Error("pairedImgData not set"); + return t.byteLength < p ? await j.decompressToBuffer(new Uint8Array(t)) : t.slice(0); + } + // not included in public docs + // read ITK MHA format image + // https://itk.org/Wiki/ITK/MetaIO/Documentation#Reading_a_Brick-of-Bytes_.28an_N-Dimensional_volume_in_a_single_file.29 + async readMHA(e, t) { + const s = e.byteLength; + if (s < 20) + throw new Error("File too small to be VTK: bytes = " + e.byteLength); + const r = new Uint8Array(e); + let a = 0; + function n(m) { + return m === 10 || m === 13; + } + function o() { + for (; a < s && n(r[a]); ) + a++; + const m = a; + for (; a < s && !n(r[a]); ) + a++; + return a - m < 2 ? "" : new TextDecoder().decode(e.slice(m, a)); + } + let l = o(); + this.hdr = new H(); + const c = this.hdr; + c.pixDims = [1, 1, 1, 1, 1, 0, 0, 0], c.dims = [1, 1, 1, 1, 1, 1, 1, 1], c.littleEndian = !0; + let h = !1, u = !1; + const d = rt(NaN, 0, 0, 0, 1, 0, 0, 0, 1), f = G(0, 0, 0); + for (; l !== ""; ) { + let m = l.split(" "); + if (m.length > 2 && (m = m.slice(2)), l.startsWith("BinaryDataByteOrderMSB") && m[0].includes("False") && (c.littleEndian = !0), l.startsWith("BinaryDataByteOrderMSB") && m[0].includes("True") && (c.littleEndian = !1), l.startsWith("CompressedData") && m[0].includes("True") && (h = !0), l.startsWith("TransformMatrix")) + for (let p = 0; p < 9; p++) + d[p] = parseFloat(m[p]); + if (l.startsWith("Offset")) + for (let p = 0; p < Math.min(m.length, 3); p++) + f[p] = parseFloat(m[p]); + if (l.startsWith("ElementSpacing")) + for (let p = 0; p < m.length; p++) + c.pixDims[p + 1] = parseFloat(m[p]); + if (l.startsWith("DimSize")) { + c.dims[0] = m.length; + for (let p = 0; p < m.length; p++) + c.dims[p + 1] = parseInt(m[p]); + } + if (l.startsWith("ElementType")) + switch (m[0]) { + case "MET_UCHAR": + c.numBitsPerVoxel = 8, c.datatypeCode = 2; + break; + case "MET_CHAR": + c.numBitsPerVoxel = 8, c.datatypeCode = 256; + break; + case "MET_SHORT": + c.numBitsPerVoxel = 16, c.datatypeCode = 4; + break; + case "MET_USHORT": + c.numBitsPerVoxel = 16, c.datatypeCode = 512; + break; + case "MET_INT": + c.numBitsPerVoxel = 32, c.datatypeCode = 8; + break; + case "MET_UINT": + c.numBitsPerVoxel = 32, c.datatypeCode = 768; + break; + case "MET_FLOAT": + c.numBitsPerVoxel = 32, c.datatypeCode = 16; + break; + case "MET_DOUBLE": + c.numBitsPerVoxel = 64, c.datatypeCode = 64; + break; + default: + throw new Error("Unsupported MHA data type: " + m[0]); + } + if (l.startsWith("ObjectType") && !m[0].includes("Image") && R.warn("Only able to read ObjectType = Image, not " + l), l.startsWith("ElementDataFile")) { + m[0] !== "LOCAL" && (u = !0); + break; + } + l = o(); + } + const g = rt(c.pixDims[1], 0, 0, 0, c.pixDims[2], 0, 0, 0, c.pixDims[3]); + for (gn(d, d, g), c.affine = [ + [-d[0], -d[3], -d[6], -f[0]], + [-d[1], -d[4], -d[7], -f[1]], + [d[2], d[5], d[8], f[2]], + [0, 0, 0, 1] + ]; r[a] === 10; ) + a++; + return c.vox_offset = a, u && t ? h ? await j.decompressToBuffer(new Uint8Array(t.slice(0))) : t.slice(0) : h ? await j.decompressToBuffer(new Uint8Array(e.slice(c.vox_offset))) : e.slice(c.vox_offset); + } + // readMHA() + // not included in public docs + // read mrtrix MIF format image + // https://mrtrix.readthedocs.io/en/latest/getting_started/image_data.html#mrtrix-image-formats + async readMIF(e, t) { + this.hdr = new H(); + const s = this.hdr; + s.pixDims = [1, 1, 1, 1, 1, 0, 0, 0], s.dims = [1, 1, 1, 1, 1, 1, 1, 1]; + let r = e.byteLength; + if (r < 20) + throw new Error("File too small to be MIF: bytes = " + r); + let a = new Uint8Array(e); + a[0] === 31 && a[1] === 139 && (R.debug("MIF with GZ decompression"), e = await j.decompressToBuffer(new Uint8Array(e)), r = e.byteLength, a = new Uint8Array(e)); + let n = 0; + function o() { + for (; n < r && a[n] === 10; ) + n++; + const T = n; + for (; n < r && a[n] !== 10; ) + n++; + return n++, n - T < 1 ? "" : new TextDecoder().decode(e.slice(T, n - 1)); + } + let l = o(); + if (!l.startsWith("mrtrix image")) + throw new Error("Not a valid MIF file"); + const c = []; + let h = !1, u = 0, d = 0, f = !1; + for (l = o(); n < r && !l.startsWith("END"); ) { + let T = l.split(":"); + if (l = o(), T.length < 2) + break; + const k = T[0]; + T = T[1].split(","); + for (let B = 0; B < T.length; B++) + T[B] = T[B].trim(); + switch (k) { + case "dim": + s.dims[0] = T.length; + for (let B = 0; B < T.length; B++) + s.dims[B + 1] = parseInt(T[B]); + break; + case "vox": + for (let B = 0; B < T.length; B++) + s.pixDims[B + 1] = parseFloat(T[B]), isNaN(s.pixDims[B + 1]) && (s.pixDims[B + 1] = 0); + break; + case "layout": + for (let B = 0; B < T.length; B++) + c.push(parseInt(T[B])); + break; + case "datatype": + { + const B = T[0]; + B.startsWith("Bit") ? (h = !0, s.datatypeCode = 2) : B.startsWith("Int8") ? s.datatypeCode = 256 : B.startsWith("UInt8") ? s.datatypeCode = 2 : B.startsWith("Int16") ? s.datatypeCode = 4 : B.startsWith("UInt16") ? s.datatypeCode = 512 : B.startsWith("Int32") ? s.datatypeCode = 8 : B.startsWith("UInt32") ? s.datatypeCode = 768 : B.startsWith("Float32") ? s.datatypeCode = 16 : B.startsWith("Float64") ? s.datatypeCode = 64 : R.warn("Unsupported datatype " + B), B.includes("8") ? s.numBitsPerVoxel = 8 : B.includes("16") ? s.numBitsPerVoxel = 16 : B.includes("32") ? s.numBitsPerVoxel = 32 : B.includes("64") && (s.numBitsPerVoxel = 64), s.littleEndian = !0, B.endsWith("LE") && (s.littleEndian = !0), B.endsWith("BE") && (s.littleEndian = !1); + } + break; + case "transform": + if (u > 2 || T.length !== 4) + break; + s.affine[u][0] = parseFloat(T[0]), s.affine[u][1] = parseFloat(T[1]), s.affine[u][2] = parseFloat(T[2]), s.affine[u][3] = parseFloat(T[3]), u++; + break; + case "comments": + s.description = T[0].substring(0, Math.min(79, T[0].length)); + break; + /* case 'command_history': + if (items[0].startsWith('dwi2tensor')) { + isTensor = true + } + break */ + case "RepetitionTime": + d = parseFloat(T[0]); + break; + case "file": + f = !T[0].startsWith(". "), f || (T = T[0].split(" "), s.vox_offset = parseInt(T[1])); + break; + } + } + const g = s.dims[0]; + g > 5 && R.warn("reader only designed for a maximum of 5 dimensions (XYZTD)"); + let m = 1; + for (let T = 0; T < g; T++) + m *= Math.max(s.dims[T + 1], 1); + for (let T = 0; T < 3; T++) + for (let k = 0; k < 3; k++) + s.affine[T][k] *= s.pixDims[k + 1]; + R.debug("mif affine:" + s.affine[0]), d > 0 && (s.pixDims[4] = d), f && !t && R.warn("MIH header provided without paired image data"); + let p; + if (t && f) + p = t.slice(0); + else if (h) { + s.numBitsPerVoxel = 8; + const T = new Uint8Array(m), k = e.slice(s.vox_offset, s.vox_offset + Math.ceil(m / 8)), B = new Uint8Array(k); + let U = 0; + for (let V = 0; V < m; V++) { + const N = V % 8; + T[V] = B[U] >> 7 - N & 1, N === 7 && U++; + } + p = T.buffer; + } else + p = e.slice(s.vox_offset, s.vox_offset + m * (s.numBitsPerVoxel / 8)); + c.length !== s.dims[0] && R.warn("dims does not match layout"); + let v = 1; + const A = [1, 1, 1, 1, 1], x = [!1, !1, !1, !1, !1]; + for (let T = 0; T < c.length; T++) + for (let k = 0; k < c.length; k++) + Math.abs(c[k]) === T && (A[k] = v, (c[k] < 0 || Object.is(c[k], -0)) && (x[k] = !0), v *= s.dims[k + 1]); + let w = j.range(0, s.dims[1] - 1, 1); + x[0] && (w = j.range(s.dims[1] - 1, 0, -1)); + for (let T = 0; T < s.dims[1]; T++) + w[T] *= A[0]; + let D = j.range(0, s.dims[2] - 1, 1); + x[1] && (D = j.range(s.dims[2] - 1, 0, -1)); + for (let T = 0; T < s.dims[2]; T++) + D[T] *= A[1]; + let b = j.range(0, s.dims[3] - 1, 1); + x[2] && (b = j.range(s.dims[3] - 1, 0, -1)); + for (let T = 0; T < s.dims[3]; T++) + b[T] *= A[2]; + let C = j.range(0, s.dims[4] - 1, 1); + x[3] && (C = j.range(s.dims[4] - 1, 0, -1)); + for (let T = 0; T < s.dims[4]; T++) + C[T] *= A[3]; + let E = j.range(0, s.dims[5] - 1, 1); + x[4] && (E = j.range(s.dims[5] - 1, 0, -1)); + for (let T = 0; T < s.dims[5]; T++) + E[T] *= A[4]; + let F = 0, M, S; + switch (s.datatypeCode) { + case 256: + M = new Int8Array(p), S = new Int8Array(m); + break; + case 2: + M = new Uint8Array(p), S = new Uint8Array(m); + break; + case 4: + M = new Int16Array(p), S = new Int16Array(m); + break; + case 512: + M = new Uint16Array(p), S = new Uint16Array(m); + break; + case 8: + M = new Int32Array(p), S = new Int32Array(m); + break; + case 768: + M = new Uint32Array(p), S = new Uint32Array(m); + break; + case 16: + M = new Float32Array(p), S = new Float32Array(m); + break; + case 64: + M = new Float64Array(p), S = new Float64Array(m); + break; + default: + throw new Error("unknown datatypeCode"); + } + for (let T = 0; T < s.dims[5]; T++) + for (let k = 0; k < s.dims[4]; k++) + for (let B = 0; B < s.dims[3]; B++) + for (let U = 0; U < s.dims[2]; U++) + for (let V = 0; V < s.dims[1]; V++) + S[F] = M[w[V] + D[U] + b[B] + C[k] + E[T]], F++; + return S.buffer; + } + // readMIF() + // not included in public docs + // Transform to orient NIfTI image to Left->Right,Posterior->Anterior,Inferior->Superior (48 possible permutations) + calculateRAS() { + if (!this.hdr) + throw new Error("hdr not set"); + const e = this.hdr.affine, t = this.hdr, s = rt( + Math.abs(e[0][0]), + Math.abs(e[0][1]), + Math.abs(e[0][2]), + Math.abs(e[1][0]), + Math.abs(e[1][1]), + Math.abs(e[1][2]), + Math.abs(e[2][0]), + Math.abs(e[2][1]), + Math.abs(e[2][2]) + ), r = [1, 1, 1]; + s[3] > s[0] && (r[0] = 2), s[6] > s[0] && s[6] > s[3] && (r[0] = 3), r[1] = 1, r[0] === 1 ? s[4] > s[7] ? r[1] = 2 : r[1] = 3 : r[0] === 2 ? s[1] > s[7] ? r[1] = 1 : r[1] = 3 : s[1] > s[4] ? r[1] = 1 : r[1] = 2, r[2] = 6 - r[1] - r[0]; + let a = [1, 2, 3]; + a[r[0] - 1] = 1, a[r[1] - 1] = 2, a[r[2] - 1] = 3; + let n = Le( + e[0][0], + e[0][1], + e[0][2], + e[0][3], + e[1][0], + e[1][1], + e[1][2], + e[1][3], + e[2][0], + e[2][1], + e[2][2], + e[2][3], + 0, + 0, + 0, + 1 + ); + this.mm000 = this.vox2mm([-0.5, -0.5, -0.5], n), this.mm100 = this.vox2mm([t.dims[1] - 0.5, -0.5, -0.5], n), this.mm010 = this.vox2mm([-0.5, t.dims[2] - 0.5, -0.5], n), this.mm001 = this.vox2mm([-0.5, -0.5, t.dims[3] - 0.5], n); + const o = ie(); + Ia(o, n); + for (let p = 0; p < 3; p++) + for (let v = 0; v < 3; v++) + o[p * 4 + v] = n[p * 4 + a[v] - 1]; + const l = [0, 0, 0]; + o[0] < 0 && (l[0] = 1), o[5] < 0 && (l[1] = 1), o[10] < 0 && (l[2] = 1), this.dimsRAS = [t.dims[0], t.dims[a[0]], t.dims[a[1]], t.dims[a[2]]], this.pixDimsRAS = [t.pixDims[0], t.pixDims[a[0]], t.pixDims[a[1]], t.pixDims[a[2]]], this.permRAS = a.slice(); + for (let p = 0; p < 3; p++) + l[p] === 1 && (this.permRAS[p] = -this.permRAS[p]); + if (this.arrayEquals(a, [1, 2, 3]) && this.arrayEquals(l, [0, 0, 0])) { + this.toRAS = ie(), this.matRAS = xe(n), this.calculateOblique(), this.img2RASstep = [1, this.dimsRAS[1], this.dimsRAS[1] * this.dimsRAS[2]], this.img2RASstart = [0, 0, 0]; + return; + } + pn(n), n[0] = 1 - l[0] * 2, n[5] = 1 - l[1] * 2, n[10] = 1 - l[2] * 2, n[3] = (t.dims[a[0]] - 1) * l[0], n[7] = (t.dims[a[1]] - 1) * l[1], n[11] = (t.dims[a[2]] - 1) * l[2]; + const c = ie(); + Re(c, n), mt(c, c, o), this.matRAS = xe(c), n = Le(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1), n[a[0] - 1 + 0] = -l[0] * 2 + 1, n[a[1] - 1 + 4] = -l[1] * 2 + 1, n[a[2] - 1 + 8] = -l[2] * 2 + 1, n[3] = l[0], n[7] = l[1], n[11] = l[2], this.toRAS = xe(n), n[3] = 0, n[7] = 0, n[11] = 0, n[12] = 0, (this.permRAS[0] === -1 || this.permRAS[1] === -1 || this.permRAS[2] === -1) && (n[12] = t.dims[1] - 1), n[13] = 0, (this.permRAS[0] === -2 || this.permRAS[1] === -2 || this.permRAS[2] === -2) && (n[13] = t.dims[2] - 1), n[14] = 0, (this.permRAS[0] === -3 || this.permRAS[1] === -3 || this.permRAS[2] === -3) && (n[14] = t.dims[3] - 1), this.toRASvox = xe(n), R.debug(this.hdr.dims), R.debug(this.dimsRAS); + const h = this.hdr; + a = this.permRAS; + const u = [Math.abs(a[0]), Math.abs(a[1]), Math.abs(a[2])], d = [h.dims[u[0]], h.dims[u[1]], h.dims[u[2]]], f = [1, h.dims[1], h.dims[1] * h.dims[2]], g = [f[u[0] - 1], f[u[1] - 1], f[u[2] - 1]], m = [0, 0, 0]; + for (let p = 0; p < 3; p++) + a[p] < 0 && (m[p] = g[p] * (d[p] - 1), g[p] = -g[p]); + this.img2RASstep = g, this.img2RASstart = m, this.calculateOblique(); + } + // Reorient raw header data to RAS + // assume single volume, use nVolumes to specify, set nVolumes = 0 for same as input + async hdr2RAS(e = 1) { + if (!this.permRAS) + throw new Error("permRAS undefined"); + if (!this.hdr) + throw new Error("hdr undefined"); + const t = Vi({ ...this.hdr, vox_offset: 352 }, !1), s = await Ht(t.buffer, !0); + e === 1 ? (s.dims[0] = 3, s.dims[4] = 1) : e > 1 && (s.dims[0] = 4, s.dims[4] = e); + const r = this.permRAS.slice(); + if (r[0] === 1 && r[1] === 2 && r[2] === 3) + return s; + s.qform_code = 0; + for (let n = 1; n < 4; n++) + s.dims[n] = this.dimsRAS[n]; + for (let n = 0; n < this.pixDimsRAS.length; n++) + s.pixDims[n] = this.pixDimsRAS[n]; + let a = 0; + for (let n = 0; n < 4; n++) + for (let o = 0; o < 4; o++) + s.affine[n][o] = this.matRAS[a], a++; + return s; + } + // Reorient raw image data to RAS + // note that GPU-based orient shader is much faster + // returns single 3D volume even for 4D input. Use nVolume to select volume (0 indexed) + img2RAS(e = 0) { + if (!this.permRAS) + throw new Error("permRAS undefined"); + if (!this.img) + throw new Error("img undefined"); + if (!this.hdr) + throw new Error("hdr undefined"); + const t = this.permRAS.slice(); + if (t[0] === 1 && t[1] === 2 && t[2] === 3) + return this.img; + const s = this.hdr, r = s.dims[1] * s.dims[2] * s.dims[3]; + let a = e * r; + (a + r > this.img.length || a < 0) && (a = 0, R.warn(`img2RAS nVolume (${e}) out of bounds (${e}+1)×${r} > ${this.img.length}`)); + const n = this.img.slice(0, r), o = [Math.abs(t[0]), Math.abs(t[1]), Math.abs(t[2])], l = [s.dims[o[0]], s.dims[o[1]], s.dims[o[2]]], c = [1, s.dims[1], s.dims[1] * s.dims[2]], h = [c[o[0] - 1], c[o[1] - 1], c[o[2] - 1]], u = [0, 0, 0]; + for (let f = 0; f < 3; f++) + t[f] < 0 && (u[f] = h[f] * (l[f] - 1), h[f] = -h[f]); + let d = 0; + for (let f = 0; f < l[2]; f++) { + const g = u[2] + f * h[2]; + for (let m = 0; m < l[1]; m++) { + const p = u[1] + m * h[1]; + for (let v = 0; v < l[0]; v++) { + const A = u[0] + v * h[0]; + n[d] = this.img[A + p + g + a], d++; + } + } + } + return n; + } + // img2RAS() + // not included in public docs + // convert voxel location (row, column slice, indexed from 0) to world space + vox2mm(e, t) { + const s = xe(t); + Ne(s, s); + const r = pe(e[0], e[1], e[2], 1); + return Me(r, r, s), G(r[0], r[1], r[2]); + } + // vox2mm() + // not included in public docs + // convert world space to voxel location (row, column slice, indexed from 0) + mm2vox(e, t = !1) { + if (!this.matRAS) + throw new Error("matRAS undefined"); + const s = xe(this.matRAS), r = xe(s); + Ne(r, s), Re(r, r); + const a = pe(e[0], e[1], e[2], 1); + Me(a, a, r); + const n = G(a[0], a[1], a[2]); + return t ? n : [Math.round(n[0]), Math.round(n[1]), Math.round(n[2])]; + } + // vox2mm() + // not included in public docs + // returns boolean: are two arrays identical? + // TODO this won't work for complex objects. Maybe use array-equal from NPM + arrayEquals(e, t) { + return Array.isArray(e) && Array.isArray(t) && e.length === t.length && e.every((s, r) => s === t[r]); + } + // not included in public docs + // base function for niivue.setColormap() + // colormaps are continuously interpolated between 256 values (0..256) + setColormap(e) { + this._colormap = e, this.calMinMax(), this.onColormapChange && this.onColormapChange(this); + } + // not included in public docs + // base function for niivue.setColormap() + // label colormaps are discretely sampled from an arbitrary number of colors + setColormapLabel(e) { + this.colormapLabel = oe.makeLabelLut(e); + } + async setColormapLabelFromUrl(e) { + this.colormapLabel = await oe.makeLabelLutFromUrl(e); + } + get colormap() { + return this._colormap; + } + get colorMap() { + return this._colormap; + } + // TODO duplicate fields, see niivue/loadDocument + set colormap(e) { + this.setColormap(e); + } + set colorMap(e) { + this.setColormap(e); + } + get opacity() { + return this._opacity; + } + set opacity(e) { + this._opacity = e, this.onOpacityChange && this.onOpacityChange(this); + } + /** + * set contrast/brightness to robust range (2%..98%) + * @param vol - volume for estimate (use -1 to use estimate on all loaded volumes; use INFINITY for current volume) + * @param isBorder - if true (default) only center of volume used for estimate + * @returns volume brightness and returns array [pct2, pct98, mnScale, mxScale] + * @see {@link https://niivue.com/demos/features/timeseries2.html | live demo usage} + */ + calMinMax(e = Number.POSITIVE_INFINITY, t = !0) { + if (!this.hdr) + throw new Error("hdr undefined"); + if (!this.img) + throw new Error("img undefined"); + let s = Number.POSITIVE_INFINITY, r = Number.NEGATIVE_INFINITY, a = 0, n = 0, o = this.hdr.dims[1] * this.hdr.dims[2] * this.hdr.dims[3]; + const l = Math.floor(this.img.length / o); + e >= l && (e = this.frame4D), e = Math.min(e, l - 1); + const c = e * o; + let h = []; + if (t) { + const k = [ + Math.floor(0.25 * this.hdr.dims[1]), + Math.floor(0.25 * this.hdr.dims[2]), + Math.floor(0.25 * this.hdr.dims[3]) + ], B = [ + this.hdr.dims[1] - 2 * k[0], + this.hdr.dims[2] - 2 * k[1], + this.hdr.dims[3] - 2 * k[2] + ], U = [B[0] + k[0], B[1] + k[1], B[2] + k[2]]; + o = B[0] * B[1] * B[2], h = new this.img.constructor(o); + let V = -1, N = 0; + for (let P = 0; P < this.hdr.dims[3]; P++) + for (let L = 0; L < this.hdr.dims[2]; L++) + for (let q = 0; q < this.hdr.dims[1]; q++) + V++, !(q < k[0] || L < k[1] || P < k[2]) && (q >= U[0] || L >= U[1] || P >= U[2] || (h[N] = this.img[V + c], N++)); + } else { + h = new this.img.constructor(o); + for (let T = 0; T < o; T++) + h[T] = this.img[T + c]; + } + const u = h.constructor !== Float64Array && h.constructor !== Float32Array && this.ignoreZeroVoxels; + if (u) + for (let T = 0; T < o; T++) + s = Math.min(h[T], s), r = Math.max(h[T], r), h[T] === 0 && a++; + else + for (let T = 0; T < o; T++) { + if (isNaN(h[T])) { + n++; + continue; + } + h[T] === 0 && (a++, this.ignoreZeroVoxels) || (s = Math.min(h[T], s), r = Math.max(h[T], r)); + } + this.ignoreZeroVoxels && s === r && a > 0 && (s = 0); + const d = this.intensityRaw2Scaled(s), f = this.intensityRaw2Scaled(r), g = oe.colormapFromKey(this._colormap); + let m = 0, p = 0; + if (g.min !== void 0 && (m = g.min), g.max !== void 0 && (p = g.max), m === p && this.trustCalMinMax && isFinite(this.hdr.cal_min) && isFinite(this.hdr.cal_max) && this.hdr.cal_max > this.hdr.cal_min) + return this.cal_min = this.hdr.cal_min, this.cal_max = this.hdr.cal_max, this.robust_min = this.cal_min, this.robust_max = this.cal_max, this.global_min = d, this.global_max = f, [this.hdr.cal_min, this.hdr.cal_max, this.hdr.cal_min, this.hdr.cal_max]; + if (m !== p) + return this.cal_min = m, this.cal_max = p, this.robust_min = this.cal_min, this.robust_max = this.cal_max, [m, p, m, p]; + const v = 100 * a / (o - 0); + let A = !1; + v > 60 && !this.ignoreZeroVoxels && (R.warn(`${Math.round(v)}% of voxels are zero: ignoring zeros for cal_max`), A = !0, this.ignoreZeroVoxels = !0), this.ignoreZeroVoxels || (a = 0), a += n; + const x = Math.round((o - 0 - a) * this.percentileFrac); + if (x < 1 || s === r) + return t ? this.calMinMax(e, !1) : (R.debug("no variability in image intensity?"), this.cal_min = d, this.cal_max = f, this.robust_min = this.cal_min, this.robust_max = this.cal_max, this.global_min = d, this.global_max = f, [d, f, d, f]); + const w = 1001, D = (w - 1) / (r - s), b = new Array(w); + for (let T = 0; T < w; T++) + b[T] = 0; + if (u) + for (let T = 0; T < o; T++) + b[Math.round((h[T] - s) * D)]++; + else if (this.ignoreZeroVoxels) + for (let T = 0; T < o; T++) + h[T] !== 0 && (isNaN(h[T]) || b[Math.round((h[T] - s) * D)]++); + else + for (let T = 0; T < o; T++) + isNaN(h[T]) || b[Math.round((h[T] - s) * D)]++; + let C = 0, E = 0; + for (; C < x; ) + C += b[E], E++; + E--, C = 0; + let F = w; + for (; C < x; ) + F--, C += b[F]; + if (E === F) { + let T = -1; + for (; T !== 0; ) + E > 0 && (E--, b[E] > 0 && (T = 0)), T !== 0 && F < w - 1 && (F++, b[F] > 0 && (T = 0)), E === 0 && F === w - 1 && (T = 0); + } + let M = this.intensityRaw2Scaled(E / D + s), S = this.intensityRaw2Scaled(F / D + s); + return this.hdr.cal_min < this.hdr.cal_max && this.hdr.cal_min >= d && this.hdr.cal_max <= f && (M = this.hdr.cal_min, S = this.hdr.cal_max), A && (M = Math.min(M, 0)), this.cal_min = M, this.cal_max = S, this.hdr.intent_code === 1002 && (this.cal_min = d, this.cal_max = f), this.robust_min = this.cal_min, this.robust_max = this.cal_max, this.global_min = d, this.global_max = f, [M, S, d, f]; + } + // calMinMax + // not included in public docs + // convert voxel intensity from stored value to scaled intensity + intensityRaw2Scaled(e) { + if (!this.hdr) + throw new Error("hdr undefined"); + return this.hdr.scl_slope === 0 && (this.hdr.scl_slope = 1), e * this.hdr.scl_slope + this.hdr.scl_inter; + } + // convert voxel intensity from scaled intensity to stored value + intensityScaled2Raw(e) { + if (!this.hdr) + throw new Error("hdr undefined"); + return this.hdr.scl_slope === 0 && (this.hdr.scl_slope = 1), (e - this.hdr.scl_inter) / this.hdr.scl_slope; + } + /** + * Converts NVImage to NIfTI compliant byte array, potentially compressed. + * Delegates to ImageWriter.saveToUint8Array. + */ + async saveToUint8Array(e, t = null) { + return Hn(this, e, t); + } + /** + * save image as NIfTI volume and trigger download. + * Delegates to ImageWriter.saveToDisk. + */ + async saveToDisk(e = "", t = null) { + return Yc(this, e, t); + } + static async fetchDicomData(e, t = {}) { + if (e === "") + throw Error("url must not be empty"); + let r = /^(?:[a-z+]+:)?\/\//i.test(e) ? e : new URL(e, window.location.href); + /(?:.([^.]+))?$/.exec(r.pathname) || (r = new URL("niivue-manifest.txt", e)); + let o = await fetch(r, { headers: t }); + if (!o.ok) + throw Error(o.statusText); + const c = (await o.text()).split(` +`), u = /(.*\/).*/.exec(r)[0], d = []; + for (const f of c) { + const g = new URL(f, u); + if (o = await fetch(g, { headers: t }), !o.ok) + throw Error(o.statusText); + const m = await o.arrayBuffer(); + d.push({ name: f, data: m }); + } + return d; + } + static async readFirstDecompressedBytes(e, t) { + const s = e.getReader(), r = new mo(), a = []; + let n = 0, o = !1, l, c; + const h = new Promise((d, f) => { + l = d, c = f; + }); + function u() { + const d = new Uint8Array(n); + let f = 0; + for (const g of a) + d.set(g, f), f += g.length; + l(d); + } + return r.ondata = (d) => { + a.push(d), n += d.length, n >= t && (o = !0, s.cancel().catch(() => { + }), u()); + }, (async () => { + try { + for (; !o; ) { + const { done: d, value: f } = await s.read(); + if (d) { + o = !0, r.push(new Uint8Array(), !0); + return; + } + r.push(f, !1); + } + } catch (d) { + c(d); + } + })().catch(() => { + }), h; + } + static extractFilenameFromUrl(e) { + const s = new URL(e).searchParams.get("response-content-disposition"); + if (s) { + const r = s.match(/filename\*?=(?:UTF-8'')?"?([^";]+)"?/); + if (r) + return decodeURIComponent(r[1]); + } + return e.split("/").pop().split("?")[0]; + } + static async loadInitialVolumesGz(e = "", t = {}, s = NaN) { + if (isNaN(s)) + return null; + const r = await fetch(e, { headers: t, cache: "force-cache" }); + let a = 352, n = await this.readFirstDecompressedBytes(r.body, a); + const o = new DataView(n.buffer, n.byteOffset, n.byteLength), l = o.getUint16(0, !0), c = l === 348; + if (!c && !(l === 23553) || (n.length > 111 && (a = o.getFloat32(108, c)), a > n.length && (n = await this.readFirstDecompressedBytes(r.body, a)), !(n[0] === 92 && n[1] === 1 || n[1] === 92 && n[0] === 1))) + return null; + const d = await Ht(n.buffer); + if (!d) + throw new Error("Could not read NIfTI header"); + const f = d.numBitsPerVoxel / 8, g = [1, 2, 3].reduce((w, D) => w * (d.dims[D] > 1 ? d.dims[D] : 1), 1), m = [4, 5, 6].reduce((w, D) => w * (d.dims[D] > 1 ? d.dims[D] : 1), 1), p = Math.max(Math.min(s, m), 1), v = d.vox_offset + p * g * f; + if (p === m) + return null; + const A = await fetch(e, { headers: t, cache: "force-cache" }); + return (await this.readFirstDecompressedBytes(A.body, v)).buffer.slice(0, v); + } + static async loadInitialVolumes(e = "", t = {}, s = NaN) { + if (isNaN(s)) + return null; + const a = (await fetch(e, { headers: t, cache: "force-cache" })).body.getReader(), { value: n, done: o } = await a.read(); + let l = n; + if (o || !l || l.length < 2) + throw new Error("Not enough data to determine compression"); + const c = new DataView(l.buffer, l.byteOffset, l.byteLength), h = c.getUint16(0, !0); + if (h === 35615) + return await a.cancel(), this.loadInitialVolumesGz(e, t, s); + const d = h === 348; + if (!d && !(h === 23553)) + return await a.cancel(), null; + let g = 352; + for (l.length > 111 && (g = c.getFloat32(108, d)); l.length < g; ) { + let E = function(S, T) { + const k = new Uint8Array(S.length + T.length); + return k.set(S, 0), k.set(T, S.length), k; + }; + const { value: F, done: M } = await a.read(); + if (M || !F) + break; + l = E(l, F); + } + const m = await Ht(l.buffer); + if (!m) + throw new Error("Could not read NIfTI header"); + const p = m.numBitsPerVoxel / 8, v = [1, 2, 3].reduce((E, F) => E * (m.dims[F] > 1 ? m.dims[F] : 1), 1), A = [4, 5, 6].reduce((E, F) => E * (m.dims[F] > 1 ? m.dims[F] : 1), 1), x = Math.max(Math.min(s, A), 1), w = m.vox_offset + x * v * p, D = new Uint8Array(w), b = Math.min(l.length, w); + D.set(l.subarray(0, b), 0); + let C = b; + for (; C < w; ) { + const { value: E, done: F } = await a.read(); + if (F || !E) + return await a.cancel(), null; + const M = Math.min(E.length, w - C); + D.set(E.subarray(0, M), C), C += M; + } + return await a.cancel(), D.buffer; + } + /** + * factory function to load and return a new NVImage instance from a given URL + */ + static async loadFromUrl({ + url: e = "", + urlImgData: t = "", + headers: s = {}, + name: r = "", + colormap: a = "", + opacity: n = 1, + cal_min: o = NaN, + cal_max: l = NaN, + trustCalMinMax: c = !0, + percentileFrac: h = 0.02, + ignoreZeroVoxels: u = !1, + useQFormNotSForm: d = !1, + colormapNegative: f = "", + frame4D: g = 0, + isManifest: m = !1, + limitFrames4D: p = NaN, + imageType: v = $.UNKNOWN, + colorbarVisible: A = !0, + buffer: x = new ArrayBuffer(0) + } = {}) { + if (e === "") + throw Error("url must not be empty"); + let w = null, D = null, b = null; + if (e instanceof Uint8Array && (e = e.slice().buffer), x.byteLength > 0 && (e = x), e instanceof ArrayBuffer) + if (D = e, r !== "") + e = r; + else { + const S = new Uint8Array(D); + e = S[0] === 31 && S[1] === 139 ? "array.nii.gz" : "array.nii"; + } + function C(S) { + const T = S.match(/\.([^.]+)(?:\.gz|\.bz2|\.xz)?$/); + return T ? T[1] : ""; + } + let E = ""; + if (r === "" ? E = C(e) : E = C(r), v === $.UNKNOWN && (v = $.parse(E)), v === $.UNKNOWN && typeof e == "string") { + const S = await fetch(e, {}); + if (S.redirected) { + const T = this.extractFilenameFromUrl(S.url); + T && T.length > 0 && r === "" && (r = T, E = C(r), v = $.parse(E)); + } + } + if (v === $.ZARR) { + const S = new URL(e).searchParams, T = S.get("z"), k = S.get("y"), B = S.get("x"), U = T ? Qt(parseInt(T), parseInt(T) + 1) : null, V = k ? Qt(parseInt(k), parseInt(k) + 1) : null, N = B ? Qt(parseInt(B), parseInt(B) + 1) : null, P = e.split("?")[0], L = new To(P), q = Wo(L); + let X; + try { + X = await at(q.resolve(e), { kind: "array" }); + } catch { + X = await at(q, { kind: "array" }); + } + let O; + if (X.shape.length === 4) { + const ee = X.shape[2], re = X.shape[1], ne = X.shape[0]; + U && U[0] >= ee && (U[0] = ee - 1), V && V[0] >= re && (V[0] = re - 1), N && N[0] >= ne && (N[0] = ne - 1), O = await Fr(X, [N, V, U, null]); + } else + O = await Fr(X, [N, V, U]); + D = O.data; + const [Y, _, W, Q] = O.shape; + b = { + data: D, + width: _, + height: Y, + depth: W, + channels: Q + }; + } + const F = v === $.DCM || $.NII; + if (!D && F && (D = await this.loadInitialVolumes(e, s, p)), !D) + if (m) + D = await Ii.fetchDicomData(e, s), v = $.DCM_MANIFEST; + else { + const S = await fetch(e, { headers: s }); + if (!S.ok) + throw Error(S.statusText); + if (!S.body) + throw new Error("No readable stream available"); + const T = await $i(S.body), k = [], B = T.getReader(); + for (; ; ) { + const { done: P, value: L } = await B.read(); + if (P) + break; + k.push(L); + } + const U = k.reduce((P, L) => P + L.length, 0); + D = new ArrayBuffer(U); + const V = new Uint8Array(D); + let N = 0; + for (const P of k) + V.set(P, N), N += P.length; + } + E.toUpperCase() === "HEAD" && t === "" && (t = e.substring(0, e.lastIndexOf("HEAD")) + "BRIK"), E.toUpperCase() === "HDR" && t === "" && (t = e.substring(0, e.lastIndexOf("HDR")) + "IMG"); + let M = null; + if (t) + try { + let S = await fetch(t, { headers: s }); + if (S.status === 404 && (t.includes("BRIK") || t.includes("IMG")) && (S = await fetch(`${t}.gz`, { headers: s })), S.ok && S.body) { + const T = await $i(S.body), k = [], B = T.getReader(); + for (; ; ) { + const { done: P, value: L } = await B.read(); + if (P) + break; + k.push(L); + } + const U = k.reduce((P, L) => P + L.length, 0); + M = new ArrayBuffer(U); + const V = new Uint8Array(M); + let N = 0; + for (const P of k) + V.set(P, N), N += P.length; + } + } catch (S) { + console.error("Error loading paired image data:", S); + } + if (!D) + throw new Error("Unable to load buffer properly from volume"); + if (!r) { + let S; + try { + S = new URL(e).pathname.split("/"); + } catch { + S = e.split("/"); + } + r = S.slice(-1)[0], r.indexOf("?") > -1 && (r = r.slice(0, r.indexOf("?"))); + } + return w = await this.new( + D, + r, + a, + n, + M, + o, + l, + c, + h, + u, + d, + f, + g, + v, + NaN, + NaN, + !0, + null, + 0, + b + ), w.url = e, w.colorbarVisible = A, w; + } + // not included in public docs + // loading Nifti files + static async readFileAsync(e, t = NaN) { + let s = e.stream(); + if (!isNaN(t)) { + let u = 0; + const d = new TransformStream({ + transform(f, g) { + if (u >= t) { + g.terminate(); + return; + } + const m = t - u; + f.length > m ? (g.enqueue(f.slice(0, m)), g.terminate()) : g.enqueue(f), u += f.length; + } + }); + s = s.pipeThrough(d); + } + const r = await $i(s), a = [], n = r.getReader(); + for (; ; ) { + const { done: u, value: d } = await n.read(); + if (u) + break; + a.push(d); + } + const o = a.reduce((u, d) => u + d.length, 0), l = new ArrayBuffer(o), c = new Uint8Array(l); + let h = 0; + for (const u of a) + c.set(u, h), h += u.length; + return l; + } + /** + * factory function to load and return a new NVImage instance from a file in the browser + */ + static async loadFromFile({ + file: e, + // file can be an array of file objects or a single file object + name: t = "", + colormap: s = "", + opacity: r = 1, + urlImgData: a = null, + cal_min: n = NaN, + cal_max: o = NaN, + trustCalMinMax: l = !0, + percentileFrac: c = 0.02, + ignoreZeroVoxels: h = !1, + useQFormNotSForm: u = !1, + colormapNegative: d = "", + frame4D: f = 0, + limitFrames4D: g = NaN, + imageType: m = $.UNKNOWN + }) { + let p = null, v = []; + try { + if (Array.isArray(e)) + v = await Promise.all(e.map((x) => this.readFileAsync(x))); + else { + if (isNaN(g)) + v = await this.readFileAsync(e); + else { + const x = await this.readFileAsync(e, 512), w = new Uint8Array(x); + if (!(w[0] === 92 && w[1] === 1 || w[1] === 92 && w[0] === 1)) + v = await this.readFileAsync(e); + else { + const b = await Ht(x); + if (!b) + throw new Error("could not read nifti header"); + const C = b.numBitsPerVoxel / 8, E = [1, 2, 3].reduce((T, k) => T * (b.dims[k] > 1 ? b.dims[k] : 1), 1), F = [4, 5, 6].reduce((T, k) => T * (b.dims[k] > 1 ? b.dims[k] : 1), 1), M = Math.max(Math.min(g, F), 1), S = b.vox_offset + M * E * C; + v = await this.readFileAsync(e, S); + } + } + t = e.name; + } + let A = null; + a && (A = await this.readFileAsync(a)), p = await this.new( + v, + t, + s, + r, + A, + n, + o, + l, + c, + h, + u, + d, + f, + m, + NaN, + NaN, + !0, + null, + 0, + null + ), p.fileObject = e; + } catch (A) { + throw R.error(A), new Error("could not build NVImage"); + } + if (p === null) + throw new Error("could not build NVImage"); + return p; + } + /** + * Creates a Uint8Array representing a NIFTI file (header + optional image data). + * Delegates to ImageWriter.createNiftiArray. + */ + static createNiftiArray(e = [256, 256, 256], t = [1, 1, 1], s = [1, 0, 0, -128, 0, 1, 0, -128, 0, 0, 1, -128, 0, 0, 0, 1], r = 2, a = new Uint8Array()) { + return Gc(e, t, s, r, a); + } + /** + * Creates a NIFTI1 header object with basic properties. + * Delegates to ImageWriter.createNiftiHeader. + */ + static createNiftiHeader(e = [256, 256, 256], t = [1, 1, 1], s = [1, 0, 0, -128, 0, 1, 0, -128, 0, 0, 1, -128, 0, 0, 0, 1], r = 2) { + return _n(e, t, s, r); + } + /** + * read a 3D slab of voxels from a volume + * @see {@link https://niivue.com/demos/features/slab_selection.html | live demo usage} + */ + /** + * read a 3D slab of voxels from a volume, specified in RAS coordinates. + * Delegates to VolumeUtils.getVolumeData. + */ + getVolumeData(e = [-1, 0, 0], t = [0, 0, 0], s = "same") { + return qc(this, e, t, s); + } + /** + * write a 3D slab of voxels from a volume + * @see {@link https://niivue.com/demos/features/slab_selection.html | live demo usage} + */ + /** + * write a 3D slab of voxels from a volume, specified in RAS coordinates. + * Delegates to VolumeUtils.setVolumeData. + * Input slabData is assumed to be in the correct raw data type for the target image. + */ + setVolumeData(e = [-1, 0, 0], t = [0, 0, 0], s = new Uint8Array()) { + Hc(this, e, t, s); + } + /** + * factory function to load and return a new NVImage instance from a base64 encoded string + * @example + * myImage = NVImage.loadFromBase64('SomeBase64String') + */ + static async loadFromBase64({ + base64: e, + name: t = "", + colormap: s = "", + opacity: r = 1, + cal_min: a = NaN, + cal_max: n = NaN, + trustCalMinMax: o = !0, + percentileFrac: l = 0.02, + ignoreZeroVoxels: c = !1, + useQFormNotSForm: h = !1, + colormapNegative: u = "", + frame4D: d = 0, + imageType: f = $.UNKNOWN, + cal_minNeg: g = NaN, + cal_maxNeg: m = NaN, + colorbarVisible: p = !0, + colormapLabel: v = null + }) { + function A(w) { + const D = window.atob(w), b = D.length, C = new Uint8Array(b); + for (let E = 0; E < b; E++) + C[E] = D.charCodeAt(E); + return C.buffer; + } + let x = null; + try { + const w = A(e); + x = await this.new( + w, + t, + s, + r, + null, + a, + n, + o, + l, + c, + h, + u, + d, + f, + g, + m, + p, + v, + 0, + null + ); + } catch (w) { + R.debug(w); + } + if (x === null) + throw new Error("could not load NVImage"); + return x; + } + /** + * make a clone of a NVImage instance and return a new NVImage + * @example + * myImage = NVImage.loadFromFile(SomeFileObject) // files can be from dialogs or drag and drop + * clonedImage = myImage.clone() + */ + clone() { + const e = new Ii(); + return e.id = gs(), e.hdr = Object.assign({}, this.hdr), e.img = this.img.slice(), e.calculateRAS(), e.calMinMax(), e; + } + /** + * fill a NVImage instance with zeros for the image data + * @example + * myImage = NVImage.loadFromFile(SomeFileObject) // files can be from dialogs or drag and drop + * clonedImageWithZeros = myImage.clone().zeroImage() + */ + zeroImage() { + this.img.fill(0); + } + /** + * get nifti specific metadata about the image + */ + getImageMetadata() { + if (!this.hdr) + throw new Error("hdr undefined"); + const e = this.id, t = this.hdr.datatypeCode, s = this.hdr.dims, r = s[1], a = s[2], n = s[3], o = Math.max(1, s[4]), l = this.hdr.pixDims, c = l[1], h = l[2], u = l[3], d = l[4], f = Math.floor(this.hdr.numBitsPerVoxel / 8); + return { id: e, datatypeCode: t, nx: r, ny: a, nz: n, nt: o, dx: c, dy: h, dz: u, dt: d, bpv: f }; + } + /** + * a factory function to make a zero filled image given a NVImage as a reference + * @example + * myImage = NVImage.loadFromFile(SomeFileObject) // files can be from dialogs or drag and drop + * newZeroImage = NVImage.zerosLike(myImage) + */ + static zerosLike(e, t = "same") { + const s = e.clone(); + return s.zeroImage(), t === "uint8" && (s.img = Uint8Array.from(s.img), s.hdr.datatypeCode = 2, s.hdr.numBitsPerVoxel = 8), t === "float32" && (s.img = Float32Array.from(s.img), s.hdr.datatypeCode = 16, s.hdr.numBitsPerVoxel = 32), s; + } + /** + * Returns voxel intensity at specific native coordinates. + * Delegates to VolumeUtils.getValue. + */ + getValue(e, t, s, r = 0, a = !1) { + return _c(this, e, t, s, r, a); + } + /** + * Returns voxel intensities at specific native coordinates. + * Delegates to VolumeUtils.getValue. + */ + getValues(e, t, s, r = 0, a = !1) { + return Wn(this, e, t, s, r, a); + } + /** + * Update options for image + */ + applyOptionsUpdate(e) { + this.hdr.cal_min = e.cal_min, this.hdr.cal_max = e.cal_max, Object.assign(this, e); + } + getImageOptions() { + return Si( + "", + // url, + "", + // urlImageData + this.name, + // name + this._colormap, + // colormap + this.opacity, + // opacity + this.hdr.cal_min, + // cal_min + this.hdr.cal_max, + // cal_max + this.trustCalMinMax, + // trustCalMinMax, + this.percentileFrac, + // percentileFrac + this.ignoreZeroVoxels, + // ignoreZeroVoxels + this.useQFormNotSForm, + // useQFormNotSForm + this.colormapNegative, + // colormapNegative + this.frame4D, + this.imageType, + // imageType + this.colormapType + ); + } + /** + * Converts NVImage to NIfTI compliant byte array. + * Handles potential re-orientation of drawing data. + * Delegates to ImageWriter.toUint8Array. + */ + toUint8Array(e = null) { + return qn(this, e); + } + // not included in public docs + convertVox2Frac(e) { + return G( + (e[0] + 0.5) / this.dimsRAS[1], + (e[1] + 0.5) / this.dimsRAS[2], + (e[2] + 0.5) / this.dimsRAS[3] + ); + } + // not included in public docs + convertFrac2Vox(e) { + return G( + Math.round(e[0] * this.dims[1] - 0.5), + // dims === RAS + Math.round(e[1] * this.dims[2] - 0.5), + // dims === RAS + Math.round(e[2] * this.dims[3] - 0.5) + // dims === RAS + ); + } + // not included in public docs + convertFrac2MM(e, t = !1) { + const s = pe(e[0], e[1], e[2], 1); + return t ? Me(s, s, this.frac2mm) : Me(s, s, this.frac2mmOrtho), s; + } + // not included in public docs + convertMM2Frac(e, t = !1) { + const s = pe(e[0], e[1], e[2], 1), r = this.dimsRAS, a = G(0, 0, 0); + if (typeof r > "u") + return a; + if (!t) { + const o = xe(this.frac2mmOrtho); + return Re(o, o), Me(s, s, o), a[0] = s[0], a[1] = s[1], a[2] = s[2], a; + } + if (r[1] < 1 || r[2] < 1 || r[3] < 1) + return a; + const n = xe(this.matRAS); + return Re(n, n), Ne(n, n), Me(s, s, n), a[0] = (s[0] + 0.5) / r[1], a[1] = (s[1] + 0.5) / r[2], a[2] = (s[2] + 0.5) / r[3], a; + } +}, Jn = /* @__PURE__ */ ((i) => (i[i.none = 0] = "none", i[i.contrast = 1] = "contrast", i[i.measurement = 2] = "measurement", i[i.pan = 3] = "pan", i[i.slicer3D = 4] = "slicer3D", i[i.callbackOnly = 5] = "callbackOnly", i[i.roiSelection = 6] = "roiSelection", i[i.angle = 7] = "angle", i[i.crosshair = 8] = "crosshair", i[i.windowing = 9] = "windowing", i))(Jn || {}), Ge = { + textHeight: -1, + fontSizeScaling: 0.4, + fontMinPx: 13, + colorbarHeight: 0.05, + colorbarWidth: -1, + // automatic (full width) + showColorbarBorder: !0, + // show border around the colorbar + crosshairWidth: 1, + crosshairWidthUnit: "voxels", + crosshairGap: 0, + rulerWidth: 4, + show3Dcrosshair: !1, + backColor: [0, 0, 0, 1], + crosshairColor: [1, 0, 0, 1], + fontColor: [0.5, 0.5, 0.5, 1], + selectionBoxColor: [1, 1, 1, 0.5], + clipPlaneColor: [0.7, 0, 0.7, 0.5], + isClipPlanesCutaway: !1, + paqdUniforms: [0.3, 0.5, 0.5, 1], + // paqdUniforms: [0.3, 0.9, 1.0, 0.5], + rulerColor: [1, 0, 0, 0.8], + colorbarMargin: 0.05, + trustCalMinMax: !0, + clipPlaneHotKey: "KeyC", + cycleClipPlaneHotKey: "KeyP", + viewModeHotKey: "KeyV", + doubleTouchTimeout: 500, + longTouchTimeout: 1e3, + keyDebounceTime: 50, + isNearestInterpolation: !1, + isResizeCanvas: !0, + atlasOutline: 0, + atlasActiveIndex: 0, + isRuler: !1, + isColorbar: !1, + isOrientCube: !1, + tileMargin: 0, + multiplanarPadPixels: 0, + // @deprecated + multiplanarForceRender: !1, + multiplanarEqualSize: !1, + multiplanarShowRender: 2, + // auto is the same behaviour as multiplanarForceRender: false + isRadiologicalConvention: !1, + meshThicknessOn2D: 1 / 0, + dragMode: 1, + dragModePrimary: 8, + mouseEventConfig: void 0, + touchEventConfig: void 0, + yoke3Dto2DZoom: !1, + isDepthPickMesh: !1, + isCornerOrientationText: !1, + isOrientationTextVisible: !0, + showAllOrientationMarkers: !1, + heroImageFraction: 0, + heroSliceType: 4, + sagittalNoseLeft: !1, + isSliceMM: !1, + isV1SliceShader: !1, + forceDevicePixelRatio: 0, + logLevel: "info", + loadingText: "loading ...", + isForceMouseClickToVoxelCenters: !1, + dragAndDropEnabled: !0, + drawingEnabled: !1, + penValue: 1, + penType: 0, + floodFillNeighbors: 6, + isFilledPen: !1, + thumbnail: "", + maxDrawUndoBitmaps: 8, + sliceType: 3, + meshXRay: 0, + isAntiAlias: null, + limitFrames4D: NaN, + isAdditiveBlend: !1, + showLegend: !0, + legendBackgroundColor: [0.3, 0.3, 0.3, 0.5], + legendTextColor: [1, 1, 1, 1], + multiplanarLayout: 0, + renderOverlayBlend: 1, + sliceMosaicString: "", + centerMosaic: !1, + penSize: 1, + // in voxels, since all drawing is done using bitmap indices + interactive: !0, + clickToSegment: !1, + clickToSegmentRadius: 3, + // in mm + clickToSegmentBright: !0, + clickToSegmentAutoIntensity: !1, + // new option, but keep clickToSegmentBright for backwards compatibility + clickToSegmentIntensityMax: NaN, + // NaN will use auto threshold (default flood fill behavior from before) + clickToSegmentIntensityMin: NaN, + // NaN will use auto threshold (default flood fill behavior from before) + // 0 will use auto threshold (default flood fill behavior from before) + // Take the voxel intensity at the click point and use this percentage +/- to threshold the flood fill operation. + // If greater than 0, clickedVoxelIntensity +/- clickedVoxelIntensity * clickToSegmentPercent will be used + // for the clickToSegmentIntensityMin and clickToSegmentIntensityMax values. + clickToSegmentPercent: 0, + clickToSegmentMaxDistanceMM: Number.POSITIVE_INFINITY, + // default value is infinity for backwards compatibility with flood fill routine. + clickToSegmentIs2D: !1, + selectionBoxLineThickness: 4, + selectionBoxIsOutline: !1, + scrollRequiresFocus: !1, + // determines if the cavas need to be focused to scroll + showMeasureUnits: !0, + // e.g. 20.2 vs 20.2 mm + measureTextJustify: "center", + // start, center, end + measureTextColor: [1, 0, 0, 1], + // red + measureLineColor: [1, 0, 0, 1], + // red + measureTextHeight: 0.06, + isAlphaClipDark: !1, + gradientOrder: 1, + gradientOpacity: 0, + renderSilhouette: 0, + gradientAmount: 0, + invertScrollDirection: !1, + is2DSliceShader: !1, + bounds: null, + showBoundsBorder: !1, + boundsBorderColor: [1, 1, 1, 1] + // white border by default +}, Xt = { + gamma: 1, + azimuth: 110, + elevation: 10, + crosshairPos: G(0.5, 0.5, 0.5), + clipPlanes: [[0, 0, 0, 0]], + // start with no planes + clipPlaneDepthAziElevs: [[2, 0, 0]], + // empty by default + volScaleMultiplier: 1, + pan2Dxyzmm: pe(0, 0, 0, 1) +}; +function jc(i, e) { + const t = {}; + for (const s in i) { + const r = i[s], a = e[s], n = Array.isArray(r) && Array.isArray(a); + (n && r.some((o, l) => o !== a[l]) || !n && r !== a) && (t[s] = r); + } + return t; +} +var es = class it { + constructor() { + I(this, "data", { + title: "Untitled document", + imageOptionsArray: [], + meshOptionsArray: [], + opts: { ...Ge }, + previewImageDataURL: "", + labels: [], + encodedImageBlobs: [], + encodedDrawingBlob: "" + }), I(this, "scene"), I(this, "volumes", []), I(this, "meshDataObjects"), I(this, "meshes", []), I(this, "drawBitmap", null), I(this, "imageOptionsMap", /* @__PURE__ */ new Map()), I(this, "meshOptionsMap", /* @__PURE__ */ new Map()), I(this, "completedMeasurements", []), I(this, "completedAngles", []), I(this, "_optsProxy", null), I(this, "_optsChangeCallback", null), this.scene = { + onAzimuthElevationChange: () => { + }, + onZoom3DChange: () => { + }, + sceneData: { + ...Xt, + pan2Dxyzmm: pe(0, 0, 0, 1), + crosshairPos: G(0.5, 0.5, 0.5) + }, + get renderAzimuth() { + return this.sceneData.azimuth; + }, + set renderAzimuth(e) { + this.sceneData.azimuth = e, this.onAzimuthElevationChange && this.onAzimuthElevationChange(this.sceneData.azimuth, this.sceneData.elevation); + }, + get renderElevation() { + return this.sceneData.elevation; + }, + set renderElevation(e) { + this.sceneData.elevation = e, this.onAzimuthElevationChange && this.onAzimuthElevationChange(this.sceneData.azimuth, this.sceneData.elevation); + }, + get volScaleMultiplier() { + return this.sceneData.volScaleMultiplier; + }, + set volScaleMultiplier(e) { + this.sceneData.volScaleMultiplier = e, this.onZoom3DChange(e); + }, + get crosshairPos() { + return this.sceneData.crosshairPos; + }, + set crosshairPos(e) { + this.sceneData.crosshairPos = e; + }, + get clipPlane() { + return this.sceneData.clipPlanes[0] ?? []; + }, + set clipPlane(e) { + this.sceneData.clipPlanes[0] = e; + }, + // get clipPlaneDepthAziElev(): number[] { + // return this.sceneData.clipPlaneDepthAziElevs[0] ?? [] + // }, + // set clipPlaneDepthAziElev(clipPlaneDepthAziElev: number[]) { + // this.sceneData.clipPlaneDepthAziElevs[0] = clipPlaneDepthAziElev + // }, + get clipPlanes() { + return this.sceneData.clipPlanes; + }, + set clipPlanes(e) { + this.sceneData.clipPlanes = e; + }, + get clipPlaneDepthAziElevs() { + return this.sceneData.clipPlaneDepthAziElevs; + }, + set clipPlaneDepthAziElevs(e) { + this.sceneData.clipPlaneDepthAziElevs = e; + }, + get pan2Dxyzmm() { + return this.sceneData.pan2Dxyzmm; + }, + /** + * Sets current 2D pan in 3D mm + */ + set pan2Dxyzmm(e) { + this.sceneData.pan2Dxyzmm = e; + }, + get gamma() { + return this.sceneData.gamma; + }, + /** + * Sets current gamma + */ + set gamma(e) { + this.sceneData.gamma = e; + } + }; + } + /** + * Title of the document + */ + get title() { + return this.data.title; + } + /** + * Gets preview image blob + * @returns dataURL of preview image + */ + get previewImageDataURL() { + return this.data.previewImageDataURL; + } + /** + * Sets preview image blob + * @param dataURL - encoded preview image + */ + set previewImageDataURL(e) { + this.data.previewImageDataURL = e; + } + /** + * @param title - title of document + */ + set title(e) { + this.data.title = e; + } + get imageOptionsArray() { + return this.data.imageOptionsArray; + } + /** + * Gets the base 64 encoded blobs of associated images + */ + get encodedImageBlobs() { + return this.data.encodedImageBlobs; + } + /** + * Gets the base 64 encoded blob of the associated drawing + * TODO the return type was marked as string[] here, was that an error? + */ + get encodedDrawingBlob() { + return this.data.encodedDrawingBlob; + } + /** + * Gets the options of the {@link Niivue} instance + */ + get opts() { + return this._optsProxy || this._createOptsProxy(), this._optsProxy; + } + /** + * Sets the options of the {@link Niivue} instance + */ + set opts(e) { + this.data.opts = { ...e }, this._optsProxy = null; + } + /** + * Gets the 3D labels of the {@link Niivue} instance + */ + get labels() { + return this.data.labels; + } + /** + * Sets the 3D labels of the {@link Niivue} instance + */ + set labels(e) { + this.data.labels = e; + } + get customData() { + return this.data.customData; + } + set customData(e) { + this.data.customData = e; + } + /** + * Checks if document has an image by id + */ + hasImage(e) { + return this.volumes.find((t) => t.id === e.id) !== void 0; + } + /** + * Checks if document has an image by url + */ + hasImageFromUrl(e) { + return this.data.imageOptionsArray.find((t) => t.url === e) !== void 0; + } + /** + * Adds an image and the options an image was created with + */ + addImageOptions(e, t) { + if (!this.hasImage(e) && !t.name) + if (t.url) { + const r = /^(?:[a-z+]+:)?\/\//i.test(t.url) ? new URL(t.url) : new URL(t.url, window.location.href); + t.name = r.pathname.split("/").pop(), t.name.toLowerCase().endsWith(".gz") && (t.name = t.name.slice(0, -3)), t.name.toLowerCase().endsWith(".nii") || (t.name += ".nii"); + } else + t.name = "untitled.nii"; + t.imageType = $.NII, this.data.imageOptionsArray.push(t), this.imageOptionsMap.set(e.id, this.data.imageOptionsArray.length - 1); + } + /** + * Removes image from the document as well as its options + */ + removeImage(e) { + if (this.imageOptionsMap.has(e.id)) { + const t = this.imageOptionsMap.get(e.id); + this.data.imageOptionsArray.length > t && this.data.imageOptionsArray.splice(t, 1), this.imageOptionsMap.delete(e.id); + } + this.volumes = this.volumes.filter((t) => t.id !== e.id); + } + /** + * Fetch any image data that is missing from this document. + * This includes loading image blobs for `ImageFromUrlOptions` with valid `url` fields. + * After calling this, `volumes` and `imageOptionsMap` will be populated. + */ + async fetchLinkedData() { + if (this.data.encodedImageBlobs = [], !!this.imageOptionsArray?.length) { + for (const e of this.imageOptionsArray) + if (e.url) + try { + const t = await fetch(e.url); + if (!t.ok) { + console.warn("Failed to fetch image:", e.url); + continue; + } + const s = await t.arrayBuffer(), r = new Uint8Array(s), a = j.uint8tob64(r); + this.data.encodedImageBlobs.push(a), console.info("fetch linked data fetched from ", e.url); + } catch (t) { + console.warn(`Failed to fetch/encode image from ${e.url}:`, t); + } + } + } + /** + * Returns the options for the image if it was added by url + */ + getImageOptions(e) { + return this.imageOptionsMap.has(e.id) ? this.data.imageOptionsArray[this.imageOptionsMap.get(e.id)] : null; + } + /** + * Serialise the document. + * + * @param embedImages If false, encodedImageBlobs is left empty + * (imageOptionsArray still records the URL / name). + * @param embedDrawing If false, encodedDrawingBlob is left empty + */ + json(e = !0, t = !0) { + const s = { + encodedImageBlobs: [], + previewImageDataURL: this.data.previewImageDataURL, + imageOptionsMap: /* @__PURE__ */ new Map() + }, r = []; + s.sceneData = { ...this.scene.sceneData }, delete s.sceneData.clipPlane, delete s.sceneData.clipPlaneDepthAziElev, delete s.sceneData.clipThick, delete s.sceneData.clipVolumeLow, delete s.sceneData.clipVolumeHigh, s.opts = jc(this.opts, Ge), this.opts.meshThicknessOn2D === 1 / 0 && (s.opts.meshThicknessOn2D = "infinity"), this.opts.meshThicknessOn2D === 1 / 0 && (s.opts.meshThicknessOn2D = "infinity"), s.labels = [...this.data.labels]; + for (const n of s.labels) + delete n.onClick; + if (s.customData = this.customData, s.completedMeasurements = [...this.completedMeasurements], s.completedAngles = [...this.completedAngles], this.volumes.length) + for (let n = 0; n < this.volumes.length; n++) { + const o = this.volumes[n]; + let l = this.getImageOptions(o); + if (l === null ? (R.warn("no options found for image, using options from the volume directly"), l = { + name: o?.name ?? "", + colormap: o?._colormap ?? "gray", + opacity: o?._opacity ?? 1, + pairedImgData: null, + cal_min: o?.cal_min ?? NaN, + cal_max: o?.cal_max ?? NaN, + trustCalMinMax: o?.trustCalMinMax ?? !0, + percentileFrac: o?.percentileFrac ?? 0.02, + ignoreZeroVoxels: o?.ignoreZeroVoxels ?? !1, + useQFormNotSForm: o?.useQFormNotSForm ?? !1, + colormapNegative: o?.colormapNegative ?? "", + colormapLabel: o?.colormapLabel ?? null, + imageType: o?.imageType ?? $.NII, + frame4D: o?.frame4D ?? 0, + limitFrames4D: o?.limitFrames4D ?? NaN, + url: o?.url ?? "", + urlImageData: o?.urlImgData ?? "", + alphaThreshold: !1, + cal_minNeg: o?.cal_minNeg ?? NaN, + cal_maxNeg: o?.cal_maxNeg ?? NaN, + colorbarVisible: o?.colorbarVisible ?? !0 + }) : "imageType" in l || (l.imageType = $.NII), l.colormap = o.colormap, l.colormapLabel = o.colormapLabel, l.opacity = o.opacity, l.cal_max = o.cal_max ?? NaN, l.cal_min = o.cal_min ?? NaN, r.push(l), e) { + const c = j.uint8tob64(o.toUint8Array()); + s.encodedImageBlobs.push(c); + } + s.imageOptionsMap.set(o.id, n); + } + s.imageOptionsArray = [...r]; + const a = []; + s.connectomes = []; + for (const n of this.meshes) { + if (n.type === "connectome") { + s.connectomes.push(JSON.stringify(n.json())); + continue; + } + const o = { + pts: n.pts, + tris: n.tris, + name: n.name, + rgba255: Uint8Array.from(n.rgba255), + opacity: n.opacity, + connectome: n.connectome, + dpg: n.dpg, + dps: n.dps, + dpv: n.dpv, + meshShaderIndex: n.meshShaderIndex, + layers: n.layers.map((l) => ({ + values: l.values, + nFrame4D: l.nFrame4D, + frame4D: 0, + outlineBorder: l.outlineBorder, + global_min: l.global_min, + global_max: l.global_max, + cal_min: l.cal_min, + cal_max: l.cal_max, + opacity: l.opacity, + colormap: l.colormap, + colormapNegative: l.colormapNegative, + colormapLabel: l.colormapLabel, + useNegativeCmap: l.useNegativeCmap + })), + hasConnectome: n.hasConnectome, + edgeColormap: n.edgeColormap, + edgeColormapNegative: n.edgeColormapNegative, + edgeMax: n.edgeMax, + edgeMin: n.edgeMin, + edges: n.edges && Array.isArray(n.edges) ? [...n.edges] : [], + extentsMax: n.extentsMax, + extentsMin: n.extentsMin, + furthestVertexFromOrigin: n.furthestVertexFromOrigin, + nodeColormap: n.nodeColormap, + nodeColormapNegative: n.nodeColormapNegative, + nodeMaxColor: n.nodeMaxColor, + nodeMinColor: n.nodeMinColor, + nodeScale: n.nodeScale, + legendLineThickness: n.legendLineThickness, + offsetPt0: n.offsetPt0, + nodes: n.nodes + }; + n.offsetPt0 && n.offsetPt0.length > 0 && (o.offsetPt0 = n.offsetPt0, o.fiberGroupColormap = n.fiberGroupColormap, o.fiberColor = n.fiberColor, o.fiberDither = n.fiberDither, o.fiberRadius = n.fiberRadius, o.colormap = n.colormap), a.push(o); + } + return s.meshesString = JSON.stringify(eo(a)), t && this.drawBitmap && (s.encodedDrawingBlob = j.uint8tob64(this.drawBitmap)), s; + } + async download(e, t, s = { embedImages: !0 }) { + const r = this.json(s.embedImages), a = JSON.stringify(r), n = t ? "application/gzip" : "application/json", o = t ? await j.compressStringToArrayBuffer(a) : a; + j.download(o, e, n); + } + /** + * Deserialize mesh data objects + */ + static deserializeMeshDataObjects(e) { + if (!e.data.meshesString || e.data.meshesString === "[]") { + e.meshDataObjects = []; + return; + } + if (e.data.meshesString) { + e.meshDataObjects = Za(JSON.parse(e.data.meshesString)); + for (const t of e.meshDataObjects) + for (const s of t.layers) + "colorMap" in s && (s.colormap = s.colorMap, delete s.colorMap), "colorMapNegative" in s && (s.colormapNegative = s.colorMapNegative, delete s.colorMapNegative); + } + } + /** + * Factory method to return an instance of NVDocument from a URL + */ + static async loadFromUrl(e) { + const s = await (await fetch(e)).arrayBuffer(); + let r; + if (j.isArrayBufferCompressed(s)) { + const a = await j.decompressArrayBuffer(s); + r = JSON.parse(a); + } else { + const a = new TextDecoder(); + r = JSON.parse(a.decode(s)); + } + return it.loadFromJSON(r); + } + /** + * Factory method to return an instance of NVDocument from a File object + */ + static async loadFromFile(e) { + const t = await j.readFileAsync(e); + let s; + const r = new it(); + return j.isArrayBufferCompressed(t) ? s = await j.decompressArrayBuffer(t) : s = new TextDecoder().decode(t), r.data = JSON.parse(s), r.data.opts.meshThicknessOn2D === "infinity" && (r.data.opts.meshThicknessOn2D = 1 / 0), r.scene.sceneData = { ...Xt, ...r.data.sceneData }, it.deserializeMeshDataObjects(r), r; + } + /** + * Factory method to return an instance of NVDocument from JSON. + * + * This will merge any saved configuration options (`opts`) with the DEFAULT_OPTIONS, + * ensuring any missing values are filled with defaults. It also restores special-case + * fields like `meshThicknessOn2D` when serialized as the string "infinity". + * + * @param data - A serialized DocumentData object + * @returns A reconstructed NVDocument instance + */ + static loadFromJSON(e) { + const t = new it(); + Object.assign(t.data, { + ...e, + imageOptionsArray: e.imageOptionsArray ?? [], + encodedImageBlobs: e.encodedImageBlobs ?? [], + labels: e.labels ?? [], + meshOptionsArray: e.meshOptionsArray ?? [], + connectomes: e.connectomes ?? [], + encodedDrawingBlob: e.encodedDrawingBlob ?? "", + previewImageDataURL: e.previewImageDataURL ?? "", + customData: e.customData ?? "", + title: e.title ?? "untitled" + }), t.data.opts = { + ...Ge, + ...e.opts || {} + }, t.data.opts.meshThicknessOn2D === "infinity" && (t.data.opts.meshThicknessOn2D = 1 / 0), t.scene.sceneData = { + ...Xt, + ...e.sceneData || {} + }; + const s = e.sceneData || {}; + return s.clipPlane && !s.clipPlanes && (t.scene.sceneData.clipPlanes = [s.clipPlane]), s.clipPlaneDepthAziElev && !s.clipPlaneDepthAziElevs && (t.scene.sceneData.clipPlaneDepthAziElevs = [s.clipPlaneDepthAziElev]), e.completedMeasurements && (t.completedMeasurements = e.completedMeasurements.map((r) => ({ + ...r, + startMM: Ye(r.startMM), + endMM: Ye(r.endMM) + }))), e.completedAngles && (t.completedAngles = e.completedAngles.map((r) => ({ + ...r, + firstLineMM: { + start: Ye(r.firstLineMM.start), + end: Ye(r.firstLineMM.end) + }, + secondLineMM: { + start: Ye(r.secondLineMM.start), + end: Ye(r.secondLineMM.end) + } + }))), t.data.meshesString && it.deserializeMeshDataObjects(t), t; + } + /** + * Factory method to return an instance of NVDocument from JSON + */ + static oldloadFromJSON(e) { + const t = new it(); + return t.data = e, t.data.opts.meshThicknessOn2D === "infinity" && (t.data.opts.meshThicknessOn2D = 1 / 0), t.scene.sceneData = { ...Xt, ...e.sceneData }, it.deserializeMeshDataObjects(t), t; + } + /** + * Sets the callback function to be called when opts properties change + */ + setOptsChangeCallback(e) { + this._optsChangeCallback = e, this._optsProxy = null; + } + /** + * Removes the opts change callback + */ + removeOptsChangeCallback() { + this._optsChangeCallback = null, this._optsProxy = null; + } + /** + * Creates a Proxy wrapper around the opts object to detect changes + */ + _createOptsProxy() { + const e = this.data.opts; + this._optsProxy = new Proxy(e, { + set: (t, s, r) => { + const a = t[s]; + return a !== r && (t[s] = r, this._optsChangeCallback && typeof s == "string" && s in Ge && this._optsChangeCallback(s, r, a)), !0; + }, + get: (t, s) => t[s] + }); + } +}, Zc = { + colormap: "gray", + opacity: 0, + nFrame4D: 0, + frame4D: 0, + outlineBorder: 0, + cal_min: 0, + cal_max: 0, + cal_minNeg: 0, + cal_maxNeg: 0, + colormapType: 0, + values: new Array(), + useNegativeCmap: !1, + showLegend: !0 +}, kt = class _e { + /** + * @param pts - a 3xN array of vertex positions (X,Y,Z coordinates). + * @param tris - a 3xN array of triangle indices (I,J,K; indexed from zero). Each triangle generated from three vertices. + * @param name - a name for this image. Default is an empty string + * @param rgba255 - the base color of the mesh. RGBA values from 0 to 255. Default is white + * @param opacity - the opacity for this mesh. default is 1 + * @param visible - whether or not this image is to be visible + * @param gl - WebGL rendering context + * @param connectome - specify connectome edges and nodes. Default is null (not a connectome). + * @param dpg - Data per group for tractography, see TRK format. Default is null (not tractograpgy) + * @param dps - Data per streamline for tractography, see TRK format. Default is null (not tractograpgy) + * @param dpv - Data per vertex for tractography, see TRK format. Default is null (not tractograpgy) + * @param groups - Groups for tractography, see TRK format. Default is null (not tractograpgy) + * @param colorbarVisible - does this mesh display a colorbar + * @param anatomicalStructurePrimary - region for mesh. Default is an empty string + */ + constructor(e, t, s = "", r = new Uint8Array([255, 255, 255, 255]), a = 1, n = !0, o, l = null, c = null, h = null, u = null, d = null, f = !0, g = "") { + I(this, "id"), I(this, "name"), I(this, "anatomicalStructurePrimary"), I(this, "colorbarVisible"), I(this, "furthestVertexFromOrigin"), I(this, "extentsMin"), I(this, "extentsMax"), I(this, "opacity"), I(this, "visible"), I(this, "meshShaderIndex", 0), I(this, "offsetPt0", null), I(this, "colormapInvert", !1), I(this, "fiberGroupColormap", null), I(this, "indexBuffer"), I(this, "vertexBuffer"), I(this, "vao"), I(this, "vaoFiber"), I(this, "pts"), I(this, "tris"), I(this, "layers"), I( + this, + "type", + "mesh" + /* MESH */ + ), I(this, "data_type"), I(this, "rgba255"), I(this, "fiberLength"), I(this, "fiberLengths"), I(this, "fiberDensity"), I(this, "fiberDither", 0.1), I(this, "fiberColor", "Global"), I(this, "fiberDecimationStride", 1), I(this, "fiberSides", 5), I(this, "fiberRadius", 0), I(this, "fiberOcclusion", 0), I(this, "f32PerVertex", 5), I(this, "dpsThreshold", NaN), I(this, "fiberMask"), I(this, "colormap"), I(this, "dpg"), I(this, "dps"), I(this, "dpv"), I(this, "groups"), I(this, "hasConnectome", !1), I(this, "connectome"), I(this, "indexCount"), I(this, "vertexCount", 1), I(this, "nodeScale", 4), I(this, "edgeScale", 1), I(this, "legendLineThickness", 0), I(this, "showLegend", !0), I(this, "nodeColormap", "warm"), I(this, "edgeColormap", "warm"), I(this, "nodeColormapNegative"), I(this, "edgeColormapNegative"), I(this, "nodeMinColor"), I(this, "nodeMaxColor"), I(this, "edgeMin"), I(this, "edgeMax"), I(this, "nodes"), I(this, "edges"), I(this, "points"), this.anatomicalStructurePrimary = g, this.name = s, this.colorbarVisible = f, this.id = gs(); + const m = gt.getExtents(e); + if (this.furthestVertexFromOrigin = m.mxDx, this.extentsMin = m.extentsMin, this.extentsMax = m.extentsMax, this.opacity = a > 1 ? 1 : a, this.visible = n, this.meshShaderIndex = 0, this.indexBuffer = o.createBuffer(), this.vertexBuffer = o.createBuffer(), this.vao = o.createVertexArray(), o.bindVertexArray(this.vao), o.bindBuffer(o.ELEMENT_ARRAY_BUFFER, this.indexBuffer), o.bindBuffer(o.ARRAY_BUFFER, this.vertexBuffer), o.enableVertexAttribArray(0), o.enableVertexAttribArray(1), this.f32PerVertex !== 7 ? (o.vertexAttribPointer(0, 3, o.FLOAT, !1, 20, 0), o.vertexAttribPointer(1, 4, o.BYTE, !0, 20, 12), o.enableVertexAttribArray(2), o.vertexAttribPointer(2, 4, o.UNSIGNED_BYTE, !0, 20, 16)) : (o.vertexAttribPointer(0, 3, o.FLOAT, !1, 28, 0), o.vertexAttribPointer(1, 3, o.FLOAT, !1, 28, 12), o.enableVertexAttribArray(2), o.vertexAttribPointer(2, 4, o.UNSIGNED_BYTE, !0, 28, 24)), o.bindVertexArray(null), this.vaoFiber = o.createVertexArray(), this.offsetPt0 = null, this.hasConnectome = !1, this.colormapInvert = !1, this.fiberGroupColormap = null, this.pts = e, this.layers = [], this.type = "mesh", this.tris = t, r[3] < 1) { + this.rgba255 = r, this.fiberLength = 2, this.fiberDither = 0.1, this.fiberColor = "Global", this.fiberDecimationStride = 1, this.fiberMask = [], this.colormap = l, this.dpg = c, this.dps = h, this.dpv = u, this.groups = d, c && this.initValuesArray(c), h && this.initValuesArray(h), u && this.initValuesArray(u), d && this.initValuesArray(d), this.offsetPt0 = new Uint32Array(t), this.tris = new Uint32Array(0), this.updateFibers(o), o.bindVertexArray(this.vaoFiber), o.bindBuffer(o.ELEMENT_ARRAY_BUFFER, this.indexBuffer), o.bindBuffer(o.ARRAY_BUFFER, this.vertexBuffer), o.enableVertexAttribArray(0), o.vertexAttribPointer(0, 3, o.FLOAT, !1, 16, 0), o.enableVertexAttribArray(1), o.vertexAttribPointer(1, 4, o.UNSIGNED_BYTE, !0, 16, 12), o.bindVertexArray(null); + return; + } + if (l) { + this.connectome = l, this.hasConnectome = !0; + const v = Object.keys(l); + for (let A = 0, x = v.length; A < x; A++) + this[v[A]] = l[v[A]]; + } + this.rgba255 = r, this.updateMesh(o); + } + initValuesArray(e) { + for (let t = 0; t < e.length; t++) { + const s = e[t].vals.reduce((a, n) => Math.min(a, n)), r = e[t].vals.reduce((a, n) => Math.max(a, n)); + e[t].global_min = s, e[t].global_max = r, e[t].cal_min = s, e[t].cal_max = r; + } + return e; + } + // given streamlines (which webGL renders as a single pixel), extrude to cylinders + linesToCylinders(e, t, s) { + function r(F) { + return G(F[0], F[1], F[2]); + } + const a = Math.pow(2, 32) - 1, n = s.length; + let o = 0, l = 0; + for (let F = 0; F < n; F++) { + if (s[F] === a) { + l++; + continue; + } + o++; + } + const c = this.fiberSides, h = c * o, u = this.f32PerVertex; + if (u !== 5) + throw Error("fiberSides > 1 requires f32PerVertex == 5"); + const d = new Float32Array(h * u), f = new Uint8Array(d.buffer); + let g = 0, m = Je(), p = Je(), v = Je(); + const A = le(); + let x = le(), w = 0; + const D = this.fiberRadius; + for (let F = 0; F < n; F++) { + const M = s[F] === a; + if (M && w < 1) + continue; + let S = s[F] * 4; + if (w++, w <= 1) { + m = pe(t[S + 0], t[S + 1], t[S + 2], t[S + 3]), p = nt(m), F + 1 < n && s[F + 1] !== a && (S = s[F + 1] * 4, v = pe(t[S + 0], t[S + 1], t[S + 2], t[S + 3]), de(A, r(m), r(v)), Pe(A, A), x = xt.getFirstPerpVector(A)); + continue; + } + M ? v = nt(p) : v = pe(t[S + 0], t[S + 1], t[S + 2], t[S + 3]), de(A, r(m), r(v)), Pe(A, A); + const T = le(); + Ci(T, x, A); + const k = le(); + Ci(k, A, T), x = Ye(x); + const B = le(); + Ci(B, A, k), Pe(B, B); + const U = le(); + for (let V = 0; V < c; V++) { + const N = Math.cos(V / c * 2 * Math.PI), P = Math.sin(V / c * 2 * Math.PI); + U[0] = D * (N * k[0] + P * B[0]), U[1] = D * (N * k[1] + P * B[1]), U[2] = D * (N * k[2] + P * B[2]), At(U, r(p), U); + const L = g * u; + d[L + 0] = U[0], d[L + 1] = U[1], d[L + 2] = U[2]; + const q = le(); + de(q, U, r(p)), Pe(q, q); + const X = (L + 3) * 4; + f[X + 0] = q[0] * 127, f[X + 1] = q[1] * 127, f[X + 2] = q[2] * 127, d[L + 4] = p[3], g++; + } + m = nt(p), p = nt(v), M && (w = 0); + } + const b = (o - l) * c * 2 * 3, C = new Uint32Array(b); + let E = 0; + g = 0; + for (let F = 1; F < n; F++) { + if (s[F] === a) { + g += c; + continue; + } + if (s[F - 1] === a) + continue; + let M = g, S = g + c; + const T = S, k = S + c; + for (let B = 0; B < c; B++) + C[E++] = M, C[E++] = S++, S === k && (S = k - c), C[E++] = S, C[E++] = M++, M === T && (M = T - c), C[E++] = S, C[E++] = M; + g += c; + } + e.bindBuffer(e.ELEMENT_ARRAY_BUFFER, this.indexBuffer), e.bufferData(e.ELEMENT_ARRAY_BUFFER, Uint32Array.from(C), e.STATIC_DRAW), e.bindBuffer(e.ARRAY_BUFFER, this.vertexBuffer), e.bufferData(e.ARRAY_BUFFER, f, e.STATIC_DRAW), this.indexCount = b; + } + // linesToCylinders + createFiberDensityMap() { + if (this.fiberDensity) + return; + const e = this.pts, t = e.length / 3; + let s = 0; + for (let A = 0; A < 3; A++) { + const x = this.extentsMax[A] - this.extentsMin[A]; + s = Math.max(s, x); + } + if (this.fiberDensity = new Float32Array(t), s === 0) + return; + const r = 64, n = s / (r - 1) / 2, o = (r - 1) / s; + let l = new Float32Array(r * r * r); + const c = [this.extentsMin[0] - n, this.extentsMin[1] - n, this.extentsMin[2] - n], h = [0, 0, 0], u = -1, d = r * r; + let f = 0; + for (let A = 0; A < t; A++) { + h[0] = Math.round((e[f++] - c[0]) * o), h[1] = Math.round((e[f++] - c[1]) * o), h[2] = Math.round((e[f++] - c[2]) * o); + const x = h[0] + h[1] * r + h[2] * d; + x !== u && l[x]++; + } + function g(A, x) { + let w = A.slice(), D = -1; + const b = x - 1; + for (let E = 0; E < x; E++) + for (let F = 0; F < x; F++) + for (let M = 0; M < x; M++) + D++, !(M < 1 || M >= b) && (A[D] = w[D - 1] + w[D] + w[D] + w[D + 1]); + D = -1, w = A.slice(); + for (let E = 0; E < x; E++) + for (let F = 0; F < x; F++) + for (let M = 0; M < x; M++) + D++, !(F < 1 || F >= b) && (A[D] = w[D - x] + w[D] + w[D] + w[D + x]); + const C = x * x; + D = -1, w = A.slice(); + for (let E = 0; E < x; E++) + for (let F = 0; F < x; F++) + for (let M = 0; M < x; M++) + D++, !(E < 1 || E >= b) && (A[D] = w[D - C] + w[D] + w[D] + w[C]); + return A; + } + l = g(l, r), l = g(l, r); + let m = 0, p = 1 / 0; + const v = r * r * r; + for (let A = 0; A < v; A++) + l[A] <= 0 || (m = Math.max(m, l[A]), p = Math.min(p, l[A])); + if (!(m <= 1 || m <= p)) { + f = 0; + for (let A = 0; A < v; A++) + l[A] = Math.max(0, l[A] - p); + m -= p; + for (let A = 0; A < t; A++) { + h[0] = Math.round((e[f++] - c[0]) * o), h[1] = Math.round((e[f++] - c[1]) * o), h[2] = Math.round((e[f++] - c[2]) * o); + const x = h[0] + h[1] * r + h[2] * d; + this.fiberDensity[A] = l[x] / m; + } + } + } + // not included in public docs + // internal function filters tractogram to identify which color and visibility of streamlines + updateFibers(e) { + if (!this.offsetPt0 || !this.fiberLength) + return; + const t = this.pts, s = this.offsetPt0, r = s.length - 1, a = t.length / 3; + if (!this.fiberLengths) { + this.fiberLengths = new Uint32Array(r); + for (let b = 0; b < r; b++) { + const C = s[b] * 3, E = (s[b + 1] - 1) * 3; + let F = 0; + for (let M = C; M < E; M += 3) { + const S = G(t[M + 0] - t[M + 3], t[M + 1] - t[M + 4], t[M + 2] - t[M + 5]); + F += ei(S); + } + this.fiberLengths[b] = F; + } + } + const n = new Float32Array(a * 4), o = new Uint32Array(n.buffer); + let l = 0, c = 0; + for (let b = 0; b < a; b++) + n[c + 0] = t[l + 0], n[c + 1] = t[l + 1], n[c + 2] = t[l + 2], l += 3, c += 4; + const h = this.fiberDither, u = h * 0.5; + function d(b, C, E) { + const M = 255 * (h * Math.random() - u); + return b = Math.max(Math.min(b + M, 255), 0), C = Math.max(Math.min(C + M, 255), 0), E = Math.max(Math.min(E + M, 255), 0), b + (C << 8) + (E << 16); + } + function f(b, C, E, F, M, S, T) { + const k = G(Math.abs(b - F), Math.abs(C - M), Math.abs(E - S)); + Pe(k, k); + const B = T - u; + for (let U = 0; U < 3; U++) + k[U] = 255 * Math.max(Math.min(Math.abs(k[U]) + B, 1), 0); + return k[0] + (k[1] << 8) + (k[2] << 16); + } + const g = this.fiberColor.toLowerCase(); + let m = null, p = null; + if (g.startsWith("dps") && this.dps && this.dps.length > 0) { + const b = parseInt(g.substring(3)); + b < this.dps.length && this.dps[b].vals.length === r && (m = this.dps[b].vals); + } + if (g.startsWith("dpv") && this.dpv && this.dpv.length > 0) { + const b = parseInt(g.substring(3)); + b < this.dpv.length && this.dpv[b].vals.length === a && (p = this.dpv[b]); + } + const v = new Int16Array(r); + if (this.groups && this.fiberGroupColormap !== null || g.startsWith("dpg") && this.dpg.length > 0) { + const b = new Uint8ClampedArray(this.groups.length * 4), C = new Array(this.groups.length).fill(!1); + if (this.fiberGroupColormap) { + const E = this.fiberGroupColormap; + E.A === void 0 && (E.A = Array.from(new Uint8ClampedArray(E.I.length).fill(255))); + for (let F = 0; F < E.I.length; F++) { + let M = E.I[F]; + M < 0 || M >= this.groups.length || E.A[F] < 1 || (C[M] = !0, M *= 4, b[M] = E.R[F], b[M + 1] = E.G[F], b[M + 2] = E.B[F], b[M + 3] = 255); + } + } else if (g.startsWith("dpg") && this.dpg.length > 0) { + const E = parseInt(g.substring(3)), F = E < this.dpg.length ? this.dpg[E] : this.dpg[0], M = oe.colormap(this.colormap, this.colormapInvert), S = F.cal_min, T = F.cal_max, k = this.groups.length; + for (let B = 0; B < k; B++) { + const U = F.vals[B]; + if (U < S) + continue; + let V = Math.round(255 * Math.min(Math.max((U - S) / (T - S), 0), 1)); + C[B] = !0; + const N = B * 4; + V *= 4, b[N] = M[V + 0], b[N + 1] = M[V + 1], b[N + 2] = M[V + 2], b[N + 3] = 255; + } + } + v.fill(-1); + for (let E = 0; E < this.groups.length; E++) + if (C[E]) + for (let F = 0; F < this.groups[E].vals.length; F++) + v[this.groups[E].vals[F]] = E; + for (let E = 0; E < r; E++) { + if (v[E] < 0) + continue; + const F = v[E] % 256 * 4, M = d(b[F], b[F + 1], b[F + 2]), S = s[E], T = s[E + 1] - 1, k = S * 4 + 3, B = T * 4 + 3; + for (let U = k; U <= B; U += 4) + o[U] = M; + } + } else if (p) { + const b = oe.colormap(this.colormap, this.colormapInvert), C = p.cal_min, E = p.cal_max; + let F = 3; + for (let M = 0; M < a; M++) { + let S = Math.min(Math.max((p.vals[M] - C) / (E - C), 0), 1); + S = Math.round(Math.max(Math.min(255, S * 255))) * 4; + const T = b[S] + (b[S + 1] << 8) + (b[S + 2] << 16); + o[F] = T, F += 4; + } + } else if (m) { + const b = oe.colormap(this.colormap, this.colormapInvert); + let C = m[0], E = m[0]; + for (let F = 0; F < r; F++) + C = Math.min(C, m[F]), E = Math.max(E, m[F]); + E === C && (C -= 1); + for (let F = 0; F < r; F++) { + let M = (m[F] - C) / (E - C); + M = Math.round(Math.max(Math.min(255, M * 255))) * 4; + const S = b[M] + (b[M + 1] << 8) + (b[M + 2] << 16), T = s[F], k = s[F + 1] - 1, B = T * 4 + 3, U = k * 4 + 3; + for (let V = B; V <= U; V += 4) + o[V] = S; + } + } else if (g.includes("fixed")) + if (h === 0) { + const b = this.rgba255[0] + (this.rgba255[1] << 8) + (this.rgba255[2] << 16); + let C = 3; + for (let E = 0; E < a; E++) + o[C] = b, C += 4; + } else + for (let b = 0; b < r; b++) { + const C = d(this.rgba255[0], this.rgba255[1], this.rgba255[2]), E = s[b], F = s[b + 1] - 1, M = E * 4 + 3, S = F * 4 + 3; + for (let T = M; T <= S; T += 4) + o[T] = C; + } + else if (g.includes("local")) + for (let b = 0; b < r; b++) { + const C = s[b], E = s[b + 1] - 1; + let F = C * 3; + const M = E * 3, S = h * Math.random(); + let T = f(t[F], t[F + 1], t[F + 2], t[F + 4], t[F + 5], t[F + 6], S), k = C * 4 + 3; + for (; F < M; ) + o[k] = T, k += 4, F += 3, T = f(t[F - 3], t[F - 2], t[F - 1], t[F + 3], t[F + 4], t[F + 5], S); + o[k] = o[k - 4]; + } + else + for (let b = 0; b < r; b++) { + const C = s[b], E = s[b + 1] - 1, F = C * 3, M = E * 3, S = f( + t[F], + t[F + 1], + t[F + 2], + t[M], + t[M + 1], + t[M + 2], + h * Math.random() + ), T = C * 4 + 3, k = E * 4 + 3; + for (let B = T; B <= k; B += 4) + o[B] = S; + } + if (this.fiberOcclusion > 0) { + let b = function(C, E) { + const F = E * (C & 255), M = E * (C >> 8 & 255), S = E * (C >> 16 & 255); + return F + (M << 8) + (S << 16); + }; + this.createFiberDensityMap(); + for (let C = 0; C < r; C++) { + const E = s[C], F = s[C + 1] - 1, M = E * 4 + 3, S = F * 4 + 3; + let T = E; + const k = Math.min(this.fiberOcclusion, 0.99); + for (let B = M; B <= S; B += 4) { + let U = this.fiberDensity[T++]; + if (U <= 0) + continue; + U = U / ((1 / k - 2) * (1 - U) + 1); + const V = 1 - Math.min(U, 0.9); + let N = o[B]; + N = b(N, V), o[B] = N; + } + } + } + if (Number.isFinite(this.dpsThreshold) && this.dps && (m || this.dps[0].vals.length === r && (m = this.dps[0].vals), m)) + for (let b = 0; b < r; b++) + m[b] < this.dpsThreshold && (v[b] = -1); + const A = this.fiberLength, x = Math.pow(2, 32) - 1, w = []; + let D = -1; + for (let b = 0; b < r; b++) + if (!(v[b] < 0) && !(this.fiberLengths[b] < A) && (D++, D % this.fiberDecimationStride === 0)) { + for (let C = s[b]; C < s[b + 1]; C++) + w.push(C); + w.push(x); + } + this.fiberSides > 2 && this.fiberRadius > 0 ? this.linesToCylinders(e, n, w) : (this.indexCount = w.length, e.bindBuffer(e.ARRAY_BUFFER, this.vertexBuffer), e.bufferData(e.ARRAY_BUFFER, Uint32Array.from(o), e.STATIC_DRAW), e.bindBuffer(e.ELEMENT_ARRAY_BUFFER, this.indexBuffer), e.bufferData(e.ELEMENT_ARRAY_BUFFER, Uint32Array.from(w), e.STATIC_DRAW)); + } + // updateFibers() + // given X,Y,Z coordinates in world space, return index of nearest vertex as well as + // the distance of this closest vertex to the coordinates + indexNearestXYZmm(e, t, s) { + const r = this.pts, a = this.pts.length / 3; + let n = 0, o = 1 / 0, l = 0; + for (let c = 0; c < a; c++) { + const h = Math.pow(r[n] - e, 2) + Math.pow(r[n + 1] - t, 2) + Math.pow(r[n + 2] - s, 2); + h < o && (o = h, l = c), n += 3; + } + return o = Math.sqrt(o), [l, o]; + } + // indexNearestXYZmm() + // internal function discards GPU resources + unloadMesh(e) { + if (e.bindBuffer(e.ARRAY_BUFFER, null), e.bindBuffer(e.ELEMENT_ARRAY_BUFFER, null), e.bindVertexArray(null), e.deleteBuffer(this.vertexBuffer), e.deleteBuffer(this.indexBuffer), e.deleteVertexArray(this.vao), e.deleteVertexArray(this.vaoFiber), this.offsetPt0 = null, this.tris = null, this.pts = null, this.layers && this.layers.length > 0) + for (let t = 0; t < this.layers.length; t++) + this.layers[t].values = null; + if (this.dpg && this.dpg.length > 0) + for (let t = 0; t < this.dpg.length; t++) + this.dpg[t].vals = null; + if (this.dps && this.dps.length > 0) + for (let t = 0; t < this.dps.length; t++) + this.dps[t].vals = null; + } + // apply color lookup table to convert scalar array to RGBA array + scalars2RGBA(e, t, s, r = !1) { + const a = s.length; + if (4 * a < e.length) + return R.error(`colormap2RGBA incorrectly specified ${a}*4 != ${e.length}`), e; + const n = Math.round(t.opacity * 255); + let o = t.cal_min, l = t.cal_max, c = oe.colormap(t.colormap, this.colormapInvert), h = 1; + if (r) { + if (!t.useNegativeCmap) + return e; + h = -1, c = oe.colormap(t.colormapNegative, t.colormapInvert), o = t.cal_min, l = t.cal_max, isFinite(t.cal_minNeg) && isFinite(t.cal_minNeg) && (o = -t.cal_minNeg, l = -t.cal_maxNeg); + } + let u = o; + t.isTransparentBelowCalMin || (u = Number.NEGATIVE_INFINITY); + const d = t.colormapType === 2; + t.colormapType !== 0 && (o = Math.min(o, 0)); + const f = 255 / (l - o); + for (let g = 0; g < a; g++) { + let m = s[g] * h; + if (isNaN(m)) + continue; + let p = n; + if (m < u) + if (m > 0 && d) + p = Math.round(t.opacity * 255 * Math.pow(m / u, 2)); + else + continue; + if (m = (m - o) * f, m < 0 && t.isTransparentBelowCalMin) + continue; + m = Math.min(255, Math.max(0, Math.round(m))) * 4; + const v = g * 4; + e[v + 0] = c[m + 0], e[v + 1] = c[m + 1], e[v + 2] = c[m + 2], e[v + 3] = p; + } + return e; + } + blendColormap(e, t, s, r, a, n, o = !1) { + const l = this.pts.length / 3, c = Math.min(s.opacity, 1); + function h(x, w, D) { + return x * (1 - D) + w * D; + } + function u(x, w) { + return Math.min(x + w, 255); + } + const d = o ? -1 : 1, f = Math.min(Math.max(s.frame4D, 0), s.nFrame4D - 1), g = l * f; + let m = r; + s.isTransparentBelowCalMin || (m = Number.NEGATIVE_INFINITY), s.colormapType !== 0 && (r = Math.min(r, 0)); + const p = 255 / (a - r); + let v = new Array(l).fill(!1); + if (s.outlineBorder !== 0) { + const x = new Uint8Array(l).fill(0); + for (let w = 0; w < l; w++) + d * s.values[w + g] >= m && (x[w] = 1); + v = gt.getClusterBoundaryU8(x, this.tris); + for (let w = 0; w < l; w++) + d * s.values[w + g] < m && (v[w] = !1); + } + const A = new Float32Array(256).fill(c); + if (m > r && s.colormapType === 2) { + let x = Math.round((m - r) * p); + x = Math.max(x, 1); + for (let w = 1; w < x; w++) + A[w] = c * Math.pow(w / x, 2); + A[0] = 0, m = r + Number.EPSILON; + } + for (let x = 0; x < l; x++) { + const w = d * s.values[x + g]; + if (w < m) + continue; + let D = Math.round((w - r) * p); + if (D < 0 && s.isTransparentBelowCalMin) + continue; + D = Math.max(0, D), D = Math.min(255, D); + let b = A[D]; + D *= 4; + let C = x * 28 + 24; + if (this.f32PerVertex !== 7 && (C = x * 20 + 16), s.isAdditiveBlend) { + const E = x * 4; + t[E + 0] = u(t[E + 0], n[D + 0]), t[E + 1] = u(t[E + 1], n[D + 1]), t[E + 2] = u(t[E + 2], n[D + 2]), t[E + 3] = u(t[E + 3], 255); + } else { + if (v[x] && (b = s.outlineBorder, s.outlineBorder < 0)) { + e[C + 0] = 0, e[C + 1] = 0, e[C + 2] = 0; + continue; + } + e[C + 0] = h(e[C + 0], n[D + 0], b), e[C + 1] = h(e[C + 1], n[D + 1], b), e[C + 2] = h(e[C + 2], n[D + 2], b); + } + } + } + // blendColormap() + // internal function filters mesh to identify which color of triangulated mesh vertices + updateMesh(e) { + if (this.offsetPt0) { + this.updateFibers(e); + return; + } + if (this.hasConnectome) + return; + if (!this.pts || !this.tris || !this.rgba255) { + R.warn("underspecified mesh"); + return; + } + function t(c, h, u) { + return c * (1 - u) + h * u; + } + const s = this.generatePosNormClr(this.pts, this.tris, this.rgba255), r = this.pts.length / 3, a = new Uint8Array(s.buffer); + let n = 0; + const o = new Uint8Array(r * 4); + let l = this.tris; + if (this.layers && this.layers.length > 0) + for (let c = 0; c < this.layers.length; c++) { + const h = this.layers[c], u = h.opacity; + if (u <= 0 || h.cal_min > h.cal_max) + continue; + if (h.outlineBorder === void 0 && (h.outlineBorder = 0), h.isAdditiveBlend === void 0 && (h.isAdditiveBlend = !1), h.colormapLabel && h.colormapLabel.R && !h.colormapLabel.lut && (h.colormapLabel = oe.makeLabelLut(h.colormapLabel, 255, h.global_max)), h.colormapLabel && h.colormapLabel.lut) { + const f = h.colormapLabel; + let g = 0; + h.colormapLabel.min && (g = h.colormapLabel.min); + let m = f.lut; + const p = Math.floor(m.length / 4); + if (h.atlasValues && p > 0 && p === h.atlasValues.length && h.colormap) { + const b = h.atlasValues; + let C = !1, E = !0; + for (let F = 0; F < p; F++) + isNaN(b[F]) ? C = !0 : E = !1; + if (E) { + R.debug("invisible mesh: all atlasValues are NaN."); + return; + } + if (C) { + R.debug("some vertices have NaN atlasValues (mesh will be decimated)."); + const F = new Array(r).fill(!1); + for (let B = 0; B < r; B++) { + const U = Math.round(h.values[B]) - g; + isNaN(b[U]) && (F[B] = !0); + } + const M = new Array(l.length).fill(!1); + for (let B = 0; B < l.length; B++) + F[l[B]] && (M[B] = !0); + const S = this.tris; + let T = 0; + for (let B = 0; B < S.length; B += 3) + !M[B] && !M[B + 1] && !M[B + 2] && T++; + T === 0 && R.debug("invisible mesh: all triangles of a vertex with a NaN atlasValue."), l = new Uint32Array(T * 3); + let k = 0; + for (let B = 0; B < S.length; B += 3) + !M[B] && !M[B + 1] && !M[B + 2] && (l[k++] = S[B], l[k++] = S[B + 1], l[k++] = S[B + 2]); + } + m.fill(0), m = this.scalars2RGBA(m, h, b), h.useNegativeCmap && (m = this.scalars2RGBA(m, h, b, !0)); + } else h.atlasValues && R.warn(`Expected ${p} atlasValues but got ${h.atlasValues.length} for mesh layer`); + if (h.showLegend && p === h.colormapLabel.labels.length) { + h.labels = []; + for (let b = 0; b < p; b++) { + const C = Array.from(m.slice(b * 4, b * 4 + 4)).map((T) => T / 255), E = h.colormapLabel.labels[b], F = [0, 0, 0]; + let M = 0; + for (let T = 0; T < r; T++) + if (h.values[T] === b) { + const k = T * 3; + F[0] += this.pts[k], F[1] += this.pts[k + 1], F[2] += this.pts[k + 2], M++; + } + if (M > 0 && (F[0] /= M, F[1] /= M, F[2] /= M), C[3] === 0 || !E || // handles empty string, null, undefined + E.startsWith("_")) + continue; + C[3] = 1; + const S = new Ys( + E, + { + textColor: C, + bulletScale: 1, + bulletColor: C, + lineWidth: 0, + lineColor: C, + textScale: 1, + textAlignment: "left", + lineTerminator: "none" + /* NONE */ + }, + F + ); + h.labels.push(S), R.debug("label for mesh layer:", S); + } + } else + delete h.labels; + const v = Math.min(Math.max(h.frame4D, 0), h.nFrame4D - 1), A = r * v, x = new Uint8Array(r * 4); + let w = 0; + for (let b = 0; b < r; b++) { + const C = h.values[b + A] - g, E = 4 * Math.min(Math.max(C, 0), p - 1); + x[w + 0] = m[E + 0], x[w + 1] = m[E + 1], x[w + 2] = m[E + 2], x[w + 3] = Math.round(m[E + 3] / 255 * h.opacity * 255), w += 4; + } + let D = new Array(r).fill(!1); + h.outlineBorder !== 0 && (D = gt.getClusterBoundary(x, this.tris)), w = 0; + for (let b = 0; b < r; b++) { + let C = b * 28 + 24; + this.f32PerVertex !== 7 && (C = b * 20 + 16); + let E = x[w + 3] / 255; + if (D[b] && (E = h.outlineBorder, h.outlineBorder < 0)) { + a[C + 0] = 0, a[C + 1] = 0, a[C + 2] = 0, w += 4; + continue; + } + a[C + 0] = t(a[C + 0], x[w + 0], E), a[C + 1] = t(a[C + 1], x[w + 1], E), a[C + 2] = t(a[C + 2], x[w + 2], E), w += 4; + } + continue; + } + if (h.values instanceof Uint8Array) { + const f = new Uint8Array(h.values.buffer); + let g = new Array(r).fill(!0); + h.outlineBorder !== 0 && (g = gt.getClusterBoundary(f, this.tris)); + let m = 0; + for (let p = 0; p < h.values.length; p++) { + let v = p * 28 + 24; + this.f32PerVertex !== 7 && (v = p * 20 + 16); + let A = u; + if (g[p] && (A = h.outlineBorder, h.outlineBorder < 0)) { + a[v + 0] = 0, a[v + 1] = 0, a[v + 2] = 0, m += 4; + continue; + } + a[v + 0] = t(a[v + 0], f[m + 0], A), a[v + 1] = t(a[v + 1], f[m + 1], A), a[v + 2] = t(a[v + 2], f[m + 2], A), m += 4; + } + continue; + } + h.useNegativeCmap && (h.cal_min = Math.max(Number.EPSILON, h.cal_min), h.cal_max = Math.max(h.cal_min + 1e-6, h.cal_max)), h.isTransparentBelowCalMin === void 0 && (h.isTransparentBelowCalMin = !0); + const d = oe.colormap(h.colormap, h.colormapInvert); + if (h.isAdditiveBlend && n++, this.blendColormap(a, o, h, h.cal_min, h.cal_max, d), h.useNegativeCmap) { + const f = oe.colormap(h.colormapNegative, h.colormapInvert); + let g = h.cal_min, m = h.cal_max; + isFinite(h.cal_minNeg) && isFinite(h.cal_minNeg) && (g = -h.cal_minNeg, m = -h.cal_maxNeg), this.blendColormap(a, o, h, g, m, f, !0); + } + } + if (n > 0) + for (let c = 0; c < r; c++) { + let h = function(g, m) { + return Math.min(g * m * 0.00392156862745098, 255); + }, u = c * 28 + 24; + this.f32PerVertex !== 7 && (u = c * 20 + 16); + const d = c * 4, f = Math.min(n, o[d + 3] / 255); + f <= 0 || (a[u + 0] = h(a[u + 0], o[d + 0]), a[u + 1] = h(a[u + 1], o[d + 1]), a[u + 2] = h(a[u + 2], o[d + 2]), a[u + 0] = t(a[u + 0], o[d + 0], f), a[u + 1] = t(a[u + 1], o[d + 1], f), a[u + 2] = t(a[u + 2], o[d + 2], f)); + } + e.bindBuffer(e.ELEMENT_ARRAY_BUFFER, this.indexBuffer), e.bufferData(e.ELEMENT_ARRAY_BUFFER, Uint32Array.from(l), e.STATIC_DRAW), e.bindBuffer(e.ARRAY_BUFFER, this.vertexBuffer), e.bufferData(e.ARRAY_BUFFER, a, e.STATIC_DRAW), this.indexCount = l.length, this.vertexCount = this.pts.length; + } + // updateMesh() + // internal function filters mesh to identify which color of triangulated mesh vertices + reverseFaces(e) { + if (this.offsetPt0 || this.hasConnectome) + return; + const t = this.tris || []; + for (let s = 0; s < t.length; s += 3) { + const r = t[s]; + t[s] = t[s + 1], t[s + 1] = r; + } + this.updateMesh(e); + } + hierarchicalOrder() { + const s = this.tris.length / 3, r = Math.log(s / 20) / Math.log(4); + if (s !== Math.pow(4, r) * 20) + return NaN; + if (this.pts.length / 3 !== Math.pow(4, r) * 10 + 2) + return NaN; + for (let n = 0; n < 15; n += 3) + if (this.tris[n] !== 0) + return NaN; + for (let n = 15; n < 24; n += 3) + if (this.tris[n] !== 3) + return NaN; + for (let n = 24; n < 30; n += 3) + if (this.tris[n] !== 4) + return NaN; + return r; + } + decimateFaces(e, t) { + let s = this.tris; + const r = 12, a = 20; + for (let n = e - 1; n >= t; n--) { + const o = Math.pow(4, n + 1) * (r - 2) + 2, l = Math.pow(4, n) * (r - 2) + 2, c = s.length / 3, h = Math.pow(4, n) * a; + R.info(`order ${n + 1} -> ${n} vertices ${o} -> ${l} faces ${c} -> ${h}`); + const u = Array.from({ length: o }, (f, g) => g + 1); + for (let f = 0; f < c; f++) { + const g = s[3 * f], m = s[3 * f + 1], p = s[3 * f + 2]; + u[g - 1] = Math.min(u[g - 1], m, p); + } + const d = new Uint32Array(h * 3); + for (let f = 0; f < h; f++) + d[3 * f] = u[s[3 * f] - 1], d[3 * f + 1] = u[s[3 * f + 1] - 1], d[3 * f + 2] = u[s[3 * f + 2] - 1]; + s = d; + } + this.tris = new Uint32Array(s); + } + // internal function simplifies FreeSurfer triangulated mesh and overlays + decimateHierarchicalMesh(e, t = 4) { + const s = this.hierarchicalOrder(); + if (isNaN(s)) + return R.warn("Unable to decimate mesh: it does not have a hierarchical structure"), !1; + if (t >= s) + return R.warn(`Unable to decimate mesh: input order (${s}) must be larger than downsampled order (${t})`), !1; + const r = this.pts.length / 3, n = Math.pow(4, t) * (12 - 2) + 2; + if (this.pts = new Float32Array(this.pts.slice(0, n * 3)), this.decimateFaces(s, t), this.layers && this.layers.length > 0) + for (let o = 0; o < this.layers.length; o++) { + const l = this.layers[o]; + l.values instanceof Float32Array || l.values.length !== r ? l.values = new Float32Array(l.values.slice(0, n)) : R.warn("decimation logic needs to be updated"); + } + return this.updateMesh(e), !0; + } + // adjust attributes of a mesh layer. invoked by niivue.setMeshLayerProperty() + // TODO this method is a bit too generic + async setLayerProperty(e, t, s, r) { + const a = this.layers[e]; + if (!a || !(t in a)) { + R.warn("mesh does not have property ", t, " for layer ", a); + return; + } + if (t === "colormapLabel") + if (typeof s == "object") + a[t] = oe.makeLabelLut(s, 255, a.global_max); + else if (typeof s == "string") { + const n = await oe.makeLabelLutFromUrl(s); + a[t] = n, this.updateMesh(r); + return; + } else + R.error("colormapLabel requires a string or object"); + else + a[t] = s; + this.updateMesh(r); + } + // adjust mesh attributes. invoked by niivue.setMeshProperty(() + // TODO this method is too generic + setProperty(e, t, s) { + if (!(e in this)) { + console.warn("Mesh does not have property:", e, this); + return; + } + this[e] = t, this.updateMesh(s); + } + // Each streamline vertex has color, normal and position attributes + // Interleaved Vertex Data https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/TechniquesforWorkingwithVertexData/TechniquesforWorkingwithVertexData.html + generatePosNormClr(e, t, s) { + (e.length < 3 || s.length < 4) && (R.error("Catastrophic failure generatePosNormClr()"), R.debug("this", this), R.debug("pts", e), R.debug("rgba", s)); + const r = gt.generateNormals(e, t), a = e.length / 3, n = a === s.length / 4, o = this.f32PerVertex, l = new Float32Array(a * o), c = new Uint8Array(l.buffer); + let h = 0, u = 0, d = 0, f = (o - 1) * 4; + for (let g = 0; g < a; g++) + l[d + 0] = e[h + 0], l[d + 1] = e[h + 1], l[d + 2] = e[h + 2], o !== 7 ? (c[f - 4] = r[h + 0] * 127, c[f - 3] = r[h + 1] * 127, c[f - 2] = r[h + 2] * 127) : (l[d + 3] = r[h + 0], l[d + 4] = r[h + 1], l[d + 5] = r[h + 2]), c[f] = s[u + 0], c[f + 1] = s[u + 1], c[f + 2] = s[u + 2], c[f + 3] = s[u + 3], n && (u += 4), h += 3, d += o, f += o * 4; + return l; + } + // wrapper to read meshes, tractograms and connectomes regardless of format + static async readMesh(e, t, s, r = 1, a = new Uint8Array([255, 255, 255, 255]), n = !0) { + let o = new Uint32Array([]), l = new Float32Array([]), c = "", h; + const u = /(?:\.([^.]+))?$/; + let d = u.exec(t)[1]; + if (d = d.toUpperCase(), d === "GZ" && (d = u.exec(t.slice(0, -3))[1], d = d.toUpperCase()), d === "JCON" && R.error("you should never see this message: load using nvconnectome not nvmesh"), d === "JSON" && R.error("you should never see this message: load using nvconnectome not nvmesh"), a[3] = Math.max(1, a[3]), d === "TCK" || d === "TRK" || d === "TT" || d === "TRX" || d === "TRACT") { + if (d === "TCK" ? h = ge.readTCK(e) : d === "TRACT" ? h = ge.readTRACT(e) : d === "TT" ? h = await ge.readTT(e) : d === "TRX" ? h = await ge.readTRX(e) : h = await ge.readTRK(e), typeof h > "u") { + const p = new Float32Array([0, 0, 0, 0, 0, 0]), v = new Uint32Array([0]); + h = { pts: p, offsetPt0: v }, R.error("Creating empty tracts"); + } + return a[3] = 0, new _e( + h.pts, + h.offsetPt0, + t, + a, + // colormap, + r, + // opacity, + n, + // visible, + s, + "inferno", + h.dpg || null, + h.dps || null, + h.dpv || null, + h.groups + ); + } + if (d === "GII") + h = await ge.readGII(e); + else if (d === "MZ3") + h = await ge.readMZ3(e), "positions" in h || R.warn("MZ3 does not have positions (statistical overlay?)"); + else if (d === "ASC") + h = ge.readASC(e); + else if (d === "DFS") + h = ge.readDFS(e); + else if (d === "BYU" || d === "G") + h = ge.readGEO(e); + else if (d === "GEO") + h = ge.readGEO(e, !0); + else if (d === "ICO" || d === "TRI") + h = ge.readICO(e); + else if (d === "OFF") + h = ge.readOFF(e); + else if (d === "NV") + h = ge.readNV(e); + else if (d === "OBJ") + h = await ge.readOBJ(e); + else if (d === "PLY") + h = ge.readPLY(e); + else if (d === "WRL") + h = ge.readWRL(e); + else if (d === "X3D") + h = ge.readX3D(e); + else if (d === "FIB" || d === "VTK") { + if (h = ge.readVTK(e), "offsetPt0" in h) + return a[3] = 0, new _e( + h.pts, + h.offsetPt0, + t, + a, + // colormap, + r, + // opacity, + n, + // visible, + s, + "inferno" + ); + } else d === "SRF" ? h = await ge.readSRF(e) : d === "STL" ? h = ge.readSTL(e) : h = ge.readFreeSurfer(e); + if (h.anatomicalStructurePrimary && (c = h.anatomicalStructurePrimary), h instanceof Float32Array) + throw new Error("fatal: unknown mesh type loaded"); + if (!("positions" in h)) + throw new Error("positions not loaded"); + if (!h.indices) + throw new Error("indices not loaded"); + if (l = h.positions, o = h.indices, "rgba255" in h && h.rgba255.length > 0 && (a = h.rgba255), "colors" in h && h.colors && h.colors.length === l.length) { + const p = l.length / 3; + a = new Uint8Array(p * 4); + let v = 0, A = 0; + for (let x = 0; x < p; x++) + a[A++] = h.colors[v] * 255, a[A++] = h.colors[v + 1] * 255, a[A++] = h.colors[v + 2] * 255, a[A++] = 255, v += 3; + } + const f = l.length / 3; + if (o.length / 3 < 1 || f < 3) + throw new Error("Mesh should have at least one triangle and three vertices"); + a[3] = Math.max(1, a[3]); + const m = new _e( + l, + o, + t, + a, + // colormap, + r, + // opacity, + n, + // visible, + s, + null, + // connectome + null, + // dpg + null, + // dps + null, + // dpv + null, + // groups + !0, + // colorbarVisible + c + ); + if ("scalars" in h && h.scalars.length > 0) { + const p = await ge.readLayer(t, e, m, r, "gray"); + typeof p > "u" ? R.warn("readLayer() failed to convert scalars") : (m.layers.push(p), m.updateMesh(s)); + } + return m; + } + static async loadLayer(e, t) { + let s = new Uint8Array().buffer; + function r(g) { + const m = window.atob(g), p = m.length, v = new Uint8Array(p); + for (let A = 0; A < p; A++) + v[A] = m.charCodeAt(A); + return v.buffer; + } + if (e.base64 !== void 0) + s = r(e.base64); + else { + if (!e.url) + throw new Error("layer: missing url"); + const g = await fetch(e.url, { headers: e.headers }); + if (!g.ok) + throw Error(g.statusText); + s = await g.arrayBuffer(); + } + let a, n = []; + if (e.name && e.name !== "") + a = e.name; + else { + if (!e.url) + throw new Error("layer: missing url"); + try { + n = new URL(e.url).pathname.split("/"); + } catch { + n = e.url.split("/"); + } finally { + a = n.slice(-1)[0]; + } + } + a.indexOf("?") > -1 && (a = a.slice(0, a.indexOf("?"))); + let o = 0.5; + "opacity" in e && (o = e.opacity); + let l = "warm"; + "colormap" in e && (l = e.colormap); + let c = "winter"; + "colormapNegative" in e && (c = e.colormapNegative); + let h = !1; + "useNegativeCmap" in e && (h = e.useNegativeCmap); + let u = null; + "cal_min" in e && (u = e.cal_min); + let d = null; + "cal_max" in e && (d = e.cal_max); + const f = await ge.readLayer( + a, + s, + t, + o, + l, + c, + h, + u, + d + ); + f && t.layers.push(f); + } + /** + * factory function to load and return a new NVMesh instance from a given URL + */ + static async loadFromUrl({ + url: e = "", + headers: t = {}, + gl: s, + name: r = "", + opacity: a = 1, + rgba255: n = [255, 255, 255, 255], + visible: o = !0, + layers: l = [], + buffer: c = new ArrayBuffer(0) + } = {}) { + let h = e.split("/"); + if (r === "") { + try { + h = new URL(e).pathname.split("/"); + } catch { + h = e.split("/"); + } + r = h.slice(-1)[0], r.indexOf("?") > -1 && (r = r.slice(0, r.indexOf("?"))); + } + if (e === "") + throw Error("url must not be empty"); + if (!s) + throw Error("gl context is null"); + let u; + if (c.byteLength > 0) + u = c; + else { + const f = await fetch(e, { headers: t }); + if (!f.ok) + throw Error(f.statusText); + u = await f.arrayBuffer(); + } + const d = await this.readMesh(u, r, s, a, new Uint8Array(n), o); + if (!l || l.length < 1) + return d; + for (let f = 0; f < l.length; f++) + await _e.loadLayer(l[f], d); + return d.updateMesh(s), d; + } + // not included in public docs + // loading Nifti files + static async readFileAsync(e) { + return new Promise((t, s) => { + const r = new FileReader(); + r.onload = () => { + t(r.result); + }, r.onerror = s, r.readAsArrayBuffer(e); + }); + } + /** + * factory function to load and return a new NVMesh instance from a file in the browser + * + * @returns NVMesh instance + */ + static async loadFromFile({ + file: e, + gl: t, + name: s = "", + opacity: r = 1, + rgba255: a = [255, 255, 255, 255], + visible: n = !0, + layers: o = [] + } = {}) { + if (!e) + throw new Error("file must be set"); + if (!t) + throw new Error("rendering context must be set"); + const l = await _e.readFileAsync(e), c = await _e.readMesh(l, s, t, r, new Uint8Array(a), n); + if (!o || o.length < 1) + return c; + for (let h = 0; h < o.length; h++) + await _e.loadLayer(o[h], c); + return c.updateMesh(t), c; + } + /** + * load and return a new NVMesh instance from a base64 encoded string + */ + async loadFromBase64({ + base64: e, + gl: t, + name: s = "", + opacity: r = 1, + rgba255: a = [255, 255, 255, 255], + visible: n = !0, + layers: o = [] + } = {}) { + if (!e) + throw new Error("base64 must bet set"); + if (!t) + throw new Error("rendering context must be set"); + function l(u) { + const d = window.atob(u), f = d.length, g = new Uint8Array(f); + for (let m = 0; m < f; m++) + g[m] = d.charCodeAt(m); + return g.buffer; + } + const c = l(e), h = await _e.readMesh(c, s, t, r, new Uint8Array(a), n); + if (!o || o.length < 1) + return h; + for (let u = 0; u < o.length; u++) + await _e.loadLayer(o[u], h); + return h.updateMesh(t), h; + } +}, Qc = "data:image/jpeg;base64,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ZwAAAAAAAAALY3JvcFJlY3RUb3Bsb25nAAAAAAA4QklNA+0AAAAAABAASAAAAAEAAQBIAAAAAQABOEJJTQQmAAAAAAAOAAAAAAAAAAAAAD+AAAA4QklNBA0AAAAAAAQAAAAeOEJJTQQZAAAAAAAEAAAAHjhCSU0D8wAAAAAACQAAAAAAAAAAAQA4QklNJxAAAAAAAAoAAQAAAAAAAAABOEJJTQP1AAAAAABIAC9mZgABAGxmZgAGAAAAAAABAC9mZgABAKGZmgAGAAAAAAABADIAAAABAFoAAAAGAAAAAAABADUAAAABAC0AAAAGAAAAAAABOEJJTQP4AAAAAABwAAD/////////////////////////////A+gAAAAA/////////////////////////////wPoAAAAAP////////////////////////////8D6AAAAAD/////////////////////////////A+gAADhCSU0EAAAAAAAAAgAAOEJJTQQCAAAAAAACAAA4QklNBDAAAAAAAAEBADhCSU0ELQAAAAAABgABAAAAMjhCSU0ECAAAAAAAEAAAAAEAAAJAAAACQAAAAAA4QklNBB4AAAAAAAQAAAAAOEJJTQQaAAAAAAM9AAAABgAAAAAAAAAAAAACAAAAAgAAAAAEAG0AYwA2ADEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAEAAAAAAABudWxsAAAAAgAAAAZib3VuZHNPYmpjAAAAAQAAAAAAAFJjdDEAAAAEAAAAAFRvcCBsb25nAAAAAAAAAABMZWZ0bG9uZwAAAAAAAAAAQnRvbWxvbmcAAAIAAAAAAFJnaHRsb25nAAACAAAAAAZzbGljZXNWbExzAAAAAU9iamMAAAABAAAAAAAFc2xpY2UAAAASAAAAB3NsaWNlSURsb25nAAAAAAAAAAdncm91cElEbG9uZwAAAAAAAAAGb3JpZ2luZW51bQAAAAxFU2xpY2VPcmlnaW4AAAANYXV0b0dlbmVyYXRlZAAAAABUeXBlZW51bQAAAApFU2xpY2VUeXBlAAAAAEltZyAAAAAGYm91bmRzT2JqYwAAAAEAAAAAAABSY3QxAAAABAAA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bnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wOkNyZWF0ZURhdGU9IjIwMTgtMDctMTJUMjA6MDc6NDcrMDE6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDE4LTA3LTEzVDE3OjU0OjI3KzAxOjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDE4LTA3LTEzVDE3OjU0OjI3KzAxOjAwIiBkYzpmb3JtYXQ9ImltYWdlL2pwZWciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo3MmVhYjVmZS02NDZkLWVhNDMtODQyNC0xNWQzZTgwOWExNDQiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDo0OGNjMGI3YS1iYTJkLWZmNDAtYmNkNC1jNGQzMzIyZjBiNzQiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDphYzFjM2NhNS1hYTg4LWI5NGMtOGE4MS05YmU4MjA3ZGZiODciPiA8cGhvdG9zaG9wOkRvY3VtZW50QW5jZXN0b3JzPiA8cmRmOkJhZz4gPHJkZjpsaT5hZG9iZTpkb2NpZDpwaG90b3Nob3A6YThjMGNhZTYtODNjYy1hYzQ4LWI2YzAtN2FiODNlOWJkMGM3PC9yZGY6bGk+IDwvcmRmOkJhZz4gPC9waG90b3Nob3A6RG9jdW1lbnRBbmNlc3RvcnM+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6YWMxYzNjYTUtYWE4OC1iOTRjLThhODEtOWJlODIwN2RmYjg3IiBzdEV2dDp3aGVuPSIyMDE4LTA3LTEyVDIwOjA3OjQ3KzAxOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgKFdpbmRvd3MpIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpjYmI3MzZlZC0zOTRhLTM2NDgtYThiOC02Mzk1MmY0NDcxMjIiIHN0RXZ0OndoZW49IjIwMTgtMDctMTNUMTY6MTM6NTgrMDE6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAoV2luZG93cykiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjQ2YjNkYTI3LWI4YjEtYmU0Yi1hYTQ4LTExZGM5NmZkNTIyZiIgc3RFdnQ6d2hlbj0iMjAxOC0wNy0xM1QxNzo1NDoyNyswMTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY29udmVydGVkIiBzdEV2dDpwYXJhbWV0ZXJzPSJmcm9tIGFwcGxpY2F0aW9uL3ZuZC5hZG9iZS5waG90b3Nob3AgdG8gaW1hZ2UvanBlZyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iZGVyaXZlZCIgc3RFdnQ6cGFyYW1ldGVycz0iY29udmVydGVkIGZyb20gYXBwbGljYXRpb24vdm5kLmFkb2JlLnBob3Rvc2hvcCB0byBpbWFnZS9qcGVnIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo3MmVhYjVmZS02NDZkLWVhNDMtODQyNC0xNWQzZTgwOWExNDQiIHN0RXZ0OndoZW49IjIwMTgtMDctMTNUMTc6NTQ6MjcrMDE6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAoV2luZG93cykiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjQ2YjNkYTI3LWI4YjEtYmU0Yi1hYTQ4LTExZGM5NmZkNTIyZiIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjg3NjM1NDk5LWZiMTItYWU0MC04NWEzLThkMWFiN2NkZGVhOSIgc3RSZWY6b3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOmFjMWMzY2E1LWFhODgtYjk0Yy04YTgxLTliZTgyMDdkZmI4NyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8P3hwYWNrZXQgZW5kPSJ3Ij8+/+IMWElDQ19QUk9GSUxFAAEBAAAMSExpbm8CEAAAbW50clJHQiBYWVogB84AAgAJAAYAMQAAYWNzcE1TRlQAAAAASUVDIHNSR0IAAAAAAAAAAAAAAAEAAPbWAAEAAAAA0y1IUCAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARY3BydAAAAVAAAAAzZGVzYwAAAYQAAABsd3RwdAAAAfAAAAAUYmtwdAAAAgQAAAAUclhZWgAAAhgAAAAUZ1hZWgAAAiwAAAAUYlhZWgAAAkAAAAAUZG1uZAAAAlQAAABwZG1kZAAAAsQAAACIdnVlZAAAA0wAAACGdmlldwAAA9QAAAAkbHVtaQAAA/gAAAAUbWVhcwAABAwAAAAkdGVjaAAABDAAAAAMclRSQwAABDwAAAgMZ1RSQwAABDwAAAgMYlRSQwAABDwAAAgMdGV4dAAAAABDb3B5cmlnaHQgKGMpIDE5OTggSGV3bGV0dC1Q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", kr = "data:image/png;base64,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", Rr = { + atlas: { + type: "msdf", + distanceRange: 2, + size: 59.65625, + width: 512, + height: 256, + yOrigin: "bottom" + }, + metrics: { + emSize: 1, + lineHeight: 1.171875, + ascender: 0.927734375, + descender: -0.244140625, + underlineY: -0.09765625, + underlineThickness: 0.048828125 + }, + glyphs: [ + { unicode: 32, advance: 0.24755859375 }, + { + unicode: 33, + advance: 0.25732421875, + planeBounds: { + left: 0.056159633438645884, + bottom: -0.02437761405677056, + right: 0.20702396031135412, + top: 0.7299440203067705 + }, + atlasBounds: { + left: 488.5, + bottom: 145.5, + right: 497.5, + top: 190.5 + } + }, + { + unicode: 34, + advance: 0.31982421875, + planeBounds: { + left: 0.049409125974004715, + bottom: 0.48691155587022, + right: 0.2840869677759953, + top: 0.77187750662978 + }, + atlasBounds: { + left: 486.5, + bottom: 213.5, + right: 500.5, + top: 230.5 + } + }, + { + unicode: 35, + advance: 0.61572265625, + planeBounds: { + left: 0.037219103997511785, + bottom: -0.02169206718177056, + right: 0.6239137085024882, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 66.5, + bottom: 51.5, + right: 101.5, + top: 96.5 + } + }, + { + unicode: 36, + advance: 0.5615234375, + planeBounds: { + left: 0.02956531458715296, + bottom: -0.12381369908983761, + right: 0.5324464041628472, + top: 0.8484230740898377 + }, + atlasBounds: { + left: 109.5, + bottom: 197.5, + right: 139.5, + top: 255.5 + } + }, + { + unicode: 37, + advance: 0.732421875, + planeBounds: { + left: 0.026481776289942378, + bottom: -0.030073418674698794, + right: 0.7137525987100576, + top: 0.7410109186746989 + }, + atlasBounds: { + left: 88.5, + bottom: 144.5, + right: 129.5, + top: 190.5 + } + }, + { + unicode: 38, + advance: 0.62158203125, + planeBounds: { + left: 0.03225572125458355, + bottom: -0.030073418674698794, + right: 0.6357130287454166, + top: 0.7410109186746989 + }, + atlasBounds: { + left: 130.5, + bottom: 144.5, + right: 166.5, + top: 190.5 + } + }, + { + unicode: 39, + advance: 0.17431640625, + planeBounds: { + left: 0.028244602049502358, + bottom: 0.49895501673814824, + right: 0.14558352295049765, + top: 0.7671582645118518 + }, + atlasBounds: { + left: 498.5, + bottom: 62.5, + right: 505.5, + top: 78.5 + } + }, + { + unicode: 40, + advance: 0.341796875, + planeBounds: { + left: 0.042983329377291775, + bottom: -0.250029542422407, + right: 0.34471198312270823, + top: 0.8227834486724072 + }, + atlasBounds: { + left: 0.5, + bottom: 191.5, + right: 18.5, + top: 255.5 + } + }, + { + unicode: 41, + advance: 0.34765625, + planeBounds: { + left: -0.003159248747708225, + bottom: -0.250029542422407, + right: 0.29856940499770823, + top: 0.8227834486724072 + }, + atlasBounds: { + left: 19.5, + bottom: 191.5, + right: 37.5, + top: 255.5 + } + }, + { + unicode: 42, + advance: 0.4306640625, + planeBounds: { + left: -0.011208599684062338, + bottom: 0.27785390031593765, + right: 0.44138438093406235, + top: 0.7304468809340623 + }, + atlasBounds: { + left: 449.5, + bottom: 23.5, + right: 476.5, + top: 50.5 + } + }, + { + unicode: 43, + advance: 0.56689453125, + planeBounds: { + left: 0.01353503347629649, + bottom: 0.053493525733368255, + right: 0.5499415290237036, + top: 0.6066627242666317 + }, + atlasBounds: { + left: 361.5, + bottom: 17.5, + right: 393.5, + top: 50.5 + } + }, + { + unicode: 44, + advance: 0.1962890625, + planeBounds: { + left: -0.009919475797210583, + bottom: -0.15981695975478, + right: 0.1744702570472106, + top: 0.12514899100478 + }, + atlasBounds: { + left: 498.5, + bottom: 79.5, + right: 509.5, + top: 96.5 + } + }, + { + unicode: 45, + advance: 0.27587890625, + planeBounds: { + left: -0.00527594412977999, + bottom: 0.24333249267450235, + right: 0.27969000662978, + top: 0.36067141357549765 + }, + atlasBounds: { left: 52.5, bottom: 7.5, right: 69.5, top: 14.5 } + }, + { + unicode: 46, + advance: 0.26318359375, + planeBounds: { + left: 0.051032680313645884, + bottom: -0.027092319686354116, + right: 0.20189700718635412, + top: 0.12377200718635412 + }, + atlasBounds: { + left: 501.5, + bottom: 221.5, + right: 510.5, + top: 230.5 + } + }, + { + unicode: 47, + advance: 0.412109375, + planeBounds: { + left: -0.013733006073205867, + bottom: -0.08573505127848349, + right: 0.4053345685732059, + top: 0.7356373950284835 + }, + atlasBounds: { + left: 252.5, + bottom: 206.5, + right: 277.5, + top: 255.5 + } + }, + { + unicode: 48, + advance: 0.5615234375, + planeBounds: { + left: 0.037458384830081196, + bottom: -0.030073418674698794, + right: 0.5235767714199189, + top: 0.7410109186746989 + }, + atlasBounds: { + left: 167.5, + bottom: 144.5, + right: 196.5, + top: 190.5 + } + }, + { + unicode: 49, + advance: 0.5615234375, + planeBounds: { + left: 0.06023674350936354, + bottom: -0.01998308280677056, + right: 0.37872810024063647, + top: 0.7343385515567705 + }, + atlasBounds: { + left: 488.5, + bottom: 97.5, + right: 507.5, + top: 142.5 + } + }, + { + unicode: 50, + advance: 0.5615234375, + planeBounds: { + left: 0.025334353719224725, + bottom: -0.01680925468177056, + right: 0.5449781462807752, + top: 0.7375123796817705 + }, + atlasBounds: { + left: 278.5, + bottom: 51.5, + right: 309.5, + top: 96.5 + } + }, + { + unicode: 51, + advance: 0.5615234375, + planeBounds: { + left: 0.028181041080081196, + bottom: -0.030073418674698794, + right: 0.5142994276699189, + top: 0.7410109186746989 + }, + atlasBounds: { + left: 197.5, + bottom: 144.5, + right: 226.5, + top: 190.5 + } + }, + { + unicode: 52, + advance: 0.5615234375, + planeBounds: { + left: 0.005886103858368255, + bottom: -0.02169206718177056, + right: 0.5590553023916317, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 310.5, + bottom: 51.5, + right: 343.5, + top: 96.5 + } + }, + { + unicode: 53, + advance: 0.5615234375, + planeBounds: { + left: 0.055524791080081196, + bottom: -0.02657487968177056, + right: 0.5416431776699189, + top: 0.7277467546817705 + }, + atlasBounds: { + left: 344.5, + bottom: 51.5, + right: 373.5, + top: 96.5 + } + }, + { + unicode: 54, + advance: 0.5615234375, + planeBounds: { + left: 0.046003306705081196, + bottom: -0.034712090549698794, + right: 0.5321216932949189, + top: 0.7363722467996989 + }, + atlasBounds: { + left: 227.5, + bottom: 144.5, + right: 256.5, + top: 190.5 + } + }, + { + unicode: 55, + advance: 0.5615234375, + planeBounds: { + left: 0.018010134969224725, + bottom: -0.02169206718177056, + right: 0.5376539275307752, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 374.5, + bottom: 51.5, + right: 405.5, + top: 96.5 + } + }, + { + unicode: 56, + advance: 0.5615234375, + planeBounds: { + left: 0.037702525455081196, + bottom: -0.030073418674698794, + right: 0.5238209120449189, + top: 0.7410109186746989 + }, + atlasBounds: { + left: 257.5, + bottom: 144.5, + right: 286.5, + top: 190.5 + } + }, + { + unicode: 57, + advance: 0.5615234375, + planeBounds: { + left: 0.029401744205081196, + bottom: -0.025434746799698794, + right: 0.5155201307949189, + top: 0.7456495905496989 + }, + atlasBounds: { + left: 287.5, + bottom: 144.5, + right: 316.5, + top: 190.5 + } + }, + { + unicode: 58, + advance: 0.2421875, + planeBounds: { + left: 0.046394008438645884, + bottom: -0.029431286627488215, + right: 0.19725833531135412, + top: 0.5572633178774882 + }, + atlasBounds: { + left: 439.5, + bottom: 61.5, + right: 448.5, + top: 96.5 + } + }, + { + unicode: 59, + advance: 0.21142578125, + planeBounds: { + left: 0.001066852327789419, + bottom: -0.16459733294591408, + right: 0.1854565851722106, + top: 0.556198895445914 + }, + atlasBounds: { + left: 406.5, + bottom: 53.5, + right: 417.5, + top: 96.5 + } + }, + { + unicode: 60, + advance: 0.50830078125, + planeBounds: { + left: 0.016948142433865897, + bottom: 0.0726146348300812, + right: 0.4527784200661341, + top: 0.5587330214199189 + }, + atlasBounds: { + left: 394.5, + bottom: 21.5, + right: 420.5, + top: 50.5 + } + }, + { + unicode: 61, + advance: 0.548828125, + planeBounds: { + left: 0.051535540940937666, + bottom: 0.17620354038436353, + right: 0.5041285215590624, + top: 0.49469489711563647 + }, + atlasBounds: { + left: 477.5, + bottom: 31.5, + right: 504.5, + top: 50.5 + } + }, + { + unicode: 62, + advance: 0.5224609375, + planeBounds: { + left: 0.047629290940937666, + bottom: 0.0731029160800812, + right: 0.5002222715590624, + top: 0.5592213026699189 + }, + atlasBounds: { + left: 421.5, + bottom: 21.5, + right: 448.5, + top: 50.5 + } + }, + { + unicode: 63, + advance: 0.47216796875, + planeBounds: { + left: 0.016704001808865897, + bottom: -0.027876153049698794, + right: 0.4525342794411341, + top: 0.7432081842996989 + }, + atlasBounds: { + left: 317.5, + bottom: 144.5, + right: 343.5, + top: 190.5 + } + }, + { + unicode: 64, + advance: 0.89794921875, + planeBounds: { + left: 0.034064457306783605, + bottom: -0.23896750384690937, + right: 0.8721996065996072, + top: 0.7165065663469093 + }, + atlasBounds: { + left: 155.5, + bottom: 198.5, + right: 205.5, + top: 255.5 + } + }, + { + unicode: 65, + advance: 0.65234375, + planeBounds: { + left: -0.008838044092129387, + bottom: -0.02169206718177056, + right: 0.6616700753421295, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 237.5, + bottom: 51.5, + right: 277.5, + top: 96.5 + } + }, + { + unicode: 66, + advance: 0.62255859375, + planeBounds: { + left: 0.06464099434422473, + bottom: -0.02169206718177056, + right: 0.5842847869057752, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 205.5, + bottom: 51.5, + right: 236.5, + top: 96.5 + } + }, + { + unicode: 67, + advance: 0.65087890625, + planeBounds: { + left: 0.038439807122511785, + bottom: -0.030073418674698794, + right: 0.6251344116274882, + top: 0.7410109186746989 + }, + atlasBounds: { + left: 344.5, + bottom: 144.5, + right: 379.5, + top: 190.5 + } + }, + { + unicode: 68, + advance: 0.65576171875, + planeBounds: { + left: 0.06301501010836826, + bottom: -0.02169206718177056, + right: 0.6161842086416317, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 162.5, + bottom: 51.5, + right: 195.5, + top: 96.5 + } + }, + { + unicode: 69, + advance: 0.568359375, + planeBounds: { + left: 0.0652904160800812, + bottom: -0.02169206718177056, + right: 0.5514088026699189, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 132.5, + bottom: 51.5, + right: 161.5, + top: 96.5 + } + }, + { + unicode: 70, + advance: 0.552734375, + planeBounds: { + left: 0.059675181705081196, + bottom: -0.02169206718177056, + right: 0.5457935682949189, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 102.5, + bottom: 51.5, + right: 131.5, + top: 96.5 + } + }, + { + unicode: 71, + advance: 0.68115234375, + planeBounds: { + left: 0.040148791497511785, + bottom: -0.030073418674698794, + right: 0.6268433960024882, + top: 0.7410109186746989 + }, + atlasBounds: { + left: 380.5, + bottom: 144.5, + right: 415.5, + top: 190.5 + } + }, + { + unicode: 72, + advance: 0.712890625, + planeBounds: { + left: 0.062365588372511785, + bottom: -0.02169206718177056, + right: 0.6490601928774882, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 30.5, + bottom: 51.5, + right: 65.5, + top: 96.5 + } + }, + { + unicode: 73, + advance: 0.27197265625, + planeBounds: { + left: 0.06917965680657412, + bottom: -0.02169206718177056, + right: 0.20328128069342588, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 196.5, + bottom: 51.5, + right: 204.5, + top: 96.5 + } + }, + { + unicode: 74, + advance: 0.5517578125, + planeBounds: { + left: 0.007184947330081194, + bottom: -0.02657487968177056, + right: 0.4933033339199188, + top: 0.7277467546817705 + }, + atlasBounds: { left: 0.5, bottom: 51.5, right: 29.5, top: 96.5 } + }, + { + unicode: 75, + advance: 0.626953125, + planeBounds: { + left: 0.061633166497511785, + bottom: -0.02169206718177056, + right: 0.6483277710024882, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 452.5, + bottom: 97.5, + right: 487.5, + top: 142.5 + } + }, + { + unicode: 76, + advance: 0.5380859375, + planeBounds: { + left: 0.06341786132300943, + bottom: -0.02169206718177056, + right: 0.5327735449269906, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 423.5, + bottom: 97.5, + right: 451.5, + top: 142.5 + } + }, + { + unicode: 77, + advance: 0.873046875, + planeBounds: { + left: 0.05911847969322944, + bottom: -0.02169206718177056, + right: 0.8134401140567705, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 377.5, + bottom: 97.5, + right: 422.5, + top: 142.5 + } + }, + { + unicode: 78, + advance: 0.712890625, + planeBounds: { + left: 0.062365588372511785, + bottom: -0.02169206718177056, + right: 0.6490601928774882, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 341.5, + bottom: 97.5, + right: 376.5, + top: 142.5 + } + }, + { + unicode: 79, + advance: 0.6875, + planeBounds: { + left: 0.033395854136655315, + bottom: -0.030073418674698794, + right: 0.6536158646133446, + top: 0.7410109186746989 + }, + atlasBounds: { + left: 416.5, + bottom: 144.5, + right: 453.5, + top: 190.5 + } + }, + { + unicode: 80, + advance: 0.630859375, + planeBounds: { + left: 0.061550166358368255, + bottom: -0.02169206718177056, + right: 0.6147193648916317, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 273.5, + bottom: 97.5, + right: 306.5, + top: 142.5 + } + }, + { + unicode: 81, + advance: 0.6875, + planeBounds: { + left: 0.030466166636655315, + bottom: -0.14391866037519643, + right: 0.6506861771133446, + top: 0.7445045978751964 + }, + atlasBounds: { + left: 214.5, + bottom: 202.5, + right: 251.5, + top: 255.5 + } + }, + { + unicode: 82, + advance: 0.61572265625, + planeBounds: { + left: 0.06350329135836826, + bottom: -0.02169206718177056, + right: 0.6166724898916317, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 186.5, + bottom: 97.5, + right: 219.5, + top: 142.5 + } + }, + { + unicode: 83, + advance: 0.59326171875, + planeBounds: { + left: 0.020778681983368255, + bottom: -0.030073418674698794, + right: 0.5739478805166317, + top: 0.7410109186746989 + }, + atlasBounds: { + left: 454.5, + bottom: 144.5, + right: 487.5, + top: 190.5 + } + }, + { + unicode: 84, + advance: 0.5966796875, + planeBounds: { + left: 0.005480822747511787, + bottom: -0.02169206718177056, + right: 0.5921754272524882, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 112.5, + bottom: 97.5, + right: 147.5, + top: 142.5 + } + }, + { + unicode: 85, + advance: 0.6484375, + planeBounds: { + left: 0.049098994483368255, + bottom: -0.02657487968177056, + right: 0.6022681930166317, + top: 0.7277467546817705 + }, + atlasBounds: { + left: 78.5, + bottom: 97.5, + right: 111.5, + top: 142.5 + } + }, + { + unicode: 86, + advance: 0.63623046875, + planeBounds: { + left: -0.008269192599201152, + bottom: -0.02169206718177056, + right: 0.6454762238492011, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 38.5, + bottom: 97.5, + right: 77.5, + top: 142.5 + } + }, + { + unicode: 87, + advance: 0.88720703125, + planeBounds: { + left: 0.011923628617731797, + bottom: -0.02169206718177056, + right: 0.8835841838822683, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 220.5, + bottom: 97.5, + right: 272.5, + top: 142.5 + } + }, + { + unicode: 88, + advance: 0.626953125, + planeBounds: { + left: 0.004098979136655316, + bottom: -0.02169206718177056, + right: 0.6243189896133446, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 0.5, + bottom: 97.5, + right: 37.5, + top: 142.5 + } + }, + { + unicode: 89, + advance: 0.6005859375, + planeBounds: { + left: -0.010793598988344685, + bottom: -0.02169206718177056, + right: 0.6094264114883446, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 148.5, + bottom: 97.5, + right: 185.5, + top: 142.5 + } + }, + { + unicode: 90, + advance: 0.5986328125, + planeBounds: { + left: 0.024196650733368255, + bottom: -0.02169206718177056, + right: 0.5773658492666317, + top: 0.7326295671817705 + }, + atlasBounds: { + left: 307.5, + bottom: 97.5, + right: 340.5, + top: 142.5 + } + }, + { + unicode: 91, + advance: 0.26513671875, + planeBounds: { + left: 0.05437250871693295, + bottom: -0.17280296457569408, + right: 0.27228764753306706, + top: 0.8329592145756942 + }, + atlasBounds: { + left: 59.5, + bottom: 195.5, + right: 72.5, + top: 255.5 + } + }, + { + unicode: 92, + advance: 0.41015625, + planeBounds: { + left: 0.0014037126767941326, + bottom: -0.08573505127848349, + right: 0.4204712873232059, + top: 0.7356373950284835 + }, + atlasBounds: { + left: 278.5, + bottom: 206.5, + right: 303.5, + top: 255.5 + } + }, + { + unicode: 93, + advance: 0.26513671875, + planeBounds: { + left: -0.020659233400995285, + bottom: -0.17280296457569408, + right: 0.2140186084009953, + top: 0.8329592145756942 + }, + atlasBounds: { + left: 94.5, + bottom: 195.5, + right: 108.5, + top: 255.5 + } + }, + { + unicode: 94, + advance: 0.41796875, + planeBounds: { + left: 0.006855376669722368, + bottom: 0.33229482979472236, + right: 0.40916024833027764, + top: 0.7345997014552776 + }, + atlasBounds: { + left: 486.5, + bottom: 231.5, + right: 510.5, + top: 255.5 + } + }, + { + unicode: 95, + advance: 0.451171875, + planeBounds: { + left: -0.017473255794918804, + bottom: -0.09553469482549765, + right: 0.4686451307949188, + top: 0.021804226075497646 + }, + atlasBounds: { left: 70.5, bottom: 7.5, right: 99.5, top: 14.5 } + }, + { + unicode: 96, + advance: 0.30908203125, + planeBounds: { + left: 0.00391839948107648, + bottom: 0.5860277898277895, + right: 0.2553589442689235, + top: 0.7704175226722105 + }, + atlasBounds: { left: 36.5, bottom: 3.5, right: 51.5, top: 14.5 } + }, + { + unicode: 97, + advance: 0.5439453125, + planeBounds: { + left: 0.028181041080081196, + bottom: -0.029187146002488215, + right: 0.5142994276699189, + top: 0.5575074585024882 + }, + atlasBounds: { + left: 30.5, + bottom: 15.5, + right: 59.5, + top: 50.5 + } + }, + { + unicode: 98, + advance: 0.56103515625, + planeBounds: { + left: 0.048932994205081196, + bottom: -0.032187684160555265, + right: 0.5350513807949189, + top: 0.7724220591605554 + }, + atlasBounds: { + left: 304.5, + bottom: 207.5, + right: 333.5, + top: 255.5 + } + }, + { + unicode: 99, + advance: 0.5234375, + planeBounds: { + left: 0.024518931705081196, + bottom: -0.029187146002488215, + right: 0.5106373182949189, + top: 0.5575074585024882 + }, + atlasBounds: { left: 0.5, bottom: 15.5, right: 29.5, top: 50.5 } + }, + { + unicode: 100, + advance: 0.56396484375, + planeBounds: { + left: 0.026227916080081196, + bottom: -0.032187684160555265, + right: 0.5123463026699189, + top: 0.7724220591605554 + }, + atlasBounds: { + left: 334.5, + bottom: 207.5, + right: 363.5, + top: 255.5 + } + }, + { + unicode: 101, + advance: 0.52978515625, + planeBounds: { + left: 0.026472056705081196, + bottom: -0.029187146002488215, + right: 0.5125904432949189, + top: 0.5575074585024882 + }, + atlasBounds: { + left: 468.5, + bottom: 61.5, + right: 497.5, + top: 96.5 + } + }, + { + unicode: 102, + advance: 0.34716796875, + planeBounds: { + left: 0.004575110905578838, + bottom: -0.022177918535555265, + right: 0.3733545765944212, + top: 0.7824318247855554 + }, + atlasBounds: { + left: 364.5, + bottom: 207.5, + right: 386.5, + top: 255.5 + } + }, + { + unicode: 103, + advance: 0.56103515625, + planeBounds: { + left: 0.026960337955081196, + bottom: -0.22888445766762702, + right: 0.5130787245449189, + top: 0.558962582667627 + }, + atlasBounds: { + left: 28.5, + bottom: 143.5, + right: 57.5, + top: 190.5 + } + }, + { + unicode: 104, + advance: 0.55078125, + planeBounds: { + left: 0.049826556565937666, + bottom: -0.01892352016762703, + right: 0.5024195371840624, + top: 0.768923520167627 + }, + atlasBounds: { + left: 0.5, + bottom: 143.5, + right: 27.5, + top: 190.5 + } + }, + { + unicode: 105, + advance: 0.24267578125, + planeBounds: { + left: 0.046882289688645884, + bottom: -0.01680925468177056, + right: 0.19774661656135412, + top: 0.7375123796817705 + }, + atlasBounds: { + left: 498.5, + bottom: 145.5, + right: 507.5, + top: 190.5 + } + }, + { + unicode: 106, + advance: 0.23876953125, + planeBounds: { + left: -0.048979545900995285, + bottom: -0.2324562772148376, + right: 0.1856982959009953, + top: 0.7397804959648377 + }, + atlasBounds: { + left: 140.5, + bottom: 197.5, + right: 154.5, + top: 255.5 + } + }, + { + unicode: 107, + advance: 0.5068359375, + planeBounds: { + left: 0.044294322330081196, + bottom: -0.01892352016762703, + right: 0.5304127089199189, + top: 0.768923520167627 + }, + atlasBounds: { + left: 58.5, + bottom: 143.5, + right: 87.5, + top: 190.5 + } + }, + { + unicode: 108, + advance: 0.24267578125, + planeBounds: { + left: 0.05428707868157412, + bottom: -0.01892352016762703, + right: 0.18838870256842588, + top: 0.768923520167627 + }, + atlasBounds: { + left: 477.5, + bottom: 208.5, + right: 485.5, + top: 255.5 + } + }, + { + unicode: 109, + advance: 0.87646484375, + planeBounds: { + left: 0.04430890170737297, + bottom: -0.024304333502488215, + right: 0.832155942042627, + top: 0.5623902710024882 + }, + atlasBounds: { + left: 116.5, + bottom: 15.5, + right: 163.5, + top: 50.5 + } + }, + { + unicode: 110, + advance: 0.5517578125, + planeBounds: { + left: 0.049826556565937666, + bottom: -0.024304333502488215, + right: 0.5024195371840624, + top: 0.5623902710024882 + }, + atlasBounds: { + left: 60.5, + bottom: 15.5, + right: 87.5, + top: 50.5 + } + }, + { + unicode: 111, + advance: 0.5703125, + planeBounds: { + left: 0.025090213094224725, + bottom: -0.029187146002488215, + right: 0.5447340056557752, + top: 0.5575074585024882 + }, + atlasBounds: { + left: 193.5, + bottom: 15.5, + right: 224.5, + top: 50.5 + } + }, + { + unicode: 112, + advance: 0.56103515625, + planeBounds: { + left: 0.048444712955081196, + bottom: -0.22644305141762702, + right: 0.5345630995449189, + top: 0.561403988917627 + }, + atlasBounds: { + left: 447.5, + bottom: 208.5, + right: 476.5, + top: 255.5 + } + }, + { + unicode: 113, + advance: 0.568359375, + planeBounds: { + left: 0.025983775455081196, + bottom: -0.22644305141762702, + right: 0.5121021620449189, + top: 0.561403988917627 + }, + atlasBounds: { + left: 417.5, + bottom: 208.5, + right: 446.5, + top: 255.5 + } + }, + { + unicode: 114, + advance: 0.33837890625, + planeBounds: { + left: 0.045180595002291775, + bottom: -0.024304333502488215, + right: 0.34690924874770823, + top: 0.5623902710024882 + }, + atlasBounds: { + left: 449.5, + bottom: 61.5, + right: 467.5, + top: 96.5 + } + }, + { + unicode: 115, + advance: 0.515625, + planeBounds: { + left: 0.021669814448009427, + bottom: -0.029187146002488215, + right: 0.4910254980519906, + top: 0.5575074585024882 + }, + atlasBounds: { + left: 164.5, + bottom: 15.5, + right: 192.5, + top: 50.5 + } + }, + { + unicode: 116, + advance: 0.32666015625, + planeBounds: { + left: -0.019433670483564695, + bottom: -0.02877457520298586, + right: 0.3158203892335647, + top: 0.675258950202986 + }, + atlasBounds: { + left: 418.5, + bottom: 54.5, + right: 438.5, + top: 96.5 + } + }, + { + unicode: 117, + advance: 0.55126953125, + planeBounds: { + left: 0.048117572190937666, + bottom: -0.034069958502488215, + right: 0.5007105528090624, + top: 0.5526246460024882 + }, + atlasBounds: { + left: 88.5, + bottom: 15.5, + right: 115.5, + top: 50.5 + } + }, + { + unicode: 118, + advance: 0.484375, + planeBounds: { + left: -0.002092396419918806, + bottom: -0.02080579450955998, + right: 0.4840259901699188, + top: 0.5491261070095601 + }, + atlasBounds: { + left: 331.5, + bottom: 16.5, + right: 360.5, + top: 50.5 + } + }, + { + unicode: 119, + advance: 0.75146484375, + planeBounds: { + left: -0.002649098431770561, + bottom: -0.02080579450955998, + right: 0.7516725359317705, + top: 0.5491261070095601 + }, + atlasBounds: { + left: 225.5, + bottom: 16.5, + right: 270.5, + top: 50.5 + } + }, + { + unicode: 120, + advance: 0.49560546875, + planeBounds: { + left: -0.0046143729128470395, + bottom: -0.02080579450955998, + right: 0.4982667166628471, + top: 0.5491261070095601 + }, + atlasBounds: { + left: 300.5, + bottom: 16.5, + right: 330.5, + top: 50.5 + } + }, + { + unicode: 121, + advance: 0.47314453125, + planeBounds: { + left: -0.007219349544918806, + bottom: -0.23645281704262702, + right: 0.4788990370449188, + top: 0.551394223292627 + }, + atlasBounds: { + left: 387.5, + bottom: 208.5, + right: 416.5, + top: 255.5 + } + }, + { + unicode: 122, + advance: 0.49560546875, + planeBounds: { + left: 0.018007705073009427, + bottom: -0.02080579450955998, + right: 0.4873633886769906, + top: 0.5491261070095601 + }, + atlasBounds: { + left: 271.5, + bottom: 16.5, + right: 299.5, + top: 50.5 + } + }, + { + unicode: 123, + advance: 0.33837890625, + planeBounds: { + left: 0.011572188891435306, + bottom: -0.20234398020069408, + right: 0.3468262486085647, + top: 0.8034181989506942 + }, + atlasBounds: { + left: 73.5, + bottom: 195.5, + right: 93.5, + top: 255.5 + } + }, + { + unicode: 124, + advance: 0.24365234375, + planeBounds: { + left: 0.06315671142450235, + bottom: -0.15466084787519643, + right: 0.18049563232549765, + top: 0.7337624103751964 + }, + atlasBounds: { + left: 206.5, + bottom: 202.5, + right: 213.5, + top: 255.5 + } + }, + { + unicode: 125, + advance: 0.33837890625, + planeBounds: { + left: -0.010156326733564695, + bottom: -0.20234398020069408, + right: 0.3250977329835647, + top: 0.8034181989506942 + }, + atlasBounds: { + left: 38.5, + bottom: 195.5, + right: 58.5, + top: 255.5 + } + }, + { + unicode: 126, + advance: 0.68017578125, + planeBounds: { + left: 0.046984728997511785, + bottom: 0.1766063915990047, + right: 0.6336793335024882, + top: 0.4112842334009953 + }, + atlasBounds: { left: 0.5, bottom: 0.5, right: 35.5, top: 14.5 } + } + ], + kerning: [] +}, fi = { + name: "untitled connectome", + nodeColormap: "warm", + nodeColormapNegative: "winter", + nodeMinColor: 0, + nodeMaxColor: 4, + nodeScale: 3, + edgeColormap: "warm", + edgeColormapNegative: "winter", + edgeMin: 2, + edgeMax: 6, + edgeScale: 1, + legendLineThickness: 0, + showLegend: !0 +}, mi = class $n extends kt { + constructor(e, t) { + super(new Float32Array([]), new Uint32Array([]), t.name, new Uint8Array([]), 1, !0, e, t), I(this, "gl"), I(this, "nodesChanged"), this.gl = e, this.type = "connectome", this.nodes && this.updateLabels(), this.nodesChanged = new EventTarget(); + } + static convertLegacyConnectome(e) { + const t = { nodes: [], edges: [], ...fi }; + for (const r in e) + if (r in fi) { + const a = r; + t[a] = e[a]; + } + const s = e.nodes; + for (let r = 0; r < s.names.length; r++) + t.nodes.push({ + name: s.names[r], + x: s.X[r], + y: s.Y[r], + z: s.Z[r], + colorValue: s.Color[r], + sizeValue: s.Size[r] + }); + for (let r = 0; r < s.names.length - 1; r++) + for (let a = r + 1; a < s.names.length; a++) { + const n = e.edges[r * s.names.length + a]; + t.edges.push({ + first: r, + second: a, + colorValue: n + }); + } + return t; + } + static convertFreeSurferConnectome(e, t = "warm") { + let s = !0; + if ("data_type" in e ? e.data_type !== "fs_pointset" && (s = !1) : s = !1, "points" in e || (s = !1), !s) + throw Error("not a valid FreeSurfer json pointset"); + const r = e.points.map((n) => ({ + name: Array.isArray(n.comments) && n.comments.length > 0 && "text" in n.comments[0] ? n.comments[0].text : "", + x: n.coordinates.x, + y: n.coordinates.y, + z: n.coordinates.z, + colorValue: 1, + sizeValue: 1, + metadata: n.comments + })); + return { + ...fi, + nodeColormap: t, + edgeColormap: t, + nodes: r, + edges: [] + }; + } + updateLabels() { + const e = this.nodes; + if (e && e.length > 0) { + const t = e.reduce((c, h) => c.sizeValue > h.sizeValue ? c : h).sizeValue; + let s, r; + if (typeof this.nodeMinColor < "u" && isFinite(this.nodeMinColor)) + s = this.nodeMinColor; + else { + s = e[0].colorValue; + for (let c = 1; c < e.length; c++) + e[c].colorValue < s && (s = e[c].colorValue); + } + if (typeof this.nodeMaxColor < "u" && isFinite(this.nodeMaxColor)) + r = this.nodeMaxColor; + else { + r = e[0].colorValue; + for (let c = 1; c < e.length; c++) + e[c].colorValue > r && (r = e[c].colorValue); + } + const a = oe.colormap(this.nodeColormap, this.colormapInvert), n = oe.colormap(this.nodeColormapNegative, this.colormapInvert), o = "nodeColormapNegative" in this; + let l = this.legendLineThickness ? this.legendLineThickness : 0; + this.showLegend === !1 && (l = 0); + for (let c = 0; c < e.length; c++) { + let h = e[c].colorValue, u = !1; + if (o && h < 0 && (u = !0, h = -h), s < r) { + if (h < s) { + R.warn("color value lower than min"); + continue; + } + h = (h - s) / (r - s); + } else + h = 1; + h = Math.round(Math.max(Math.min(255, h * 255))) * 4; + let d = [a[h], a[h + 1], a[h + 2], 255]; + u && (d = [n[h], n[h + 1], n[h + 2], 255]), d = d.map((f) => f / 255), R.debug("adding label for ", e[c]), e[c].label = new Ys( + e[c].name, + { + textColor: d, + bulletScale: e[c].sizeValue / t, + bulletColor: d, + lineWidth: l, + lineColor: d, + textScale: 1, + textAlignment: "left", + lineTerminator: "none" + /* NONE */ + }, + [e[c].x, e[c].y, e[c].z] + ), R.debug("label for node:", e[c].label); + } + } + } + addConnectomeNode(e) { + if (R.debug("adding node", e), !this.nodes) + throw new Error("nodes not defined"); + this.nodes.push(e), this.updateLabels(), this.nodesChanged.dispatchEvent(new CustomEvent("nodeAdded", { detail: { node: e } })); + } + deleteConnectomeNode(e) { + const t = this.nodes.indexOf(e), s = this.edges; + s && (this.edges = s.filter((r) => r.first !== t && r.second !== t)), this.nodes = this.nodes.filter((r) => r !== e), this.updateLabels(), this.updateConnectome(this.gl), this.nodesChanged.dispatchEvent(new CustomEvent("nodeDeleted", { detail: { node: e } })); + } + updateConnectomeNodeByIndex(e, t) { + this.nodes[e] = t, this.updateLabels(), this.updateConnectome(this.gl), this.nodesChanged.dispatchEvent(new CustomEvent("nodeChanged", { detail: { node: t } })); + } + updateConnectomeNodeByPoint(e, t) { + const s = this.nodes; + if (!s) + throw new Error("Node to update does not exist"); + const r = s.find((n) => j.arraysAreEqual([n.x, n.y, n.z], e)); + if (!r) + throw new Error(`Node with point ${e} to update does not exist`); + const a = s.findIndex((n) => n === r); + this.updateConnectomeNodeByIndex(a, t); + } + addConnectomeEdge(e, t, s) { + const r = this.edges; + let a = r.find((n) => (n.first === e || n.second === e) && n.first + n.second === e + t); + return a || (a = { first: e, second: t, colorValue: s }, r.push(a), this.updateConnectome(this.gl), a); + } + deleteConnectomeEdge(e, t) { + const s = this.edges, r = s.find((a) => (a.first === e || a.first === t) && a.first + a.second === e + t); + if (r) + this.edges = s.filter((a) => a !== r); + else + throw new Error(`edge between ${e} and ${t} not found`); + return this.updateConnectome(this.gl), r; + } + findClosestConnectomeNode(e, t) { + const s = this.nodes; + if (!s || s.length === 0) + return null; + const r = s.map((a, n) => ({ + node: a, + distance: Math.sqrt(Math.pow(a.x - e[0], 2) + Math.pow(a.y - e[1], 2) + Math.pow(a.z - e[2], 2)), + index: n + })).filter((a) => a.distance < t).sort((a, n) => a.distance - n.distance); + return r.length > 0 ? r[0].node : null; + } + updateConnectome(e) { + const t = [], s = [], r = []; + let a = oe.colormap(this.nodeColormap, this.colormapInvert), n = oe.colormap(this.nodeColormapNegative, this.colormapInvert), o = "nodeColormapNegative" in this; + this.nodeMinColor === void 0 && (this.nodeMinColor = NaN), this.nodeMaxColor === void 0 && (this.nodeMaxColor = NaN), this.edgeMin === void 0 && (this.edgeMin = NaN), this.edgeMax === void 0 && (this.edgeMax = NaN); + let l = this.nodeMinColor, c = this.nodeMaxColor; + if (!isFinite(l) || !isFinite(l)) { + const v = this.nodes; + l = v[0].colorValue, c = v[0].colorValue; + for (let A = 0; A < v.length; A++) + l = Math.min(l, v[A].colorValue), c = Math.max(c, v[A].colorValue); + } + const h = this.nodes, u = h.length; + for (let v = 0; v < u; v++) { + const A = h[v].sizeValue * this.nodeScale; + if (A <= 0) + continue; + let x = h[v].colorValue, w = !1; + if (o && x < 0 && (w = !0, x = -x), l < c) { + if (x < l) + continue; + x = (x - l) / (c - l); + } else + x = 1; + x = Math.round(Math.max(Math.min(255, x * 255))) * 4; + let D = [a[x], a[x + 1], a[x + 2], 255]; + w && (D = [n[x], n[x + 1], n[x + 2], 255]); + const b = G(h[v].x, h[v].y, h[v].z); + xt.makeColoredSphere(s, t, r, A, b, D); + } + a = oe.colormap(this.edgeColormap, this.colormapInvert), n = oe.colormap(this.edgeColormapNegative, this.colormapInvert), o = "edgeColormapNegative" in this; + const d = this.edges; + if (d !== void 0 && d.length > 0) { + if (l = this.edgeMin, c = this.edgeMax, !isFinite(l) || !isFinite(l)) { + l = d[0].colorValue, c = d[0].colorValue; + for (let v = 0; v < d.length; v++) + l = Math.min(l, d[v].colorValue), c = Math.max(c, d[v].colorValue); + } + for (const v of d) { + let A = v.colorValue; + const x = o && A < 0; + x && (A = -A); + const w = A * this.edgeScale; + if (w <= 0) + continue; + if (l < c) { + if (A < l) + continue; + A = (A - l) / (c - l); + } else + A = 1; + A = Math.round(Math.max(Math.min(255, A * 255))) * 4; + let D = [a[A], a[A + 1], a[A + 2], 255]; + x && (D = [n[A], n[A + 1], n[A + 2], 255]); + const b = G(h[v.first].x, h[v.first].y, h[v.first].z), C = G(h[v.second].x, h[v.second].y, h[v.second].z); + xt.makeColoredCylinder(s, t, r, b, C, w, D); + } + } + const f = new Float32Array(s), g = new Uint32Array(t), m = gt.getExtents(f); + this.furthestVertexFromOrigin = m.mxDx, this.extentsMin = m.extentsMin, this.extentsMax = m.extentsMax; + const p = this.generatePosNormClr(f, g, new Uint8Array(r)); + e.bindBuffer(e.ELEMENT_ARRAY_BUFFER, this.indexBuffer), e.bufferData(e.ELEMENT_ARRAY_BUFFER, Uint32Array.from(g), e.STATIC_DRAW), e.bindBuffer(e.ARRAY_BUFFER, this.vertexBuffer), e.bufferData(e.ARRAY_BUFFER, Float32Array.from(p), e.STATIC_DRAW), this.indexCount = t.length; + } + updateMesh(e) { + this.updateConnectome(e), this.updateLabels(); + } + json() { + const e = {}; + for (const t in this) + (t in fi || t === "nodes" || t === "edges") && (e[t] = this[t]); + return e; + } + /** + * Factory method to create connectome from options + */ + static async loadConnectomeFromUrl(e, t) { + const r = await (await fetch(t)).json(); + return new $n(e, r); + } +}; +function Jc(i, e, t) { + if (!i.dimsRAS || !i.matRAS || !i.pixDimsRAS || !i.vox2mm) + throw new Error("Cannot create NiivueObject3D: Missing required RAS properties or vox2mm access on NVImage."); + const s = i.dimsRAS, r = i.matRAS, a = i.pixDimsRAS, n = -0.5, o = -0.5, l = -0.5, c = s[1] - 1 + 0.5, h = s[2] - 1 + 0.5, u = s[3] - 1 + 0.5, d = i.vox2mm, f = d.call(i, [n, o, l], r), g = d.call(i, [n, h, l], r), m = d.call(i, [n, o, u], r), p = d.call(i, [n, h, u], r), v = d.call(i, [c, o, l], r), A = d.call(i, [c, h, l], r), x = d.call(i, [c, o, u], r), w = d.call(i, [c, h, u], r), D = [ + // Superior face vertices (Indices 0-3) + ...m, + 0, + 0, + 1, + // 0 + ...x, + 1, + 0, + 1, + // 1 + ...w, + 1, + 1, + 1, + // 2 + ...p, + 0, + 1, + 1, + // 3 + // Inferior face vertices (Indices 4-7) + ...f, + 0, + 0, + 0, + // 4 + ...g, + 0, + 1, + 0, + // 5 + ...A, + 1, + 1, + 0, + // 6 + ...v, + 1, + 0, + 0 + // 7 + ], b = t.createBuffer(); + if (!b) + throw new Error("Failed to create GL index buffer"); + t.bindBuffer(t.ELEMENT_ARRAY_BUFFER, b); + const C = [ + 0, + 3, + 2, + 2, + 1, + 0, + // Top + 4, + 7, + 6, + 6, + 5, + 4, + // Bottom + 5, + 6, + 2, + 2, + 3, + 5, + // Front -> Corresponds to LAI(5), RAI(6), RAS(2) / RAS(2), LAS(3), LAI(5) + 4, + 0, + 1, + 1, + 7, + 4, + // Back -> Corresponds to LPI(4), LPS(0), RPS(1) / RPS(1), RPI(7), LPI(4) + 7, + 1, + 2, + 2, + 6, + 7, + // Right -> Corresponds to RPI(7), RPS(1), RAS(2) / RAS(2), RAI(6), RPI(7) + 4, + 5, + 3, + 3, + 0, + 4 + // Left -> Corresponds to LPI(4), LAI(5), LAS(3) / LAS(3), LPS(0), LPI(4) + ]; + t.bufferData(t.ELEMENT_ARRAY_BUFFER, new Uint16Array(C), t.STATIC_DRAW); + const E = t.createBuffer(); + if (!E) + throw new Error("Failed to create GL vertex buffer"); + t.bindBuffer(t.ARRAY_BUFFER, E), t.bufferData(t.ARRAY_BUFFER, new Float32Array(D), t.STATIC_DRAW); + const F = t.createVertexArray(); + if (!F) + throw new Error("Failed to create GL VAO"); + t.bindVertexArray(F), t.bindBuffer(t.ELEMENT_ARRAY_BUFFER, b), t.bindBuffer(t.ARRAY_BUFFER, E); + const M = 24; + t.enableVertexAttribArray(0), t.vertexAttribPointer(0, 3, t.FLOAT, !1, M, 0), t.enableVertexAttribArray(1), t.vertexAttribPointer(1, 3, t.FLOAT, !1, M, 12), t.bindVertexArray(null); + const S = new xt(e, E, t.TRIANGLES, C.length, b, F), T = [...m, ...x, ...w, ...p, ...f, ...g, ...A, ...v], k = zc(T); + return S.extentsMin = k.min.slice(), S.extentsMax = k.max.slice(), S.furthestVertexFromOrigin = k.furthestVertexFromOrigin, S.originNegate = Ye(k.origin), Na(S.originNegate, S.originNegate), S.fieldOfViewDeObliqueMM = [s[1] * a[1], s[2] * a[2], s[3] * a[3]], S; +} +function $c(i) { + const e = i.length; + let t = 0; + const s = new Uint8Array(e + Math.ceil(0.01 * e)), r = new Int8Array(s.buffer); + let a = 0; + for (; t < e; ) { + let n = i[t]; + t++; + let o = 1; + for (; o < 129 && t < e && i[t] === n; ) + t++, o++; + if (o > 1) { + r[a] = -o + 1, a++, s[a] = n, a++; + continue; + } + for (; t < e && !(o > 127 || t + 2 < e && n !== i[t] && i[t + 2] === i[t] && i[t + 1] === i[t]); ) + n = i[t], t++, o++; + s[a] = o - 1, a++; + for (let l = 0; l < o; l++) + s[a] = i[t - o + l], a++; + } + return R.debug("PackBits " + e + " -> " + a + " bytes (x" + e / a + ")"), s.slice(0, a); +} +function bs(i, e) { + const t = new Uint8Array(i.buffer), s = new Int8Array(t.buffer); + let r = 0; + const a = new Uint8Array(e); + let n = 0; + for (; r < t.length; ) { + const o = s[r]; + if (r++, o < 0) { + const l = s[r]; + r++; + for (let c = 0; c < 1 - o; c++) + a[n] = l, n++; + } else + for (let l = 0; l < o + 1; l++) + a[n] = s[r], r++, n++; + } + return a; +} +var eh = ({ + drawUndoBitmaps: i, + currentDrawUndoBitmap: e, + drawBitmap: t +}) => { + const s = i.length; + if (s < 1) { + R.debug("undo bitmaps not loaded"); + return; + } + if (e--, e < 0 && (e = s - 1), e >= s && (e = 0), i[e].length < 2) { + R.debug("drawUndo is misbehaving"); + return; + } + return t = bs(i[e], t.length), { drawBitmap: t, currentDrawUndoBitmap: e }; +}; +function th(i, e, t) { + const { dimX: s, dimY: r, dimZ: a } = t; + let n; + if (i === 0) + n = a; + else if (i === 1) + n = r; + else if (i === 2) + n = s; + else + return null; + let o = -1, l = -1; + for (let c = 0; c < n; c++) { + let h = !1; + if (i === 0) { + const u = c * s * r; + for (let d = 0; d < s * r; d++) + if (e[u + d] > 0) { + h = !0; + break; + } + } else if (i === 1) + for (let u = 0; u < a; u++) { + for (let d = 0; d < s; d++) { + const f = d + c * s + u * s * r; + if (e[f] > 0) { + h = !0; + break; + } + } + if (h) + break; + } + else if (i === 2) + for (let u = 0; u < a; u++) { + for (let d = 0; d < r; d++) { + const f = c + d * s + u * s * r; + if (e[f] > 0) { + h = !0; + break; + } + } + if (h) + break; + } + h && (o === -1 && (o = c), l = c); + } + return o === -1 || l === -1 ? null : { first: o, last: l }; +} +function ts(i, e, t, s) { + const { dimX: r, dimY: a, dimZ: n } = s; + let o; + if (e === 0) { + o = new Float32Array(r * a); + const l = i * r * a; + for (let c = 0; c < r * a; c++) + o[c] = t[l + c]; + } else if (e === 1) { + o = new Float32Array(r * n); + for (let l = 0; l < n; l++) + for (let c = 0; c < r; c++) { + const h = c + i * r + l * r * a, u = c + l * r; + o[u] = t[h]; + } + } else if (e === 2) { + o = new Float32Array(a * n); + for (let l = 0; l < n; l++) + for (let c = 0; c < a; c++) { + const h = i + c * r + l * r * a, u = c + l * a; + o[u] = t[h]; + } + } else + throw new Error("Invalid slice type"); + return o; +} +function is(i, e, t, s, r) { + const { dimX: a, dimY: n, dimZ: o } = s; + let l; + if (e === 0) { + l = new Float32Array(a * n); + const c = i * a * n; + for (let h = 0; h < a * n; h++) + l[h] = t[c + h] / r; + } else if (e === 1) { + l = new Float32Array(a * o); + for (let c = 0; c < o; c++) + for (let h = 0; h < a; h++) { + const u = h + i * a + c * a * n, d = h + c * a; + l[d] = t[u] / r; + } + } else if (e === 2) { + l = new Float32Array(n * o); + for (let c = 0; c < o; c++) + for (let h = 0; h < n; h++) { + const u = i + h * a + c * a * n, d = h + c * n; + l[d] = t[u] / r; + } + } else + throw new Error("Invalid slice type"); + return l; +} +function ih(i, e, t, s, r, a, n) { + const { dimX: o, dimY: l, dimZ: c } = r; + if (t === 0) { + const h = e * o * l; + for (let u = 0; u < i.length; u++) + i[u] >= a && (s[h + u] = n); + } else if (t === 1) + for (let h = 0; h < c; h++) + for (let u = 0; u < o; u++) { + const d = u + h * o, f = u + e * o + h * o * l; + i[d] >= a && (s[f] = n); + } + else if (t === 2) + for (let h = 0; h < c; h++) + for (let u = 0; u < l; u++) { + const d = u + h * l, f = e + u * o + h * o * l; + i[d] >= a && (s[f] = n); + } + else + throw new Error("Invalid slice type"); +} +function Vr(i, e, t) { + if (e < 3 || t < 3) + return; + const s = new Float32Array(i.length); + for (let r = 0; r < t; r++) + for (let a = 0; a < e; a++) { + const n = a + r * e; + a === 0 || a === e - 1 ? s[n] = i[n] : s[n] = (i[n - 1] + 2 * i[n] + i[n + 1]) * 0.25; + } + for (let r = 0; r < t; r++) + for (let a = 0; a < e; a++) { + const n = a + r * e; + r === 0 || r === t - 1 ? i[n] = s[n] : i[n] = (s[n - e] + 2 * s[n] + s[n + e]) * 0.25; + } +} +function sh(i, e, t, s) { + const r = Math.abs(t - i), a = Math.abs(t - e), n = Math.exp(-r * r / (2 * s * s)), o = Math.exp(-a * a / (2 * s * s)), l = n + o; + return l < 1e-6 ? 0.5 : n / l; +} +function rh(i, e, t, s, r, a) { + const n = (t - s) / (r - s), o = 1 - n; + for (let l = 0; l < i.length; l++) + a[l] = i[l] * o + e[l] * n; +} +function nh(i, e, t, s, r, a, n, o, l, c) { + const h = (t - s) / (r - s), u = 1 - h; + for (let d = 0; d < i.length; d++) + if (i[d] > 0 || e[d] > 0) { + const f = sh( + o[d], + l[d], + c[d], + n.intensitySigma + ), g = n.intensityWeight, m = g * f + (1 - g) * u, p = 1 - m; + a[d] = i[d] * m + e[d] * p; + } else + a[d] = i[d] * u + e[d] * h; +} +function ah(i, e, t, s, r, a, n, o) { + const { dimX: l, dimY: c, dimZ: h } = e, u = n.sliceType ?? 0; + let d, f, g; + if (u === 0) + d = l, f = c, g = h - 1; + else if (u === 1) + d = l, f = h, g = c - 1; + else if (u === 2) + d = c, f = h, g = l - 1; + else + throw new Error("Invalid slice type. Must be AXIAL, CORONAL, or SAGITTAL"); + const m = { + intensityWeight: n.intensityWeight ?? 0.7, + binaryThreshold: n.binaryThreshold ?? 0.375, + intensitySigma: n.intensitySigma ?? 0.1, + applySmoothingToSlices: n.applySmoothingToSlices ?? !0, + useIntensityGuided: n.useIntensityGuided ?? !0 + }; + if (r !== void 0 && a !== void 0) { + if (r >= a) + throw new Error("Low slice index must be less than high slice index"); + if (r < 0 || a > g) + throw new Error(`Slice indices out of bounds [0, ${g}]`); + } + const p = /* @__PURE__ */ new Map(); + for (let v = 0; v <= g; v++) { + const A = ts(v, u, i, e); + for (let x = 0; x < A.length; x++) { + const w = A[x]; + if (w > 0) + if (!p.has(w)) + p.set(w, { min: v, max: v }); + else { + const D = p.get(w); + D.min = Math.min(D.min, v), D.max = Math.max(D.max, v); + } + } + } + for (const [v, A] of p) { + const x = r !== void 0 ? Math.max(r, A.min) : A.min, w = a !== void 0 ? Math.min(a, A.max) : A.max; + if (x >= w || w - x < 2) + continue; + const D = ts(x, u, i, e), b = ts(w, u, i, e), C = new Float32Array(D.length), E = new Float32Array(b.length); + for (let F = 0; F < D.length; F++) + C[F] = D[F] === v ? 1 : 0, E[F] = b[F] === v ? 1 : 0; + m.applySmoothingToSlices && (Vr(C, d, f), Vr(E, d, f)); + for (let F = x + 1; F < w; F++) { + const M = new Float32Array(d * f); + if (m.useIntensityGuided && t) { + const S = is(x, u, t, e, s), T = is(w, u, t, e, s), k = is(F, u, t, e, s); + nh( + C, + E, + F, + x, + w, + M, + m, + S, + T, + k + ); + } else + rh(C, E, F, x, w, M); + ih(M, F, u, i, e, m.binaryThreshold, v); + } + } + o(); +} +var zt = `#version 300 es +#line 4 +layout(location=0) in vec3 pos; +layout(location=1) in vec3 texCoords; +uniform mat4 mvpMtx; +out vec3 vColor; +void main(void) { + gl_Position = mvpMtx * vec4(pos, 1.0); + vColor = texCoords; +}`, _s = ` + vec4 drawColor(float scalar, float drawOpacity) { + float nlayer = float(textureSize(colormap, 0).y); + float layer = (nlayer - 0.5) / nlayer; + vec4 dcolor = texture(colormap, vec2((scalar * 255.0)/256.0 + 0.5/256.0, layer)).rgba; + dcolor.a *= drawOpacity; + return dcolor; +}`, ni = `vec3 GetBackPosition(vec3 startPositionTex) { + vec3 startPosition = startPositionTex * volScale; + vec3 invR = 1.0 / rayDir; + vec3 tbot = invR * (vec3(0.0)-startPosition); + vec3 ttop = invR * (volScale-startPosition); + vec3 tmax = max(ttop, tbot); + vec2 t = min(tmax.xx, tmax.yz); + vec3 endPosition = startPosition + (rayDir * min(t.x, t.y)); + //convert world position back to texture position: + endPosition = endPosition / volScale; + return endPosition; +} + +float distance2Plane(in vec4 samplePos, in vec4 clipPlane) { + // treat clipPlane.a > 1 as "no clip" sentinel (keeps existing behavior) + if (clipPlane.a > 1.0) { + return 1000.0; // sentinel large distance + } + vec3 n = clipPlane.xyz; + const float EPS = 1e-6; + float nlen = length(n); + if (nlen < EPS) { + return 1000.0; // invalid plane normal + } + // signed plane value: dot(n, p-0.5) + a + float signedDist = dot(n, samplePos.xyz - 0.5) + clipPlane.a; + // perpendicular (Euclidean) distance is |signedDist| / |n| + return abs(signedDist) / nlen; +} + +// see if clip plane trims ray sampling range sampleStartEnd.x..y +void clipSampleRange(in vec3 dir, in vec4 rayStart, in vec4 clipPlane, inout vec2 sampleStartEnd, inout bool hasClip) { + const float CSR_EPS = 1e-6; + // quick exit: no clip plane + if (clipPlane.a > 1.0) + return; + hasClip = true; + // quick exit: empty range + if ((sampleStartEnd.y - sampleStartEnd.x) <= CSR_EPS) + return; + // Which side does the ray start on? (plane eqn: dot(n, p-0.5) + a = 0) + float sampleSide = dot(clipPlane.xyz, rayStart.xyz - 0.5) + clipPlane.a; + bool startsFront = (sampleSide < 0.0); + float dis = - 1.0; + // plane normal dot ray direction + float cdot = dot(dir, clipPlane.xyz); + // avoid division by 0 for near-parallel plne + if (abs(cdot) >= CSR_EPS) + dis = (-clipPlane.a - dot(clipPlane.xyz, rayStart.xyz - 0.5)) / cdot; + if (dis < 0.0 || dis > sampleStartEnd.y + CSR_EPS) { + if (startsFront) + sampleStartEnd = vec2(0.0, 0.0); + return; + } + bool frontface = (cdot > 0.0); + if (frontface) + sampleStartEnd.x = max(sampleStartEnd.x, dis); + else + sampleStartEnd.y = min(sampleStartEnd.y, dis); + // if nothing remains, mark empty + if (sampleStartEnd.y - sampleStartEnd.x <= CSR_EPS) + sampleStartEnd = vec2(0.0, 0.0); +} + +bool skipSample (float pos, vec2 sampleRange) { + return (pos < sampleRange.x || pos > sampleRange.y); +} + +float frac2ndc(vec3 frac) { +//https://stackoverflow.com/questions/7777913/how-to-render-depth-linearly-in-modern-opengl-with-gl-fragcoord-z-in-fragment-sh + vec4 pos = vec4(frac.xyz, 1.0); //fraction + vec4 dim = vec4(vec3(textureSize(volume, 0)), 1.0); + pos = pos * dim; + vec4 shim = vec4(-0.5, -0.5, -0.5, 0.0); + pos += shim; + vec4 mm = transpose(matRAS) * pos; + float z_ndc = (mvpMtx * vec4(mm.xyz, 1.0)).z; + return (z_ndc + 1.0) / 2.0; +}` + _s, qs = `void main() { + if (fColor.x > 2.0) { + fColor = vec4(1.0, 0.0, 0.0, 0.5); + return; + } + fColor = vec4(0.0,0.0,0.0,0.0); + vec4 clipPlaneColorX = clipPlaneColor; + //if (clipPlaneColor.a < 0.0) + // clipPlaneColorX.a = - 1.0; + bool isColorPlaneInVolume = false; + if (clipPlaneColorX.a < 0.0) { + isColorPlaneInVolume = true; + clipPlaneColorX.a = 0.0; + } + //fColor = vec4(vColor.rgb, 1.0); return; + vec3 start = vColor; + gl_FragDepth = 1.0; + vec3 backPosition = GetBackPosition(start); + // fColor = vec4(backPosition, 1.0); return; + vec3 dir = normalize(backPosition - start); + //clipVolumeStart(start, backPosition); + dir = normalize(dir); + float len = length(backPosition - start); + float lenVox = length((texVox * start) - (texVox * backPosition)); + if ((lenVox < 0.5) || (len > 3.0)) { //length limit for parallel rays + return; + } + float sliceSize = len / lenVox; //e.g. if ray length is 1.0 and traverses 50 voxels, each voxel is 0.02 in unit cube + float stepSize = sliceSize; //quality: larger step is faster traversal, but fewer samples + float opacityCorrection = stepSize/sliceSize; + vec4 deltaDir = vec4(dir.xyz * stepSize, stepSize); + vec4 samplePos = vec4(start.xyz, 0.0); //ray position + + vec2 sampleRange = vec2(0.0, len); + bool hasClip = false; + for (int i = 0; i < MAX_CLIP_PLANES; i++) + clipSampleRange(dir, samplePos, clipPlanes[i], sampleRange, hasClip); + bool isClip = (sampleRange.x > 0.0) || ((sampleRange.y < len) && (sampleRange.y > 0.0)); + float stepSizeFast = sliceSize * 1.9; + vec4 deltaDirFast = vec4(dir.xyz * stepSizeFast, stepSizeFast); + if ((isClipCutaway) && (sampleRange.x <= 0.0) && (sampleRange.y >= len)) { + //completely clipped, but ray does not intersect plane + if (hasClip) + samplePos.a = len + 1.0; + else + sampleRange = vec2(0.0, 0.0); + } + if ((!isClipCutaway) && (sampleRange.x >= sampleRange.y)) + samplePos.a = len + 1.0; + while (samplePos.a <= len) { + if (skipSample(samplePos.a, sampleRange) ^^ isClipCutaway) { + samplePos += deltaDirFast; + continue; + } + float val = texture(volume, samplePos.xyz).a; + if (val > 0.01) + break; + samplePos += deltaDirFast; //advance ray position + } + float drawOpacityA = renderDrawAmbientOcclusionXY.y; + if ((samplePos.a >= len) && (((overlays < 1.0) && (drawOpacityA <= 0.0) ) || (backgroundMasksOverlays > 0))) { + if (isClip) + fColor += clipPlaneColorX; + return; + } + fColor = vec4(1.0, 1.0, 1.0, 1.0); + //gl_FragDepth = frac2ndc(samplePos.xyz); //crude due to fast pass resolution + if (samplePos.a > deltaDirFast.a ) + samplePos -= deltaDirFast; + //end: fast pass + vec4 colAcc = vec4(0.0,0.0,0.0,0.0); + vec4 firstHit = vec4(0.0,0.0,0.0,2.0 * len); + const float earlyTermination = 0.95; + float backNearest = len; //assume no hit + float ran = fract(sin(gl_FragCoord.x * 12.9898 + gl_FragCoord.y * 78.233) * 43758.5453); + // clip planes create steep gradients: reduce aliasing with more jitter + if (isClip) + samplePos += deltaDir * ran * 1.41; //jitter ray + else + samplePos += deltaDir * ran; //jitter ray +`, _i = ` + if (firstHit.a < len) { + gl_FragDepth = frac2ndc(firstHit.xyz); + vec4 paqdSample = texture(paqd, samplePos.xyz); + if (paqdSample.a > 0.0) { + //colAcc.rgb = paqdSample.rgb; + float a = max(abs(paqdUniforms[2]), abs(paqdUniforms[3])); + colAcc.rgb = mix(colAcc.rgb, paqdSample.rgb, 0.5 * paqdSample.a * a); + } + if (isClip) { + //shade voxels with clip color + if (clipPlaneColor.a < 0.0) { + float thresh = 4.0 * sliceSize; + float firstHit1 = firstHit.a + deltaDir.a; + if (isClipCutaway) { + float min1 = abs(firstHit1 - sampleRange.y); + float dx = samplePos.a - firstHit1; + if (min1 < thresh) + colAcc.rgb = mix(colAcc.rgb, clipPlaneColorX.rgb, abs(clipPlaneColor.a)); + else if (( colAcc.a > earlyTermination ) && (dx > thresh)) { + min1 = abs(firstHit1 - sampleRange.x); + if (min1 < (thresh * 0.5)) { + colAcc.rgb = mix(colAcc.rgb , clipPlaneColorX.rgb, abs(clipPlaneColor.a)*0.5); + } + + } + } else { + if (abs(firstHit1 - sampleRange.x) < thresh) + colAcc.rgb = mix(colAcc.rgb, clipPlaneColorX.rgb, abs(clipPlaneColor.a)); + } // clipPlaneColor.a < 0.0 + } + //ambient occlusion: make creases dark + float min1 = 1000.0; + float min2 = 1000.0; + // find smallest and second-smallest distances + vec4 firstHit1 = firstHit - deltaDir; + for (int i = 0; i < MAX_CLIP_PLANES; i++) { + float d = distance2Plane(firstHit1, clipPlanes[i]); + if (d < min1) { + min2 = min1; + min1 = d; + } else if (d < min2) { + min2 = d; + } + } + float thresh = 1.2 * sliceSize; + if ((isClipCutaway) && (min2 < thresh) && (sampleRange.x > 0.0)) { + if ((abs(sampleRange.x - firstHit.a) > ( 2.0 * thresh)) && ((abs(sampleRange.y - firstHit.a) > (2.0 * thresh)))) + min2 = thresh; + } + // if second is 0 -> factor 0 (black), if second >= sliceSize -> factor 1 (unchanged) + const float aoFrac = 0.5; + float factor = (1.0 - aoFrac) + aoFrac * clamp(min2 / thresh, 0.0, 1.0); + // linear darkening: multiply color by factor (or use mix(vec3(0), colAcc.rgb, factor)) + colAcc.rgb *= factor; + } + } + colAcc.a = (colAcc.a / earlyTermination) * backOpacity; + fColor = colAcc; + float renderDrawAmbientOcclusionX = renderDrawAmbientOcclusionXY.x; + float drawOpacity = renderDrawAmbientOcclusionXY.y; + if ((overlays < 1.0) && (drawOpacity <= 0.0)) + return; + //overlay pass + samplePos = vec4(start.xyz, 0.0); //ray position + //start: OPTIONAL fast pass: rapid traversal until first hit + stepSizeFast = sliceSize * 1.0; + deltaDirFast = vec4(dir.xyz * stepSizeFast, stepSizeFast); + while (samplePos.a <= len) { + float val = texture(overlay, samplePos.xyz).a; + if (drawOpacity > 0.0) + val = max(val, texture(drawing, samplePos.xyz).r); + if (val > 0.001) + break; + samplePos += deltaDirFast; //advance ray position + } + if (samplePos.a >= len) { + if (isClip && (fColor.a == 0.0)) + fColor += clipPlaneColorX; + return; + } + samplePos -= deltaDirFast; + if (samplePos.a < 0.0) + vec4 samplePos = vec4(start.xyz, 0.0); //ray position + //end: fast pass + float overFarthest = len; + colAcc = vec4(0.0, 0.0, 0.0, 0.0); + + samplePos += deltaDir * ran; //jitter ray + vec4 overFirstHit = vec4(0.0,0.0,0.0,2.0 * len); + if (backgroundMasksOverlays > 0) + samplePos = firstHit; + bool firstDraw = true; + while (samplePos.a <= len) { + vec4 colorSample = texture(overlay, samplePos.xyz); + if ((colorSample.a < 0.01) && (drawOpacity > 0.0)) { + float val = texture(drawing, samplePos.xyz).r; + vec4 draw = drawColor(val, drawOpacity); + if ((draw.a > 0.0) && (firstDraw)) { + firstDraw = false; + float sum = 0.0; + const float mn = 1.0 / 256.0; + const float sampleRadius = 1.1; + float dx = sliceSize * sampleRadius; + vec3 center = samplePos.xyz; + //six neighbors that share a face + sum += min(texture(drawing, center.xyz + cross(vec3(0.0,0.0,+dx), dir)).r, mn); + sum += min(texture(drawing, center.xyz + cross(vec3(0.0,0.0,-dx), dir)).r, mn); + sum += min(texture(drawing, center.xyz + cross(vec3(0.0,+dx,0.0), dir)).r, mn); + sum += min(texture(drawing, center.xyz + cross(vec3(0.0,-dx,0.0), dir)).r, mn); + sum += min(texture(drawing, center.xyz + cross(vec3(+dx,0.0,0.0), dir)).r, mn); + sum += min(texture(drawing, center.xyz + cross(vec3(-dx,0.0,0.0), dir)).r, mn); + //float proportion = (sum / mn) / 6.0; + + //12 neighbors that share an edge + dx = sliceSize * sampleRadius * sqrt(2.0) * 0.5; + sum += min(texture(drawing, center.xyz + cross(vec3(0.0,+dx,+dx), dir)).r, mn); + sum += min(texture(drawing, center.xyz + cross(vec3(+dx,0.0,+dx), dir)).r, mn); + sum += min(texture(drawing, center.xyz + cross(vec3(+dx,+dx,0.0), dir)).r, mn); + sum += min(texture(drawing, center.xyz + cross(vec3(0.0,-dx,-dx), dir)).r, mn); + sum += min(texture(drawing, center.xyz + cross(vec3(-dx,0.0,-dx), dir)).r, mn); + sum += min(texture(drawing, center.xyz + cross(vec3(-dx,-dx,0.0), dir)).r, mn); + + sum += min(texture(drawing, center.xyz + cross(vec3(0.0,+dx,-dx), dir)).r, mn); + sum += min(texture(drawing, center.xyz + cross(vec3(+dx,0.0,-dx), dir)).r, mn); + sum += min(texture(drawing, center.xyz + cross(vec3(+dx,-dx,0.0), dir)).r, mn); + + sum += min(texture(drawing, center.xyz + cross(vec3(0.0,-dx,+dx), dir)).r, mn); + sum += min(texture(drawing, center.xyz + cross(vec3(-dx,0.0,+dx), dir)).r, mn); + sum += min(texture(drawing, center.xyz + cross(vec3(-dx,+dx,0.0), dir)).r, mn); + float proportion = (sum / mn) / 18.0; //proportion of six neighbors is non-zero + + //a high proportion of hits means crevice + //since the AO term adds shadows that darken most voxels, it will result in dark surfaces + //the term brighten adds a little illumination to balance this + // without brighten, only the most extreme ridges will not be darker + const float brighten = 1.2; + vec3 ao = draw.rgb * (1.0 - proportion) * brighten; + draw.rgb = mix (draw.rgb, ao , renderDrawAmbientOcclusionX); + } + colorSample = draw; + } + samplePos += deltaDir; //advance ray position + if (colorSample.a >= 0.01) { + if (overFirstHit.a > len) + overFirstHit = samplePos; + colorSample.a *= renderOverlayBlend; + colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection); + colorSample.rgb *= colorSample.a; + colAcc= (1.0 - colAcc.a) * colorSample + colAcc; + overFarthest = samplePos.a; + if ( colAcc.a > earlyTermination ) + break; + } + } + //if (samplePos.a >= len) { + if (colAcc.a <= 0.0) { + if (isClip && (fColor.a == 0.0)) + fColor += clipPlaneColorX; + return; + } + if (overFirstHit.a < firstHit.a) + gl_FragDepth = frac2ndc(overFirstHit.xyz); + float overMix = colAcc.a; + float overlayDepth = 0.3; + if (fColor.a <= 0.0) + overMix = 1.0; + else if (((overFarthest) > backNearest)) { + float dx = (overFarthest - backNearest)/1.73; + dx = fColor.a * pow(dx, overlayDepth); + overMix *= 1.0 - dx; + } + fColor.rgb = mix(fColor.rgb, colAcc.rgb, overMix); + fColor.a = max(fColor.a, colAcc.a); +}`, oh = `#version 300 es +#line 215 +#define MAX_CLIP_PLANES 6 +precision highp int; +precision highp float; +uniform vec3 rayDir; +uniform vec3 texVox; +uniform int backgroundMasksOverlays; +uniform vec3 volScale; +uniform vec4 clipPlane; +uniform vec4 clipPlanes[MAX_CLIP_PLANES]; +uniform highp sampler3D volume, overlay; +uniform highp sampler3D paqd; +uniform vec4 paqdUniforms; +uniform float overlays; +uniform float backOpacity; +uniform mat4 mvpMtx; +uniform mat4 matRAS; +uniform vec4 clipPlaneColor; +uniform float renderOverlayBlend; +uniform highp sampler3D drawing; +uniform highp sampler2D colormap; +uniform vec2 renderDrawAmbientOcclusionXY; +in vec3 vColor; +out vec4 fColor; +` + ni + ` + void main() { + vec3 start = vColor; + gl_FragDepth = 1.0; + vec3 backPosition = GetBackPosition(start); + vec3 dir = normalize(backPosition - start); + //clipVolumeStart(start, backPosition); + float len = length(backPosition - start); + float lenVox = length((texVox * start) - (texVox * backPosition)); + if ((lenVox < 0.5) || (len > 3.0)) { //length limit for parallel rays + fColor = vec4(0.0,0.0,0.0,0.0); + return; + } + float sliceSize = len / lenVox; //e.g. if ray length is 1.0 and traverses 50 voxels, each voxel is 0.02 in unit cube + float stepSize = sliceSize; //quality: larger step is faster traversal, but fewer samples + float opacityCorrection = stepSize/sliceSize; + vec4 deltaDir = vec4(dir.xyz * stepSize, stepSize); + vec4 samplePos = vec4(start.xyz, 0.0); //ray position + vec4 colAcc = vec4(0.0,0.0,0.0,0.0); + vec4 firstHit = vec4(0.0,0.0,0.0,2.0 * len); + const float earlyTermination = 0.95; + float backNearest = len; //assume no hit + float dis = len; + //check if axial plane is closest + vec4 aClip = vec4(0.0, 0.0, 1.0, (1.0- clipPlane.z) - 0.5); + float adis = (-aClip.a - dot(aClip.xyz, samplePos.xyz-0.5)) / dot(dir,aClip.xyz); + if (adis > 0.0) + dis = min(adis, dis); + //check of coronal plane is closest + vec4 cClip = vec4(0.0, 1.0, 0.0, (1.0- clipPlane.y) - 0.5); + float cdis = (-cClip.a - dot(cClip.xyz, samplePos.xyz-0.5)) / dot(dir,cClip.xyz); + if (cdis > 0.0) + dis = min(cdis, dis); + //check if coronal slice is closest + vec4 sClip = vec4(1.0, 0.0, 0.0, (1.0- clipPlane.x) - 0.5); + float sdis = (-sClip.a - dot(sClip.xyz, samplePos.xyz-0.5)) / dot(dir,sClip.xyz); + if (sdis > 0.0) + dis = min(sdis, dis); + if ((dis > 0.0) && (dis < len)) { + samplePos = vec4(samplePos.xyz+dir * dis, dis); + colAcc = texture(volume, samplePos.xyz); + colAcc.a = earlyTermination; + firstHit = samplePos; + backNearest = min(backNearest, samplePos.a); + } + //the following are only used by overlays + vec4 clipPlaneColorX = clipPlaneColor; + bool isColorPlaneInVolume = false; + bool isClip = false; + bool isClipCutaway = false; + vec2 sampleRange; + // vec4 clipPos = applyClip(dir, samplePos, len, isClip); + float stepSizeFast = sliceSize * 1.9; + vec4 deltaDirFast = vec4(dir.xyz * stepSizeFast, stepSizeFast); + if (samplePos.a < 0.0) + vec4 samplePos = vec4(start.xyz, 0.0); //ray position + float ran = fract(sin(gl_FragCoord.x * 12.9898 + gl_FragCoord.y * 78.233) * 43758.5453); + samplePos += deltaDir * ran; //jitter ray +` + _i, lh = `#version 300 es +#line 215 +#define MAX_CLIP_PLANES 6 +precision highp int; +precision highp float; +uniform vec3 rayDir; +uniform vec3 texVox; +uniform int backgroundMasksOverlays; +uniform vec3 volScale; +uniform vec4 clipPlane; +uniform vec4 clipPlanes[MAX_CLIP_PLANES]; +uniform bool isClipCutaway; +uniform highp sampler3D volume, overlay; +uniform highp sampler3D paqd; +uniform vec4 paqdUniforms; +uniform float overlays; +uniform float backOpacity; +uniform mat4 mvpMtx; +uniform mat4 matRAS; +uniform vec4 clipPlaneColor; +uniform float renderOverlayBlend; +uniform highp sampler3D drawing; +uniform highp sampler2D colormap; +uniform vec2 renderDrawAmbientOcclusionXY; +in vec3 vColor; +out vec4 fColor; +` + ni + qs + `while (samplePos.a <= len) { + if (skipSample(samplePos.a, sampleRange) ^^ isClipCutaway) { + samplePos += deltaDirFast; + continue; + } + vec4 colorSample = texture(volume, samplePos.xyz); + samplePos += deltaDir; //advance ray position + if (colorSample.a >= 0.01) { + if (firstHit.a > len) + firstHit = samplePos; + // backNearest = min(backNearest, samplePos.a); + colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection); + colorSample.rgb *= colorSample.a; + colAcc= (1.0 - colAcc.a) * colorSample + colAcc; + if ( colAcc.a > earlyTermination ) + break; + } + } + if (firstHit.a < len) + backNearest = firstHit.a; +` + _i, Jt = 192, ea = `#version 300 es +#line 215 +#define MAX_CLIP_PLANES 6 +precision highp int; +precision highp float; +uniform vec3 rayDir; +uniform vec3 texVox; +uniform int backgroundMasksOverlays; +uniform vec3 volScale; +uniform vec4 clipPlane; +uniform vec4 clipPlanes[MAX_CLIP_PLANES]; +uniform bool isClipCutaway; +uniform highp sampler3D volume, overlay; +uniform highp sampler3D paqd; +uniform vec4 paqdUniforms; +uniform float overlays; +uniform float backOpacity; +uniform mat4 mvpMtx; +uniform mat4 normMtx; +uniform mat4 matRAS; +uniform vec4 clipPlaneColor; +uniform float renderOverlayBlend; +uniform highp sampler3D drawing, gradient; +uniform highp sampler2D colormap; +uniform highp sampler2D matCap; +uniform vec2 renderDrawAmbientOcclusionXY; +uniform float gradientAmount; +uniform float silhouettePower; +uniform float gradientOpacity[${Jt}]; +in vec3 vColor; +out vec4 fColor; +`, ch = ea + ni + qs + ` + float startPos = samplePos.a; + float clipCloseThresh = 5.0 * deltaDir.a; + float clipClose = sampleRange.x; + if (isClipCutaway) + clipClose = sampleRange.y; + if (!isClip) + clipClose = -1.0; + float brighten = 2.0; //modulating makes average intensity darker 0.5 * 0.5 = 0.25 + //vec4 prevGrad = vec4(0.0); + float silhouetteThreshold = 1.0 - silhouettePower; + while (samplePos.a <= len) { + if (skipSample(samplePos.a, sampleRange) ^^ isClipCutaway) { + samplePos += deltaDirFast; + continue; + } + vec4 colorSample = texture(volume, samplePos.xyz); + if (colorSample.a >= 0.0) { + vec4 grad = texture(gradient, samplePos.xyz); + grad.rgb = normalize(grad.rgb*2.0 - 1.0); + //if (grad.a < prevGrad.a) + // grad.rgb = prevGrad.rgb; + //prevGrad = grad; + vec3 n = mat3(normMtx) * grad.rgb; + n.y = - n.y; + vec4 mc = vec4(texture(matCap, n.xy * 0.5 + 0.5).rgb, 1.0) * brighten; + mc = mix(vec4(1.0), mc, gradientAmount); + if (abs(samplePos.a - clipClose) > clipCloseThresh) + colorSample.rgb *= mc.rgb; + if (firstHit.a > len) + firstHit = samplePos; + backNearest = min(backNearest, samplePos.a); + colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection); + int gradIdx = int(grad.a * ${Jt}.0); + colorSample.a *= gradientOpacity[gradIdx]; + float lightNormDot = dot(grad.rgb, rayDir); + // n.b. "lightNormDor" is cosTheta, "silhouettePower" is Fresnel effect exponent + colorSample.a *= pow(1.0 - abs(lightNormDot), silhouettePower); + float viewAlign = abs(lightNormDot); // 0 = perpendicular, 1 = aligned + // linearly map silhouettePower (0..1) to a threshold range, e.g., [1.0, 0.0] + // Cull voxels that are too aligned with the view direction + if (viewAlign > silhouetteThreshold) + colorSample.a = 0.0; + colorSample.rgb *= colorSample.a; + colAcc= (1.0 - colAcc.a) * colorSample + colAcc; + if ( colAcc.a > earlyTermination ) + break; + } + samplePos += deltaDir; //advance ray position + } +` + _i, hh = ea + ni + qs + ` + float startPos = samplePos.a; + //float clipClose = clipPos.a + 3.0 * deltaDir.a; //do not apply gradients near clip plane + float brighten = 2.0; //modulating makes average intensity darker 0.5 * 0.5 = 0.25 + //vec4 prevGrad = vec4(0.0); + while (samplePos.a <= len) { + vec4 colorSample = texture(volume, samplePos.xyz); + if (colorSample.a >= 0.0) { + vec4 grad = texture(gradient, samplePos.xyz); + colorSample.rgb = abs(normalize(grad.rgb*2.0 - 1.0)); + if (firstHit.a > len) + firstHit = samplePos; + backNearest = min(backNearest, samplePos.a); + colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection); + colorSample.rgb *= colorSample.a; + colAcc= (1.0 - colAcc.a) * colorSample + colAcc; + if ( colAcc.a > earlyTermination ) + break; + } + samplePos += deltaDir; //advance ray position + } +` + _i, gi = `#version 300 es +#line 392 +layout(location=0) in vec3 pos; +uniform int axCorSag; +uniform mat4 mvpMtx; +uniform mat4 frac2mm; +uniform float slice; +out vec3 texPos; +void main(void) { + texPos = vec3(pos.x, pos.y, slice); + if (axCorSag > 1) + texPos = vec3(slice, pos.x, pos.y); + else if (axCorSag > 0) + texPos = vec3(pos.x, slice, pos.y); + vec4 mm = frac2mm * vec4(texPos, 1.0); + gl_Position = mvpMtx * mm; +}`, ta = `#version 300 es +#line 411 +precision highp int; +precision highp float; +uniform highp sampler3D volume, overlay; +uniform highp sampler3D paqd; +uniform vec4 paqdUniforms; +uniform int backgroundMasksOverlays; +uniform float overlayOutlineWidth; +uniform float overlayAlphaShader; +uniform int axCorSag; +uniform float overlays; +uniform float opacity; +uniform float drawOpacity; +uniform float drawRimOpacity; +uniform bool isAlphaClipDark; +uniform highp sampler3D drawing; +uniform highp sampler2D colormap; +in vec3 texPos; +out vec4 color; +` + _s + ` +vec4 blendRGBA(vec4 foreground, vec4 background) { + float alphaOut = foreground.a + background.a * (1.0 - foreground.a); + vec3 colorOut = (foreground.rgb * foreground.a + background.rgb * background.a * (1.0 - foreground.a)) / alphaOut; + return vec4(colorOut, alphaOut); +} +float paqdEaseAlpha(float alpha) { + // t are alpha transitions + // y0 + // t0..t1 -> mix between y0..y1 + // t1..t2 -> mix between y1..y2 + // >t2 -> y2 + float t0 = paqdUniforms[0]; // 0.3; + float t1 = 0.5 * (paqdUniforms[0] + paqdUniforms[1]); // 0.4; + float t2 = paqdUniforms[1]; // 0.9; + float y0 = 0.0; + float y1 = abs(paqdUniforms[2]); // 1.0; + float y2 = abs(paqdUniforms[3]); //0.25; + if (alpha <= t0) { + return y0; + } else if (alpha <= t1) { + return mix(y0, y1, (alpha - t0) / (t1 - t0)); // LERP 0.0 → 1.0 + } else if (alpha <= t2) { + return mix(y1, y2, (alpha - t1) / (t2 - t1)); // LERP 1.0 → 0.2 + } else { + return y2; + } +} + +void main() { + //color = vec4(1.0, 0.0, 1.0, 1.0);return; + vec4 background = texture(volume, texPos); + color = vec4(background.rgb, opacity); + if ((isAlphaClipDark) && (background.a == 0.0)) color.a = 0.0; //FSLeyes clipping range + vec4 ocolor = vec4(0.0); + float overlayAlpha = overlayAlphaShader; + if (overlays > 0.0) { + ocolor = texture(overlay, texPos); + //dFdx for "boxing" issue 435 has aliasing on some implementations (coarse vs fine) + //however, this only identifies 50% of the edges due to aliasing effects + // http://www.aclockworkberry.com/shader-derivative-functions/ + // https://bgolus.medium.com/distinctive-derivative-differences-cce38d36797b + //if ((ocolor.a >= 1.0) && ((dFdx(ocolor.a) != 0.0) || (dFdy(ocolor.a) != 0.0) )) + // ocolor.rbg = vec3(0.0, 0.0, 0.0); + bool isOutlineBelowNotAboveThreshold = true; + if (isOutlineBelowNotAboveThreshold) { + if ((overlayOutlineWidth > 0.0) && (ocolor.a < 1.0)) { //check voxel neighbors for edge + vec3 vx = (overlayOutlineWidth ) / vec3(textureSize(overlay, 0)); + //6 voxel neighbors that share a face + vec3 vxR = vec3(texPos.x+vx.x, texPos.y, texPos.z); + vec3 vxL = vec3(texPos.x-vx.x, texPos.y, texPos.z); + vec3 vxA = vec3(texPos.x, texPos.y+vx.y, texPos.z); + vec3 vxP = vec3(texPos.x, texPos.y-vx.y, texPos.z); + vec3 vxS = vec3(texPos.x, texPos.y, texPos.z+vx.z); + vec3 vxI = vec3(texPos.x, texPos.y, texPos.z-vx.z); + float a = 0.0; + if (axCorSag != 2) { + a = max(a, texture(overlay, vxR).a); + a = max(a, texture(overlay, vxL).a); + } + if (axCorSag != 1) { + a = max(a, texture(overlay, vxA).a); + a = max(a, texture(overlay, vxP).a); + } + if (axCorSag != 0) { + a = max(a, texture(overlay, vxS).a); + a = max(a, texture(overlay, vxI).a); + } + bool isCheckCorners = true; + if (isCheckCorners) { + //12 voxel neighbors that share an edge + vec3 vxRA = vec3(texPos.x+vx.x, texPos.y+vx.y, texPos.z); + vec3 vxLA = vec3(texPos.x-vx.x, texPos.y+vx.y, texPos.z); + vec3 vxRP = vec3(texPos.x+vx.x, texPos.y-vx.y, texPos.z); + vec3 vxLP = vec3(texPos.x-vx.x, texPos.y-vx.y, texPos.z); + vec3 vxRS = vec3(texPos.x+vx.x, texPos.y, texPos.z+vx.z); + vec3 vxLS = vec3(texPos.x-vx.x, texPos.y, texPos.z+vx.z); + vec3 vxRI = vec3(texPos.x+vx.x, texPos.y, texPos.z-vx.z); + vec3 vxLI = vec3(texPos.x-vx.x, texPos.y, texPos.z-vx.z); + vec3 vxAS = vec3(texPos.x, texPos.y+vx.y, texPos.z+vx.z); + vec3 vxPS = vec3(texPos.x, texPos.y-vx.y, texPos.z+vx.z); + vec3 vxAI = vec3(texPos.x, texPos.y+vx.y, texPos.z-vx.z); + vec3 vxPI = vec3(texPos.x, texPos.y-vx.y, texPos.z-vx.z); + + if (axCorSag == 0) { //axial corners + a = max(a, texture(overlay, vxRA).a); + a = max(a, texture(overlay, vxLA).a); + a = max(a, texture(overlay, vxRP).a); + a = max(a, texture(overlay, vxLP).a); + } + if (axCorSag == 1) { //coronal corners + a = max(a, texture(overlay, vxRS).a); + a = max(a, texture(overlay, vxLS).a); + a = max(a, texture(overlay, vxRI).a); + a = max(a, texture(overlay, vxLI).a); + } + if (axCorSag == 2) { //sagittal corners + a = max(a, texture(overlay, vxAS).a); + a = max(a, texture(overlay, vxPS).a); + a = max(a, texture(overlay, vxAI).a); + a = max(a, texture(overlay, vxPI).a); + } + } + if (a >= 1.0) { + ocolor = vec4(0.0, 0.0, 0.0, 1.0); + overlayAlpha = 1.0; + } + } + + } else { + if ((overlayOutlineWidth > 0.0) && (ocolor.a >= 1.0)) { //check voxel neighbors for edge + vec3 vx = (overlayOutlineWidth ) / vec3(textureSize(overlay, 0)); + vec3 vxR = vec3(texPos.x+vx.x, texPos.y, texPos.z); + vec3 vxL = vec3(texPos.x-vx.x, texPos.y, texPos.z); + vec3 vxA = vec3(texPos.x, texPos.y+vx.y, texPos.z); + vec3 vxP = vec3(texPos.x, texPos.y-vx.y, texPos.z); + vec3 vxS = vec3(texPos.x, texPos.y, texPos.z+vx.z); + vec3 vxI = vec3(texPos.x, texPos.y, texPos.z-vx.z); + float a = 1.0; + if (axCorSag != 2) { + a = min(a, texture(overlay, vxR).a); + a = min(a, texture(overlay, vxL).a); + } + if (axCorSag != 1) { + a = min(a, texture(overlay, vxA).a); + a = min(a, texture(overlay, vxP).a); + } + if (axCorSag != 0) { + a = min(a, texture(overlay, vxS).a); + a = min(a, texture(overlay, vxI).a); + } + if (a < 1.0) { + ocolor = vec4(0.0, 0.0, 0.0, 1.0); + overlayAlpha = 1.0; + } + } + } //outline above threshold + } + +`, uh = `#version 300 es +#line 411 +precision highp int; +precision highp float; +uniform highp sampler2D volume, overlay; +uniform int backgroundMasksOverlays; +uniform float overlayOutlineWidth; +uniform float overlayAlphaShader; +uniform int axCorSag; +uniform float overlays; +uniform float opacity; +uniform float drawOpacity; +uniform bool isAlphaClipDark; +uniform highp sampler2D drawing; +uniform highp sampler2D colormap; +in vec3 texPos; +out vec4 color;` + _s + `void main() { + //color = vec4(1.0, 0.0, 1.0, 1.0);return; + vec4 background = texture(volume, texPos.xy); + color = vec4(background.rgb, opacity); + if ((isAlphaClipDark) && (background.a == 0.0)) color.a = 0.0; //FSLeyes clipping range + vec4 dcolor = drawColor(texture(drawing, texPos.xy).r, drawOpacity); + if (dcolor.a > 0.0) { + color.rgb = mix(color.rgb, dcolor.rgb, dcolor.a); + color.a = max(drawOpacity, color.a); + } +}`, ia = ` ocolor.a *= overlayAlpha; + float drawV = texture(drawing, texPos).r; + vec4 dcolor = drawColor(drawV, drawOpacity); + if (dcolor.a > 0.0) { + if (drawRimOpacity >= 0.0) { + vec3 vx = 1.0 / vec3(textureSize(drawing, 0)); + //6 voxel neighbors that share a face + vec3 offsetX = dFdx(texPos); // left-right spacing + vec3 offsetY = dFdy(texPos); // up-down spacing + float L = texture(drawing, texPos - offsetX).r; + float R = texture(drawing, texPos + offsetX).r; + float T = texture(drawing, texPos - offsetY).r; + float B = texture(drawing, texPos + offsetY).r; + if (L != drawV || R != drawV || T != drawV || B != drawV) + dcolor.a = drawRimOpacity; + } + color.rgb = mix(color.rgb, dcolor.rgb, dcolor.a); + color.a = max(drawOpacity, color.a); + } + vec4 pcolor = texture(paqd, texPos); + if (pcolor.a > 0.0) { + pcolor.a = paqdEaseAlpha(pcolor.a); + if (pcolor.a > 0.0) { + if (paqdUniforms[3] < 0.0) + ocolor = blendRGBA(pcolor, ocolor); + else + ocolor = blendRGBA(ocolor, pcolor); + } + } + if ((backgroundMasksOverlays > 0) && (background.a == 0.0)) + return; + float a = color.a + ocolor.a * (1.0 - color.a); // premultiplied alpha + if (a == 0.0) return; + color.rgb = mix(color.rgb, ocolor.rgb, ocolor.a / a); + color.a = a; +}`, dh = ta + ia, fh = ta + ` if (ocolor.a > 0.0) { + //https://gamedev.stackexchange.com/questions/102889/is-it-possible-to-convert-vec4-to-int-in-glsl-using-opengl-es + uint alpha = uint(ocolor.a * 255.0); + vec3 xyzFlip = vec3(float((uint(1) & alpha) > uint(0)), float((uint(2) & alpha) > uint(0)), float((uint(4) & alpha) > uint(0))); + //convert from 0 and 1 to -1 and 1 + xyzFlip = (xyzFlip * 2.0) - 1.0; + //https://math.stackexchange.com/questions/1905533/find-perpendicular-distance-from-point-to-line-in-3d + //v1 principle direction of tensor for this voxel + vec3 v1 = ocolor.rgb; + //flips encode polarity to convert from 0..1 to -1..1 (27 bits vs 24 bit precision) + v1 = normalize( v1 * xyzFlip); + vec3 vxl = fract(texPos * vec3(textureSize(volume, 0))) - 0.5; + //vxl coordinates now -0.5..+0.5 so 0,0,0 is origin + vxl.x = -vxl.x; + float t = dot(vxl,v1); + vec3 P = t * v1; + float dx = length(P-vxl); + ocolor.a = 1.0 - smoothstep(0.2,0.25, dx); + //if modulation was applied, use that to scale alpha not color: + ocolor.a *= length(ocolor.rgb); + ocolor.rgb = normalize(ocolor.rgb); + //compute distance one half voxel closer to viewer: + float pan = 0.5; + if (axCorSag == 0) + vxl.z -= pan; + if (axCorSag == 1) + vxl.y -= pan; + if (axCorSag == 2) + vxl.x += pan; + t = dot(vxl,v1); + P = t * v1; + float dx2 = length(P-vxl); + ocolor.rgb += (dx2-dx-(0.5 * pan)) * 1.0; + } +` + ia, ss = `#version 300 es +#line 480 +precision highp int; +precision highp float; +uniform vec4 lineColor; +out vec4 color; +void main() { + color = lineColor; +}`, mh = `#version 300 es +#line 723 +precision highp int; +precision highp float; + +uniform vec4 lineColor; +uniform vec4 leftTopWidthHeight; +uniform float thickness; // line thickness in pixels +uniform vec2 canvasWidthHeight; + +out vec4 color; + +void main() { + // fragment position in screen coordinates + vec2 fragCoord = gl_FragCoord.xy; + + // canvas height + float canvasHeight = canvasWidthHeight.y; + + // 'top' and 'bottom' to match gl_FragCoord.y coordinate system + float top = canvasHeight - leftTopWidthHeight.y; + float bottom = top - leftTopWidthHeight.w; + + // left and right edges + float left = leftTopWidthHeight.x; + float right = left + leftTopWidthHeight.z; + + bool withinLeft = fragCoord.x >= left && fragCoord.x <= left + thickness; + bool withinRight = fragCoord.x <= right && fragCoord.x >= right - thickness; + bool withinTop = fragCoord.y <= top && fragCoord.y >= top - thickness; + bool withinBottom = fragCoord.y >= bottom && fragCoord.y <= bottom + thickness; + + bool isOutline = withinLeft || withinRight || withinTop || withinBottom; + + if (isOutline) { + color = lineColor; + } else { + discard; + } +}`, gh = `#version 300 es +#line 490 +layout(location=0) in vec3 pos; +uniform vec2 canvasWidthHeight; +uniform vec4 leftTopWidthHeight; +out vec2 vColor; +void main(void) { + //convert pixel x,y space 1..canvasWidth,1..canvasHeight to WebGL 1..-1,-1..1 + vec2 frac; + frac.x = (leftTopWidthHeight.x + (pos.x * leftTopWidthHeight.z)) / canvasWidthHeight.x; //0..1 + frac.y = 1.0 - ((leftTopWidthHeight.y + ((1.0 - pos.y) * leftTopWidthHeight.w)) / canvasWidthHeight.y); //1..0 + frac = (frac * 2.0) - 1.0; + gl_Position = vec4(frac, 0.0, 1.0); + vColor = pos.xy; +}`, ph = `#version 300 es +#line 506 +precision highp int; +precision highp float; +uniform highp sampler2D colormap; +uniform float layer; +in vec2 vColor; +out vec4 color; +void main() { + float nlayer = float(textureSize(colormap, 0).y); + float fmap = (0.5 + layer) / nlayer; + color = vec4(texture(colormap, vec2(vColor.x, fmap)).rgb, 1.0); +}`, Ur = `#version 300 es +#line 520 +layout(location=0) in vec3 pos; +uniform vec2 canvasWidthHeight; +uniform vec4 leftTopWidthHeight; +void main(void) { + //convert pixel x,y space 1..canvasWidth,1..canvasHeight to WebGL 1..-1,-1..1 + vec2 frac; + frac.x = (leftTopWidthHeight.x + (pos.x * leftTopWidthHeight.z)) / canvasWidthHeight.x; //0..1 + frac.y = 1.0 - ((leftTopWidthHeight.y + ((1.0 - pos.y) * leftTopWidthHeight.w)) / canvasWidthHeight.y); //1..0 + frac = (frac * 2.0) - 1.0; + gl_Position = vec4(frac, 0.0, 1.0); +}`, Ah = `#version 300 es +#line 534 +layout(location=0) in vec3 pos; +uniform vec2 canvasWidthHeight; +uniform float thickness; +uniform vec4 startXYendXY; +void main(void) { + vec2 posXY = mix(startXYendXY.xy, startXYendXY.zw, pos.x); + vec2 dir = normalize(startXYendXY.xy - startXYendXY.zw); + posXY += vec2(-dir.y, dir.x) * thickness * (pos.y - 0.5); + posXY.x = (posXY.x) / canvasWidthHeight.x; //0..1 + posXY.y = 1.0 - (posXY.y / canvasWidthHeight.y); //1..0 + gl_Position = vec4((posXY * 2.0) - 1.0, 0.0, 1.0); +}`, vh = `#version 300 es +#line 534 +layout(location=0) in vec3 pos; +uniform vec2 canvasWidthHeight; +uniform float thickness; +uniform vec2 startXY; +uniform vec3 endXYZ; // transformed XYZ point +void main(void) { + vec2 posXY = mix(startXY.xy, endXYZ.xy, pos.x); + vec2 startDiff = endXYZ.xy - startXY.xy; + float startDistance = length(startDiff); + vec2 diff = endXYZ.xy - posXY; + float currentDistance = length(diff); + vec2 dir = normalize(startXY.xy - endXYZ.xy); + posXY += vec2(-dir.y, dir.x) * thickness * (pos.y - 0.5); + posXY.x = (posXY.x) / canvasWidthHeight.x; //0..1 + posXY.y = 1.0 - (posXY.y / canvasWidthHeight.y); //1..0 + float z = endXYZ.z * ( 1.0 - abs(currentDistance/startDistance)); + gl_Position = vec4((posXY * 2.0) - 1.0, z, 1.0); +}`, xh = `#version 300 es +#line 549 +layout(location=0) in vec3 pos; +uniform vec2 canvasWidthHeight; +uniform vec4 leftTopWidthHeight; +out vec2 vUV; +void main(void) { + //convert pixel x,y space 1..canvasWidth,1..canvasHeight to WebGL 1..-1,-1..1 + vec2 frac; + frac.x = (leftTopWidthHeight.x + (pos.x * leftTopWidthHeight.z)) / canvasWidthHeight.x; //0..1 + frac.y = 1.0 - ((leftTopWidthHeight.y + ((1.0 - pos.y) * leftTopWidthHeight.w)) / canvasWidthHeight.y); //1..0 + frac = (frac * 2.0) - 1.0; + gl_Position = vec4(frac, 0.0, 1.0); + vUV = vec2(pos.x, 1.0 - pos.y); +}`, wh = `#version 300 es +#line 565 +precision highp int; +precision highp float; +uniform highp sampler2D bmpTexture; +in vec2 vUV; +out vec4 color; +void main() { + color = texture(bmpTexture, vUV); +}`, bh = `#version 300 es +#line 576 +layout(location=0) in vec3 pos; +uniform vec2 canvasWidthHeight; +uniform vec4 leftTopWidthHeight; +uniform vec4 uvLeftTopWidthHeight; +out vec2 vUV; +void main(void) { + //convert pixel x,y space 1..canvasWidth,1..canvasHeight to WebGL 1..-1,-1..1 + vec2 frac; + frac.x = (leftTopWidthHeight.x + (pos.x * leftTopWidthHeight.z)) / canvasWidthHeight.x; //0..1 + frac.y = 1.0 - ((leftTopWidthHeight.y + ((1.0 - pos.y) * leftTopWidthHeight.w)) / canvasWidthHeight.y); //1..0 + frac = (frac * 2.0) - 1.0; + gl_Position = vec4(frac, 0.0, 1.0); + vUV = vec2(uvLeftTopWidthHeight.x + (pos.x * uvLeftTopWidthHeight.z), uvLeftTopWidthHeight.y + ((1.0 - pos.y) * uvLeftTopWidthHeight.w) ); +}`, yh = `#version 300 es +#line 593 +precision highp int; +precision highp float; +uniform highp sampler2D fontTexture; +uniform vec4 fontColor; +uniform float screenPxRange; +in vec2 vUV; +out vec4 color; +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} +void main() { + vec3 msd = texture(fontTexture, vUV).rgb; + float sd = median(msd.r, msd.g, msd.b); + float screenPxDistance = screenPxRange*(sd - 0.5); + float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0); + color = vec4(fontColor.rgb , fontColor.a * opacity); +}`, Ch = `#version 300 es +layout(location=0) in vec3 pos; +uniform vec2 canvasWidthHeight; +uniform vec4 leftTopWidthHeight; +uniform vec4 uvLeftTopWidthHeight; +out vec2 vUV; +void main(void) { + //convert pixel x,y space 1..canvasWidth,1..canvasHeight to WebGL 1..-1,-1..1 + vec2 frac; + frac.x = (leftTopWidthHeight.x + (pos.x * leftTopWidthHeight.z)) / canvasWidthHeight.x; //0..1 + frac.y = 1.0 - ((leftTopWidthHeight.y + ((1.0 - pos.y) * leftTopWidthHeight.w)) / canvasWidthHeight.y); //1..0 + frac = (frac * 2.0) - 1.0; + gl_Position = vec4(frac, 0.0, 1.0); + vUV = pos.xy; +}`, Dh = `#version 300 es +precision highp int; +precision highp float; +uniform vec4 circleColor; +uniform float fillPercent; +in vec2 vUV; +out vec4 color; +void main() { + /* Check if the pixel is inside the circle + and color it with a gradient. Otherwise, color it + transparent */ + float distance = length(vUV-vec2(0.5,0.5)); + if ( distance < 0.5 && distance >= (1.0 - fillPercent) / 2.0){ + color = vec4(circleColor.r,circleColor.g,circleColor.b,circleColor.a) ; + }else{ + color = vec4(0.0,0.0,0.0,0.0); + } +} +`, ht = `#version 300 es +#line 613 +precision highp int; +precision highp float; +in vec3 vPos; +out vec2 TexCoord; +void main() { + TexCoord = vPos.xy; + gl_Position = vec4( (vPos.xy-vec2(0.5,0.5)) * 2.0, 0.0, 1.0); +}`, pi = `#version 300 es +uniform highp usampler3D intensityVol; +`, Nr = `#version 300 es +uniform highp isampler3D intensityVol; +`, Eh = `#version 300 es +uniform highp sampler3D intensityVol; +`, Pr = `#line 1042 +precision highp int; +precision highp float; +in vec2 TexCoord; +out vec4 FragColor; +uniform bool isAdditiveBlend; +uniform float coordZ; +uniform float layer; +uniform highp sampler2D colormap; +uniform lowp sampler3D blend3D; +uniform float opacity; +uniform uint activeIndex; +uniform vec4 xyzaFrac; +uniform mat4 mtx; +float textureWidth; +float nlayer; +float layerY; + +vec4 scalar2color(uint idx) { + float fx = (float(idx) + 0.5) / textureWidth; + vec4 clr = texture(colormap, vec2(fx, layerY)).rgba; + if (clr.a > 0.0) + clr.a = 1.0; + clr.a *= opacity; + return clr; +} +void main(void) { + vec4 vx = vec4(TexCoord.x, TexCoord.y, coordZ, 1.0) * mtx; + uint idx = uint(texture(intensityVol, vx.xyz).r); + if (idx == uint(0)) { + if (layer < 1.0) { + FragColor = vec4(0.0, 0.0, 0.0, 0.0); + return; + } + FragColor = texture(blend3D, vec3(TexCoord.xy, coordZ)); + return; + } + textureWidth = float(textureSize(colormap, 0).x); + nlayer = float(textureSize(colormap, 0).y); + layerY = ((2.0 * layer) + 1.5) / nlayer; + //idx = ((idx - uint(1)) % uint(100))+uint(1); + FragColor = scalar2color(idx); + bool isBorder = false; + vx = vec4(TexCoord.x+xyzaFrac.x, TexCoord.y, coordZ, 1.0) * mtx; + uint R = uint(texture(intensityVol, vx.xyz).r); + vx = vec4(TexCoord.x-xyzaFrac.x, TexCoord.y, coordZ, 1.0) * mtx; + uint L = uint(texture(intensityVol, vx.xyz).r); + vx = vec4(TexCoord.x, TexCoord.y+xyzaFrac.y, coordZ, 1.0) * mtx; + uint A = uint(texture(intensityVol, vx.xyz).r); + vx = vec4(TexCoord.x, TexCoord.y-xyzaFrac.y, coordZ, 1.0) * mtx; + uint P = uint(texture(intensityVol, vx.xyz).r); + vx = vec4(TexCoord.x, TexCoord.y, coordZ+xyzaFrac.z, 1.0) * mtx; + uint S = uint(texture(intensityVol, vx.xyz).r); + vx = vec4(TexCoord.x, TexCoord.y, coordZ-xyzaFrac.z, 1.0) * mtx; + uint I = uint(texture(intensityVol, vx.xyz).r); + vec4 centerColor = FragColor; + FragColor.a += scalar2color(R).a; + FragColor.a += scalar2color(L).a; + FragColor.a += scalar2color(A).a; + FragColor.a += scalar2color(P).a; + FragColor.a += scalar2color(S).a; + FragColor.a += scalar2color(I).a; + FragColor.a /= 7.0; + if ((!isBorder) &&(idx == activeIndex)) { + if (centerColor.a > 0.5) + FragColor.a *= 0.4; + else + FragColor.a =0.8; + } + if (xyzaFrac.a != 0.0) { //outline + if ((idx != R) || (idx != L) || (idx != A) || (idx != P) || (idx != S) || (idx != I)) { + isBorder = true; + if (xyzaFrac.a > 0.0) + FragColor.a = xyzaFrac.a; + else + FragColor = vec4(0.0, 0.0, 0.0, 1.0); + } + } + if (layer < 1.0) return; + vec4 prevColor = texture(blend3D, vec3(TexCoord.xy, coordZ)); + // https://en.wikipedia.org/wiki/Alpha_compositing + float aout = FragColor.a + (1.0 - FragColor.a) * prevColor.a; + if (aout <= 0.0) return; + if (isAdditiveBlend) + FragColor.rgb = ((FragColor.rgb * FragColor.a) + (prevColor.rgb * prevColor.a)) / aout; + else + FragColor.rgb = ((FragColor.rgb * FragColor.a) + (prevColor.rgb * prevColor.a * (1.0 - FragColor.a))) / aout; + FragColor.a = aout; +}`, rs = `#line 691 +precision highp int; +precision highp float; +in vec2 TexCoord; +out vec4 FragColor; +uniform float coordZ; +uniform float layer; +uniform float scl_slope; +uniform float scl_inter; +uniform float cal_max; +uniform float cal_min; +uniform float cal_maxNeg; +uniform float cal_minNeg; +uniform bool isAlphaThreshold; +uniform bool isColorbarFromZero; +uniform bool isAdditiveBlend; +uniform highp sampler2D colormap; +uniform lowp sampler3D blend3D; +uniform int modulation; +uniform highp sampler3D modulationVol; +uniform float opacity; +uniform mat4 mtx; +void main(void) { + vec4 vx = vec4(TexCoord.xy, coordZ, 1.0) * mtx; + if ((vx.x < 0.0) || (vx.x > 1.0) || (vx.y < 0.0) || (vx.y > 1.0) || (vx.z < 0.0) || (vx.z > 1.0)) { + //set transparent if out of range + //https://webglfundamentals.org/webgl/webgl-3d-textures-repeat-clamp.html + FragColor = texture(blend3D, vec3(TexCoord.xy, coordZ)); + return; + } + float f = (scl_slope * float(texture(intensityVol, vx.xyz).r)) + scl_inter; + float mn = cal_min; + float mx = cal_max; + if ((isAlphaThreshold) || (isColorbarFromZero)) + mn = 0.0; + float r = max(0.00001, abs(mx - mn)); + mn = min(mn, mx); + float txl = mix(0.0, 1.0, (f - mn) / r); + if (f > mn) { //issue1139: survives threshold, so round up to opaque voxel + txl = max(txl, 2.0/256.0); + } + //https://stackoverflow.com/questions/5879403/opengl-texture-coordinates-in-pixel-space + float nlayer = float(textureSize(colormap, 0).y); + //each volume has two color maps: + // (layer*2) = negative and (layer * 2) + 1 = positive + float y = ((2.0 * layer) + 1.5)/nlayer; + FragColor = texture(colormap, vec2(txl, y)).rgba; + //negative colors + mn = cal_minNeg; + mx = cal_maxNeg; + if ((isAlphaThreshold) || (isColorbarFromZero)) + mx = 0.0; + //if ((!isnan(cal_minNeg)) && ( f < mx)) { + if ((cal_minNeg < cal_maxNeg) && ( f < mx)) { + r = max(0.00001, abs(mx - mn)); + mn = min(mn, mx); + txl = 1.0 - mix(0.0, 1.0, (f - mn) / r); + //issue1139: survives threshold, so round up to opaque voxel + txl = max(txl, 2.0/256.0); + y = ((2.0 * layer) + 0.5)/nlayer; + FragColor = texture(colormap, vec2(txl, y)); + } + if (layer > 0.7) + FragColor.a = step(0.00001, FragColor.a); + //if (modulation > 10) + // FragColor.a *= texture(modulationVol, vx.xyz).r; + // FragColor.rgb *= texture(modulationVol, vx.xyz).r; + if (isAlphaThreshold) { + if ((cal_minNeg != cal_maxNeg) && ( f < 0.0) && (f > cal_maxNeg)) + FragColor.a = pow(-f / -cal_maxNeg, 2.0); + else if ((f > 0.0) && (cal_min > 0.0)) + FragColor.a *= pow(f / cal_min, 2.0); //issue435: A = (V/X)**2 + //FragColor.g = 0.0; + } else if (isColorbarFromZero) { + if ((cal_minNeg != cal_maxNeg) && ( f < 0.0) && (f > cal_maxNeg)) + FragColor.a = 0.0; + else if ((f > 0.0) && (cal_min > 0.0) && (f < cal_min)) + FragColor.a *= 0.0; + + } + if (modulation == 1) { + FragColor.rgb *= texture(modulationVol, vx.xyz).r; + } else if (modulation == 2) { + FragColor.a = texture(modulationVol, vx.xyz).r; + } + FragColor.a *= opacity; + if (layer < 1.0) return; + vec4 prevColor = texture(blend3D, vec3(TexCoord.xy, coordZ)); + // https://en.wikipedia.org/wiki/Alpha_compositing + float aout = FragColor.a + (1.0 - FragColor.a) * prevColor.a; + if (aout <= 0.0) return; + if (isAdditiveBlend) + FragColor.rgb = ((FragColor.rgb * FragColor.a) + (prevColor.rgb * prevColor.a)) / aout; + else + FragColor.rgb = ((FragColor.rgb * FragColor.a) + (prevColor.rgb * prevColor.a * (1.0 - FragColor.a))) / aout; + FragColor.a = aout; +}`, Fh = `#line 773 +precision highp int; +precision highp float; +in vec2 TexCoord; +out vec4 FragColor; +uniform float coordZ; +uniform float layer; +uniform float scl_slope; +uniform float scl_inter; +uniform float cal_max; +uniform float cal_min; +uniform highp sampler2D colormap; +uniform lowp sampler3D blend3D; +uniform float opacity; +uniform mat4 mtx; +uniform bool hasAlpha; +uniform int modulation; +uniform highp sampler3D modulationVol; +float textureWidth; +float nlayer; +float layerY; + +vec4 scalar2color(uint idx) { + float fx = (float(idx) + 0.5) / textureWidth; + vec4 clr = texture(colormap, vec2(fx, layerY)).rgba; + if (clr.a > 0.0) + clr.a = 1.0; + clr.a *= opacity; + return clr; +} + +vec4 paqd2color(uvec4 rgba) { + // paqd r: max prob index, g: 2nd index, b: max prob a: 2nd prob + float prob1 = float(rgba.b)/255.0; + float prob2 = float(rgba.a)/255.0; + vec4 clr1 = scalar2color(rgba.r); + vec4 clr2 = scalar2color(rgba.g); + float total = prob1 + prob2; + vec4 clr = vec4(clr1.rgb, total); + // vec4 clr = vec4(clr1.rgb, prob1); + if (total > 0.0) { + clr.rgb = mix(clr2.rgb, clr1.rgb, prob1 / total); + } + return clr; +} +void main(void) { + vec4 vx = vec4(TexCoord.xy, coordZ, 1.0) * mtx; + ivec3 voxelCoord = ivec3(vx.xyz * vec3(textureSize(intensityVol, 0))); + uvec4 rgba = texelFetch(intensityVol, voxelCoord, 0); + FragColor = vec4(0.0, 0.0, 0.0, 0.0); + if (rgba.r > uint(0)) { + textureWidth = float(textureSize(colormap, 0).x); + nlayer = float(textureSize(colormap, 0).y); + layerY = ((2.0 * layer) + 1.5) / nlayer; + FragColor = paqd2color(rgba); + return; + } + // if (layer > 2.0) return; + // FragColor = texture(blend3D, vec3(TexCoord.xy, coordZ)); +}`, Mh = `#line 773 +precision highp int; +precision highp float; +in vec2 TexCoord; +out vec4 FragColor; +uniform float coordZ; +uniform float layer; +uniform float scl_slope; +uniform float scl_inter; +uniform float cal_max; +uniform float cal_min; +uniform highp sampler2D colormap; +uniform lowp sampler3D blend3D; +uniform float opacity; +uniform mat4 mtx; +uniform bool hasAlpha; +uniform int modulation; +uniform highp sampler3D modulationVol; +void main(void) { + vec4 vx = vec4(TexCoord.xy, coordZ, 1.0) * mtx; + uvec4 aColor = texture(intensityVol, vx.xyz); + FragColor = vec4(float(aColor.r) / 255.0, float(aColor.g) / 255.0, float(aColor.b) / 255.0, float(aColor.a) / 255.0); + if (modulation == 1) + FragColor.rgb *= texture(modulationVol, vx.xyz).r; + if (!hasAlpha) { + FragColor.a = (FragColor.r * 0.21 + FragColor.g * 0.72 + FragColor.b * 0.07); + //next line: we could binarize alpha, but see rendering of visible human + //FragColor.a = step(0.01, FragColor.a); + } + if (modulation == 2) + FragColor.a = texture(modulationVol, vx.xyz).r; + FragColor.a *= opacity; + if (layer < 1.0) return; + vec4 prevColor = texture(blend3D, vec3(TexCoord.xy, coordZ)); + // https://en.wikipedia.org/wiki/Alpha_compositing + float aout = FragColor.a + (1.0 - FragColor.a) * prevColor.a; + if (aout <= 0.0) return; + FragColor.rgb = ((FragColor.rgb * FragColor.a) + (prevColor.rgb * prevColor.a * (1.0 - FragColor.a))) / aout; + FragColor.a = aout; +}`, Th = `#version 300 es +#line 808 +precision highp int; +precision highp float; +in vec3 vPos; +out vec2 TexCoord; +void main() { + TexCoord = vPos.xy; + gl_Position = vec4((vPos.x - 0.5) * 2.0, (vPos.y - 0.5) * 2.0, 0.0, 1.0); +}`, Sh = `#version 300 es +#line 829 + precision highp float; + precision highp int; + precision highp isampler3D; + layout(location = 0) out int label; + layout(location = 1) out int strength; + in vec2 TexCoord; + uniform int finalPass; + uniform float coordZ; + uniform lowp sampler3D in3D; + uniform highp isampler3D backTex; // background + uniform highp isampler3D labelTex; // label + uniform highp isampler3D strengthTex; // strength +void main(void) { + vec3 interpolatedTextureCoordinate = vec3(TexCoord.xy, coordZ); + ivec3 size = textureSize(backTex, 0); + ivec3 texelIndex = ivec3(floor(interpolatedTextureCoordinate * vec3(size))); + int background = texelFetch(backTex, texelIndex, 0).r; + label = texelFetch(labelTex, texelIndex, 0).r; + strength = texelFetch(strengthTex, texelIndex, 0).r; + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + if (i != 0 && j != 0 && k != 0) { + ivec3 neighborIndex = texelIndex + ivec3(i,j,k); + int neighborBackground = texelFetch(backTex, neighborIndex, 0).r; + int neighborStrength = texelFetch(strengthTex, neighborIndex, 0).r; + int strengthCost = abs(neighborBackground - background); + int takeoverStrength = neighborStrength - strengthCost; + if (takeoverStrength > strength) { + strength = takeoverStrength; + label = texelFetch(labelTex, neighborIndex, 0).r; + } + } + } + } + } + if (finalPass < 1) + return; + int ok = 1; + ivec4 labelCount = ivec4(0,0,0,0); + for (int k = -1; k <= 1; k++) + for (int j = -1; j <= 1; j++) + for (int i = -1; i <= 1; i++) { + ivec3 neighborIndex = texelIndex + ivec3(i,j,k); + int ilabel = texelFetch(labelTex, neighborIndex, 0).r; + if ((ilabel < 0) || (ilabel > 3)) + ok = 0; + else + labelCount[ilabel]++; + } + if (ok != 1) { + return; + } + int maxIdx = 0; + for (int i = 1; i < 4; i++) { + if (labelCount[i] > labelCount[maxIdx]) + maxIdx = i; + } + label = maxIdx; +}`, Ih = `#version 300 es +layout(location=0) in vec3 pos; +uniform mat4 mvpMtx; +void main(void) { + gl_Position = mvpMtx * vec4(pos, 1.0); +}`, Bh = `#version 300 es +precision highp int; +precision highp float; +uniform vec4 surfaceColor; +out vec4 color; +void main() { + color = surfaceColor; +}`, kh = `#version 300 es +layout(location=0) in vec3 pos; +layout(location=1) in vec4 clr; +out vec4 vClr; +uniform mat4 mvpMtx; +void main(void) { + gl_Position = mvpMtx * vec4(pos, 1.0); + vClr = clr; +}`, Rh = `#version 300 es +precision highp int; +precision highp float; +in vec4 vClr; +out vec4 color; +uniform float opacity; +void main() { + color = vec4(vClr.rgb, opacity); +}`, ns = `#version 300 es +layout(location=0) in vec3 pos; +layout(location=1) in vec4 norm; +layout(location=2) in vec4 clr; +uniform mat4 mvpMtx; +//uniform mat4 modelMtx; +uniform mat4 normMtx; +out vec4 vClr; +out vec3 vN; +out vec4 vP; +out vec4 vPc; +void main(void) { + vec3 lightPosition = vec3(0.0, 0.0, -10.0); + vP = vec4(pos, 1.0); + vPc = mvpMtx * vec4(pos, 1.0); + gl_Position = vPc; + vN = normalize((normMtx * vec4(norm.xyz,1.0)).xyz); + //vV = -vec3(modelMtx*vec4(pos,1.0)); + vClr = clr; +}`, Vh = `#version 300 es +precision highp int; +precision highp float; +uniform float opacity; +out vec4 color; +vec4 packFloatToVec4i(const float value) { + const vec4 bitSh = vec4(256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0); + const vec4 bitMsk = vec4(0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0); + vec4 res = fract(value * bitSh); + res -= res.xxyz * bitMsk; + return res; +} +void main() { + color = packFloatToVec4i(gl_FragCoord.z); +}`, Uh = `#version 300 es +precision highp int; +precision highp float; +uniform float opacity; +in vec4 vClr; +in vec3 vN; +out vec4 color; +float stepmix(float edge0, float edge1, float E, float x){ + float T = clamp(0.5 * (x - edge0 + E) / E, 0.0, 1.0); + return mix(edge0, edge1, T); +} +void main() { + vec3 r = vec3(0.0, 0.0, 1.0); + float ambient = 0.3; + float diffuse = 0.6; + float specular = 0.5; + float shininess = 50.0; + vec3 n = normalize(vN); + vec3 lightPosition = vec3(0.0, 10.0, -5.0); + vec3 l = normalize(lightPosition); + float df = max(0.0, dot(n, l)); + float sf = pow(max(dot(reflect(l, n), r), 0.0), shininess); + const float A = 0.1; + const float B = 0.3; + const float C = 0.6; + const float D = 1.0; + float E = fwidth(df); + if (df > A - E && df < A + E) df = stepmix(A, B, E, df); + else if (df > B - E && df < B + E) df = stepmix(B, C, E, df); + else if (df > C - E && df < C + E) df = stepmix(C, D, E, df); + else if (df < A) df = 0.0; + else if (df < B) df = B; + else if (df < C) df = C; + else df = D; + E = fwidth(sf); + if (sf > 0.5 - E && sf < 0.5 + E) + sf = smoothstep(0.5 - E, 0.5 + E, sf); + else + sf = step(0.5, sf); + vec3 a = vClr.rgb * ambient; + vec3 d = max(df, 0.0) * vClr.rgb * diffuse; + color.rgb = a + d + (specular * sf); + color.a = opacity; +}`, Nh = `#version 300 es +precision highp int; +precision highp float; +uniform float opacity; +in vec4 vClr; +in vec3 vN; +out vec4 color; +void main() { + vec3 r = vec3(0.0, 0.0, 1.0); //rayDir: for orthographic projections moving in Z direction (no need for normal matrix) + float ambient = 0.3; + float diffuse = 0.6; + float specular = 0.25; + float shininess = 10.0; + float PenWidth = 0.6; + vec3 n = normalize(vN); + vec3 lightPosition = vec3(0.0, 10.0, -5.0); + vec3 l = normalize(lightPosition); + float lightNormDot = dot(n, l); + float view = abs(dot(n,r)); //with respect to viewer + if (PenWidth < view) discard; + vec3 a = vClr.rgb * ambient; + vec3 d = max(lightNormDot, 0.0) * vClr.rgb * diffuse; + float s = specular * pow(max(dot(reflect(l, n), r), 0.0), shininess); + color.rgb = a + d + s; + color.a = opacity; +}`, Ph = `#version 300 es +precision highp int; +precision highp float; +uniform float opacity; +in vec4 vClr; +in vec3 vN; +out vec4 color; +void main() { + const float thresh = 0.4; + const vec3 viewDir = vec3(0.0, 0.0, -1.0); + vec3 n = normalize(vN); + // use abs() for two-sided lighting, max() for one sided + float cosTheta = abs(dot(n, viewDir)); + // float cosTheta = max(dot(n, viewDir), 0.0); + // optional fresnel equation - adjust exponent + // cosTheta = 1.0 - pow(1.0 - cosTheta, 2.0); + // use step for binary edges, smoothstep for feathered edges + // vec3 d = step(thresh, cosTheta) * vClr.rgb; + vec3 d = smoothstep(thresh - 0.05, thresh + 0.05, cosTheta) * vClr.rgb; + color = vec4(d, opacity); +}`, Lh = `#version 300 es +precision highp int; +precision highp float; +uniform float opacity; +in vec4 vClr; +in vec3 vN; +out vec4 color; +void main() { + const float edge0 = 0.1; + const float edge1 = 0.25; + const vec3 viewDir = vec3(0.0, 0.0, -1.0); + vec3 n = normalize(vN); + float cosTheta = abs(dot(n, viewDir)); + float alpha = 1.0 - smoothstep(edge0, edge1, cosTheta); + if (alpha <= 0.0) { + discard; + } + color = vec4(0.0, 0.0, 0.0, opacity * alpha); +}`, Oh = `#version 300 es +precision highp int; +precision highp float; +uniform float opacity; +in vec4 vClr; +in vec3 vN; +out vec4 color; +void main() { + vec3 r = vec3(0.0, 0.0, 1.0); //rayDir: for orthographic projections moving in Z direction (no need for normal matrix) + float diffuse = 1.0; + float specular = 0.2; + float shininess = 10.0; + vec3 n = normalize(vN); + vec3 lightPosition = vec3(0.0, 0.0, -5.0); + vec3 l = normalize(lightPosition); + float lightNormDot = max(dot(n, l), 0.0); + vec3 d = lightNormDot * vClr.rgb * diffuse; + float s = specular * pow(max(dot(reflect(l, n), r), 0.0), shininess); + color = vec4(d + s, opacity); +}`, zh = `#version 300 es +precision highp int; +precision highp float; +uniform float opacity; +in vec4 vClr; +in vec3 vN; +out vec4 color; +void main() { + float diffuse = 1.4; + vec3 l = vec3(0.0, 0.0, -1.0); + float lightNormDot = max(dot(normalize(vN), l), 0.0); + color = vec4(lightNormDot * vClr.rgb * diffuse, opacity); +}`, Gh = `#version 300 es +precision highp int; +precision highp float; +uniform float opacity; +in vec4 vClr; +in vec3 vN; +out vec4 color; +void main() { + float specularRGB = 0.7; + float specularWhite = 0.3; + float shininess = 10.0; + float diffuse = 1.0; + vec3 r = vec3(0.0, 0.0, 1.0); //rayDir: for orthographic projections moving in Z direction (no need for normal matrix) + vec3 n = normalize(vN); + vec3 l = vec3(0.0, 0.0, -1.0); + float lightNormDot = max(dot(n, l), 0.0); + vec3 d3 = lightNormDot * vClr.rgb * diffuse; + float s = pow(max(dot(reflect(l, n), r), 0.0), shininess); + vec3 s3 = specularRGB * s * vClr.rgb; + s *= specularWhite; + color = vec4(d3 + s3 + s, opacity); +}`, Yh = `#version 300 es +precision highp int; +precision highp float; +uniform float opacity; +in vec4 vClr; +in vec3 vN; +in vec4 vPc; +out vec4 color; +void main() { + vec3 n = normalize(vN); + // Compute curvature + vec3 dx = dFdx(n); + vec3 dy = dFdy(n); + vec3 xneg = n - dx; + vec3 xpos = n + dx; + vec3 yneg = n - dy; + vec3 ypos = n + dy; + float depth = length(vPc.xyz); + float curv = (cross(xneg, xpos).y - cross(yneg, ypos).x) / depth; + //at this stage 0.5 for flat, with valleys dark and ridges bright + curv = 1.0 - (curv + 0.5); + //clamp + curv = min(max(curv, 0.0), 1.0); + // easing function + curv = pow(curv, 0.5); + //modulate ambient and diffuse with curvature + vec3 r = vec3(0.0, 0.0, 1.0); //rayDir: for orthographic projections moving in Z direction (no need for normal matrix) + float ambient = 0.6; + float diffuse = 0.6; + float specular = 0.2; + float shininess = 10.0; + vec3 lightPosition = vec3(0.0, 10.0, -2.0); + vec3 l = normalize(lightPosition); + float lightNormDot = dot(n, l); + vec3 a = vClr.rgb * ambient * curv; + vec3 d = max(lightNormDot, 0.0) * vClr.rgb * diffuse; + float s = specular * pow(max(dot(reflect(l, n), r), 0.0), shininess); + color = vec4(a + d + s, opacity); +}`, _h = `#version 300 es +precision highp int; +precision highp float; +uniform vec4 sliceMM; +uniform float thickMM ; +in vec4 vClr; +in vec4 vP; // vertex position in mm +out vec4 color; +void main() { + const float LINE_WIDTH_PX = 4.0; // target thickness in pixels + //const float LINE_THRESH_MM = 1.5; // target thickness in pixels + const float TILT_STRENGTH = 1.0; // >0 shrinks ribbon for oblique triangles + // --- signed distances to each orthogonal plane (object space) --- + vec3 d = vP.xyz - sliceMM.xyz; + vec3 ad = abs(d); + // --- derivatives to get pixel-consistent widths (per-axis) --- + vec3 fd = fwidth(vP.xyz); + //minDist is in mm not pixels + float minDist = min(ad.x, min(ad.y, ad.z)); + if (minDist > sliceMM.w) discard; + // --- per-plane obliqueness: use the two in-plane components' fwidth --- + float tiltX = length(fd.yz); // for plane with normal X + float tiltY = length(fd.xz); // for plane with normal Y + float tiltZ = length(fd.xy); // for plane with normal Z + float tfX = clamp(1.0 / (1.0 + TILT_STRENGTH * tiltX), 0.0, 1.0); + float tfY = clamp(1.0 / (1.0 + TILT_STRENGTH * tiltY), 0.0, 1.0); + float tfZ = clamp(1.0 / (1.0 + TILT_STRENGTH * tiltZ), 0.0, 1.0); + // --- half-widths for each plane (apply per-axis tilt factor) --- + vec3 halfWidth; + halfWidth.x = (LINE_WIDTH_PX * 0.5) * fd.x * tfX; + halfWidth.y = (LINE_WIDTH_PX * 0.5) * fd.y * tfY; + halfWidth.z = (LINE_WIDTH_PX * 0.5) * fd.z * tfZ; + // --- smooth alpha for each plane --- + vec3 edgeA = 1.0 - smoothstep(vec3(0.0), halfWidth, ad); + // combine planes (max of X,Y,Z ribbons) + float edgeAlpha = max(edgeA.x, max(edgeA.y, edgeA.z)); + if (edgeAlpha <= 1e-4) discard; // outside ribbons + color = vec4(vClr.rgb, vClr.a * edgeAlpha); +}`, qh = `#version 300 es +precision highp int; +precision highp float; +uniform float opacity; +in vec4 vClr; +in vec3 vN; +out vec4 color; +void main() { + vec3 r = vec3(0.0, 0.0, 1.0); //rayDir: for orthographic projections moving in Z direction (no need for normal matrix) + float ambient = 0.35; + float diffuse = 0.5; + float specular = 0.2; + float shininess = 10.0; + vec3 n = normalize(vN); + vec3 lightPosition = vec3(0.0, 10.0, -5.0); + vec3 l = normalize(lightPosition); + float lightNormDot = dot(n, l); + vec3 a = vClr.rgb * ambient; + vec3 d = max(lightNormDot, 0.0) * vClr.rgb * diffuse; + float s = specular * pow(max(dot(reflect(l, n), r), 0.0), shininess); + color = vec4(a + d + s, opacity); +}`, Hh = `#version 300 es +precision highp int; +precision highp float; +uniform float opacity; +in vec4 vClr; +in vec3 vN; +uniform sampler2D matCap; +out vec4 color; +void main() { + vec3 n = normalize(vN); + vec2 uv = n.xy * 0.5 + 0.5; + uv.y = 1.0 - uv.y; + vec3 clr = texture(matCap,uv.xy).rgb * vClr.rgb; + color = vec4(clr, opacity); +}`, Wh = `#version 300 es +precision highp int; +precision highp float; +uniform float opacity; +in vec4 vClr; +in vec3 vN; +out vec4 color; +void main() { + float ambient = 0.35; + float diffuse = 0.6; + vec3 n = normalize(vN); + vec3 lightPosition = vec3(0.0, 10.0, -5.0); + vec3 l = normalize(lightPosition); + float lightNormDot = dot(n, l); + vec3 a = vClr.rgb * ambient; + vec3 d = max(lightNormDot, 0.0) * vClr.rgb * diffuse; + color = vec4(a + d, opacity); +}`, Kh = `#version 300 es +precision highp int; +precision highp float; +uniform float opacity; +in vec4 vClr; +in vec3 vN; +out vec4 color; +void main() { + vec3 r = vec3(0.0, 0.0, 1.0); //rayDir: for orthographic projections moving in Z direction (no need for normal matrix) + float ambient = 0.35; + float diffuse = 0.5; + float specular = 0.2; + float shininess = 10.0; + vec3 n = normalize(vN); + vec3 lightPosition = vec3(0.0, 10.0, -5.0); + vec3 l = normalize(lightPosition); + float lightNormDot = dot(n, l); + vec3 up = vec3(0.0, 1.0, 0.0); + float ax = dot(n, up) * 0.5 + 0.5; //Shreiner et al. (2013) OpenGL Programming Guide, 8th Ed., p 388. ISBN-10: 0321773039 + vec3 upClr = vec3(1.0, 1.0, 0.95); + vec3 downClr = vec3(0.4, 0.4, 0.6); + vec3 a = vClr.rgb * ambient; + a *= mix(downClr, upClr, ax); + vec3 d = max(lightNormDot, 0.0) * vClr.rgb * diffuse; + float s = specular * pow(max(dot(reflect(l, n), r), 0.0), shininess); + color = vec4(a + d + s, opacity); +}`, Xh = `#version 300 es +precision highp int; +precision highp float; +uniform float opacity; +in vec4 vClr; +in vec3 vN; +out vec4 color; +//Spherical harmonics constants +const float C1 = 0.429043; +const float C2 = 0.511664; +const float C3 = 0.743125; +const float C4 = 0.886227; +const float C5 = 0.247708; +//Spherical harmonics coefficients +// Ramamoorthi, R., and P. Hanrahan. 2001b. "An Efficient Representation for Irradiance Environment Maps." In Proceedings of SIGGRAPH 2001, pp. 497–500. +// https://github.com/eskimoblood/processingSketches/blob/master/data/shader/shinyvert.glsl +// https://github.com/eskimoblood/processingSketches/blob/master/data/shader/shinyvert.glsl +// Constants for Eucalyptus Grove lighting +const vec3 L00 = vec3( 0.3783264, 0.4260425, 0.4504587); +const vec3 L1m1 = vec3( 0.2887813, 0.3586803, 0.4147053); +const vec3 L10 = vec3( 0.0379030, 0.0295216, 0.0098567); +const vec3 L11 = vec3(-0.1033028, -0.1031690, -0.0884924); +const vec3 L2m2 = vec3(-0.0621750, -0.0554432, -0.0396779); +const vec3 L2m1 = vec3( 0.0077820, -0.0148312, -0.0471301); +const vec3 L20 = vec3(-0.0935561, -0.1254260, -0.1525629); +const vec3 L21 = vec3(-0.0572703, -0.0502192, -0.0363410); +const vec3 L22 = vec3( 0.0203348, -0.0044201, -0.0452180); +vec3 SH(vec3 vNormal) { + vNormal = vec3(vNormal.x,vNormal.z,vNormal.y); + vec3 diffuseColor = C1 * L22 * (vNormal.x * vNormal.x - vNormal.y * vNormal.y) + + C3 * L20 * vNormal.z * vNormal.z + + C4 * L00 - + C5 * L20 + + 2.0 * C1 * L2m2 * vNormal.x * vNormal.y + + 2.0 * C1 * L21 * vNormal.x * vNormal.z + + 2.0 * C1 * L2m1 * vNormal.y * vNormal.z + + 2.0 * C2 * L11 * vNormal.x + + 2.0 * C2 * L1m1 * vNormal.y + + 2.0 * C2 * L10 * vNormal.z; + return diffuseColor; +} +void main() { + vec3 r = vec3(0.0, 0.0, 1.0); //rayDir: for orthographic projections moving in Z direction (no need for normal matrix) + float ambient = 0.3; + float diffuse = 0.6; + float specular = 0.1; + float shininess = 10.0; + vec3 n = normalize(vN); + vec3 lightPosition = vec3(0.0, 10.0, -5.0); + vec3 l = normalize(lightPosition); + float s = specular * pow(max(dot(reflect(l, n), r), 0.0), shininess); + vec3 a = vClr.rgb * ambient; + vec3 d = vClr.rgb * diffuse * SH(-reflect(n, vec3(l.x, l.y, -l.z)) ); + color = vec4(a + d + s, opacity); +}`, jh = `#version 300 es +layout(location=0) in vec3 pos; +layout(location=1) in vec4 norm; +layout(location=2) in vec4 clr; +uniform mat4 mvpMtx; +//uniform mat4 modelMtx; +uniform mat4 normMtx; +out vec4 vClr; +flat out vec3 vN; +void main(void) { + gl_Position = mvpMtx * vec4(pos, 1.0); + vN = normalize((normMtx * vec4(norm.xyz,1.0)).xyz); + //vV = -vec3(modelMtx*vec4(pos,1.0)); + vClr = clr; +}`, Lr = `#version 300 es +precision highp int; +precision highp float; +uniform float opacity; +in vec4 vClr; +flat in vec3 vN; +out vec4 color; +void main() { + vec3 r = vec3(0.0, 0.0, 1.0); //rayDir: for orthographic projections moving in Z direction (no need for normal matrix) + float ambient = 0.35; + float diffuse = 0.5; + float specular = 0.2; + float shininess = 10.0; + vec3 n = normalize(vN); + vec3 lightPosition = vec3(0.0, 10.0, -5.0); + vec3 l = normalize(lightPosition); + float lightNormDot = dot(n, l); + vec3 a = vClr.rgb * ambient; + vec3 d = max(lightNormDot, 0.0) * vClr.rgb * diffuse; + float s = specular * pow(max(dot(reflect(l, n), r), 0.0), shininess); + color = vec4(a + d + s, opacity); +}`, Zh = `#version 300 es +#line 1260 +#define MAX_CLIP_PLANES 6 +//precision highp int; +precision highp float; +uniform vec3 rayDir; +uniform vec3 volScale; +uniform vec3 texVox; +uniform vec4 clipPlane; +uniform vec4 clipPlanes[MAX_CLIP_PLANES]; +uniform bool isClipCutaway; +uniform highp sampler3D volume, overlay; +uniform highp sampler3D paqd; +uniform vec4 paqdUniforms; +uniform float overlays; +uniform mat4 matRAS; +uniform mat4 mvpMtx; +uniform float drawOpacity, renderOverlayBlend; +uniform highp sampler3D drawing; +uniform highp sampler2D colormap; +uniform int backgroundMasksOverlays; +in vec3 vColor; +out vec4 fColor; +` + ni + ` +void main() { + int id = 254; + vec3 start = vColor; + gl_FragDepth = 1.0; + fColor = vec4(0.0, 0.0, 0.0, 0.0); //assume no hit: ID = 0 + float fid = float(id & 255)/ 255.0; + vec3 backPosition = GetBackPosition(start); + vec3 dir = normalize(backPosition - start); + //clipVolumeStart(start, backPosition); + float len = length(backPosition - start); + float lenVox = length((texVox * start) - (texVox * backPosition)); + if ((lenVox < 0.5) || (len > 3.0)) return;//discard; //length limit for parallel rays + float sliceSize = len / lenVox; //e.g. if ray length is 1.0 and traverses 50 voxels, each voxel is 0.02 in unit cube + float stepSize = sliceSize; //quality: larger step is faster traversal, but fewer samples + float opacityCorrection = stepSize/sliceSize; + dir = normalize(dir); + vec4 samplePos = vec4(start.xyz, 0.0); //ray position + bool hasClip = false; + vec2 sampleRange = vec2(0.0, len); + for (int i = 0; i < MAX_CLIP_PLANES; i++) + clipSampleRange(dir, samplePos, clipPlanes[i], sampleRange, hasClip); + bool isClip = (sampleRange.x > 0.0) || ((sampleRange.y < len) && (sampleRange.y > 0.0)); + //vec4 clipPos = applyClip(dir, samplePos, len, isClip); + if (isClip) fColor = vec4(samplePos.xyz, 253.0 / 255.0); //assume no hit: ID = 0 + if ((isClipCutaway) && (sampleRange.x <= 0.0) && (sampleRange.y >= len)) { + //completely clipped, but ray does not intersect plane + if (hasClip) + samplePos.a = len + 1.0; + else + sampleRange = vec2(0.0, 0.0); + } + //start: OPTIONAL fast pass: rapid traversal until first hit + float stepSizeFast = sliceSize * 1.9; + vec4 deltaDirFast = vec4(dir.xyz * stepSizeFast, stepSizeFast); + while (samplePos.a <= len) { + if (skipSample(samplePos.a, sampleRange) ^^ isClipCutaway) { + samplePos += deltaDirFast; + continue; + } + float val = texture(volume, samplePos.xyz).a; + if (val > 0.01) { + fColor = vec4(samplePos.rgb, fid); + gl_FragDepth = frac2ndc(samplePos.xyz); + break; + } + samplePos += deltaDirFast; //advance ray position + } + //end: fast pass + if ((overlays < 1.0) || (backgroundMasksOverlays > 0)) { + return; //background hit, no overlays + } + //overlay pass + len = min(len, samplePos.a); //only find overlay closer than background + samplePos = vec4(start.xyz, 0.0); //ray position + while (samplePos.a <= len) { + float val = texture(overlay, samplePos.xyz).a; + if (val > 0.01) { + fColor = vec4(samplePos.rgb, fid); + gl_FragDepth = frac2ndc(samplePos.xyz); + return; + } + samplePos += deltaDirFast; //advance ray position + } + //if (fColor.a == 0.0) discard; //no hit in either background or overlays + //you only get here if there is a hit with the background that is closer than any overlay +}`, Qh = `#version 300 es +// an attribute is an input (in) to a vertex shader. +// It will receive data from a buffer +layout(location=0) in vec3 a_position; +layout(location=1) in vec3 a_color; +// A matrix to transform the positions by +uniform mat4 u_matrix; +out vec3 vColor; +// all shaders have a main function +void main() { + // Multiply the position by the matrix. + vec4 pos = vec4(a_position, 1.0); + gl_Position = u_matrix * vec4(pos); + vColor = a_color; +} +`, Jh = `#version 300 es +precision highp float; +uniform vec4 u_color; +in vec3 vColor; +out vec4 outColor; +void main() { + outColor = vec4(vColor, 1.0); +}`, $h = `#version 300 es +#line 1359 +precision highp int; +precision highp float; +in vec3 vPos; +out vec2 TexCoord; +void main() { + TexCoord = vPos.xy; + vec2 viewCoord = (vPos.xy - 0.5) * 2.0; + gl_Position = vec4((vPos.xy - 0.5) * 2.0, 0.0, 1.0); +}`, e0 = `#version 300 es +precision highp int; +precision highp float; +in vec2 TexCoord; +out vec4 FragColor; +uniform float coordZ; +uniform lowp sampler3D in3D; +void main(void) { + FragColor = texture(in3D, vec3(TexCoord.xy, coordZ)); +}`, Ai = `#version 300 es +#line 286 +precision highp int; +precision highp float; +in vec3 vPos; +out vec2 TexCoord; +void main() { + TexCoord = vPos.xy; + gl_Position = vec4( (vPos.xy-vec2(0.5,0.5))* 2.0, 0.0, 1.0); +}`, t0 = `#version 300 es +#line 298 +precision highp int; +precision highp float; +in vec2 TexCoord; +out vec4 FragColor; +uniform float coordZ; +uniform float dX; +uniform float dY; +uniform float dZ; +uniform highp sampler3D intensityVol; +void main(void) { + vec3 vx = vec3(TexCoord.xy, coordZ); + vec4 samp = texture(intensityVol,vx+vec3(+dX,+dY,+dZ)); + samp += texture(intensityVol,vx+vec3(+dX,+dY,-dZ)); + samp += texture(intensityVol,vx+vec3(+dX,-dY,+dZ)); + samp += texture(intensityVol,vx+vec3(+dX,-dY,-dZ)); + samp += texture(intensityVol,vx+vec3(-dX,+dY,+dZ)); + samp += texture(intensityVol,vx+vec3(-dX,+dY,-dZ)); + samp += texture(intensityVol,vx+vec3(-dX,-dY,+dZ)); + samp += texture(intensityVol,vx+vec3(-dX,-dY,-dZ)); + FragColor = samp*0.125; +}`, i0 = `#version 300 es +#line 298 +precision highp int; +precision highp float; +in vec2 TexCoord; +out vec4 FragColor; +uniform float coordZ; +uniform float dX; +uniform float dY; +uniform float dZ; +uniform highp sampler3D intensityVol; +void main(void) { + vec3 vx = vec3(TexCoord.xy, coordZ); + vec4 XYZ = texture(intensityVol,vx+vec3(+dX,+dY,+dZ)); + vec4 OYZ = texture(intensityVol,vx+vec3(0.0,+dY,+dZ)); + vec4 xYZ = texture(intensityVol,vx+vec3(-dX,+dY,+dZ)); + vec4 XOZ = texture(intensityVol,vx+vec3(+dX,0.0,+dZ)); + vec4 OOZ = texture(intensityVol,vx+vec3(0.0,0.0,+dZ)); + vec4 xOZ = texture(intensityVol,vx+vec3(-dX,0.0,+dZ)); + vec4 XyZ = texture(intensityVol,vx+vec3(+dX,-dY,+dZ)); + vec4 OyZ = texture(intensityVol,vx+vec3(0.0,-dY,+dZ)); + vec4 xyZ = texture(intensityVol,vx+vec3(-dX,-dY,+dZ)); + + vec4 XYO = texture(intensityVol,vx+vec3(+dX,+dY,0.0)); + vec4 OYO = texture(intensityVol,vx+vec3(0.0,+dY,0.0)); + vec4 xYO = texture(intensityVol,vx+vec3(-dX,+dY,0.0)); + vec4 XOO = texture(intensityVol,vx+vec3(+dX,0.0,0.0)); + vec4 OOO = texture(intensityVol,vx+vec3(0.0,0.0,0.0)); + vec4 xOO = texture(intensityVol,vx+vec3(-dX,0.0,0.0)); + vec4 XyO = texture(intensityVol,vx+vec3(+dX,-dY,0.0)); + vec4 OyO = texture(intensityVol,vx+vec3(0.0,-dY,0.0)); + vec4 xyO = texture(intensityVol,vx+vec3(-dX,-dY,0.0)); + + vec4 XYz = texture(intensityVol,vx+vec3(+dX,+dY,-dZ)); + vec4 OYz = texture(intensityVol,vx+vec3(0.0,+dY,-dZ)); + vec4 xYz = texture(intensityVol,vx+vec3(-dX,+dY,-dZ)); + vec4 XOz = texture(intensityVol,vx+vec3(+dX,0.0,-dZ)); + vec4 OOz = texture(intensityVol,vx+vec3(0.0,0.0,-dZ)); + vec4 xOz = texture(intensityVol,vx+vec3(-dX,0.0,-dZ)); + vec4 Xyz = texture(intensityVol,vx+vec3(+dX,-dY,-dZ)); + vec4 Oyz = texture(intensityVol,vx+vec3(0.0,-dY,-dZ)); + vec4 xyz = texture(intensityVol,vx+vec3(-dX,-dY,-dZ)); + + vec4 blurred = vec4 (0.0, 0.0, 0.0, 0.0); + blurred.r = 2.0*(xOz.r +xOZ.r +xyO.r +xYO.r +xOO.r +XOz.r +XOZ.r +XyO.r +XYO.r +XOO.r) +xyz.r +xyZ.r +xYz.r +xYZ.r +Xyz.r +XyZ.r +XYz.r +XYZ.r; + blurred.g = 2.0*(Oyz.r +OyZ.r +xyO.r +XyO.r +OyO.r +OYz.r +OYZ.r +xYO.r +XYO.r +OYO.r) +xyz.r +Xyz.r +xyZ.r +XyZ.r +xYz.r +XYz.r +xYZ.r +XYZ.r; + blurred.b = 2.0*(Oyz.r +OYz.r +xOz.r +XOz.r +OOz.r +OyZ.r +OYZ.r +xOZ.r +XOZ.r +OOZ.r) +xyz.r +Xyz.r +xYz.r +XYz.r +xyZ.r +XyZ.r +XyZ.r +XYZ.r; + blurred.a = 0.32*(abs(blurred.r)+abs(blurred.g)+abs(blurred.b)); + // 0.0357 = 1/28 to account for weights, rescale to 2**16, + FragColor = 0.0357*blurred; +}`, sa = ` + gradientSample.a = log2(gradientSample.r*gradientSample.r + gradientSample.g*gradientSample.g + gradientSample.b*gradientSample.b + 1.922337562475971e-06) + 18.988706873717717; +`, s0 = `#version 300 es +#line 323 +precision highp int; +precision highp float; +in vec2 TexCoord; +out vec4 FragColor; +uniform float coordZ; +uniform float dX; +uniform float dY; +uniform float dZ; +uniform highp sampler3D intensityVol; +void main(void) { + vec3 vx = vec3(TexCoord.xy, coordZ); + //Neighboring voxels 'T'op/'B'ottom, 'A'nterior/'P'osterior, 'R'ight/'L'eft + float TAR = texture(intensityVol,vx+vec3(+dX,+dY,+dZ)).r; + float TAL = texture(intensityVol,vx+vec3(+dX,+dY,-dZ)).r; + float TPR = texture(intensityVol,vx+vec3(+dX,-dY,+dZ)).r; + float TPL = texture(intensityVol,vx+vec3(+dX,-dY,-dZ)).r; + float BAR = texture(intensityVol,vx+vec3(-dX,+dY,+dZ)).r; + float BAL = texture(intensityVol,vx+vec3(-dX,+dY,-dZ)).r; + float BPR = texture(intensityVol,vx+vec3(-dX,-dY,+dZ)).r; + float BPL = texture(intensityVol,vx+vec3(-dX,-dY,-dZ)).r; + vec4 gradientSample = vec4 (0.0, 0.0, 0.0, 0.0); + gradientSample.r = BAR+BAL+BPR+BPL -TAR-TAL-TPR-TPL; + gradientSample.g = TPR+TPL+BPR+BPL -TAR-TAL-BAR-BAL; + gradientSample.b = TAL+TPL+BAL+BPL -TAR-TPR-BAR-BPR; +${sa} + // 0.04242020977371934 = 1/(log2(3*8) - log2(1/(255**2*8))) // 3*8 -> max for 1st order gradient + gradientSample.a *= 0.04242020977371934; + gradientSample.rgb = normalize(gradientSample.rgb); + gradientSample.rgb = (gradientSample.rgb * 0.5)+0.5; + FragColor = gradientSample; +}`, r0 = `#version 300 es +#line 323 +precision highp int; +precision highp float; +in vec2 TexCoord; +out vec4 FragColor; +uniform float coordZ; +uniform float dX; +uniform float dY; +uniform float dZ; +uniform float dX2; +uniform float dY2; +uniform float dZ2; +uniform highp sampler3D intensityVol; +void main(void) { + vec3 vx = vec3(TexCoord.xy, coordZ); + //Neighboring voxels 'T'op/'B'ottom, 'A'nterior/'P'osterior, 'R'ight/'L'eft + vec4 TAR = texture(intensityVol,vx+vec3(+dX,+dY,+dZ)); + vec4 TAL = texture(intensityVol,vx+vec3(+dX,+dY,-dZ)); + vec4 TPR = texture(intensityVol,vx+vec3(+dX,-dY,+dZ)); + vec4 TPL = texture(intensityVol,vx+vec3(+dX,-dY,-dZ)); + vec4 BAR = texture(intensityVol,vx+vec3(-dX,+dY,+dZ)); + vec4 BAL = texture(intensityVol,vx+vec3(-dX,+dY,-dZ)); + vec4 BPR = texture(intensityVol,vx+vec3(-dX,-dY,+dZ)); + vec4 BPL = texture(intensityVol,vx+vec3(-dX,-dY,-dZ)); + vec4 T = texture(intensityVol,vx+vec3(+dX2,0.0,0.0)); + vec4 A = texture(intensityVol,vx+vec3(0.0,+dY2,0.0)); + vec4 R = texture(intensityVol,vx+vec3(0.0,0.0,+dZ2)); + vec4 B = texture(intensityVol,vx+vec3(-dX2,0.0,0.0)); + vec4 P = texture(intensityVol,vx+vec3(0.0,-dY2,0.0)); + vec4 L = texture(intensityVol,vx+vec3(0.0,0.0,-dZ2)); + vec4 gradientSample = vec4 (0.0, 0.0, 0.0, 0.0); + gradientSample.r = -4.0*B.r +8.0*(BAR.r+BAL.r+BPR.r+BPL.r) -8.0*(TAR.r+TAL.r+TPR.r+TPL.r) +4.0*T.r; + gradientSample.g = -4.0*P.g +8.0*(TPR.g+TPL.g+BPR.g+BPL.g) -8.0*(TAR.g+TAL.g+BAR.g+BAL.g) +4.0*A.g; + gradientSample.b = -4.0*L.b +8.0*(TAL.b+TPL.b+BAL.b+BPL.b) -8.0*(TAR.b+TPR.b+BAR.b+BPR.b) +4.0*R.b; +${sa} + gradientSample.a *= 0.0325; + gradientSample.rgb = normalize(gradientSample.rgb); + gradientSample.rgb = (gradientSample.rgb * 0.5)+0.5; + FragColor = gradientSample; +}`, n0 = function(i, e, t) { + const s = i.createShader(i.VERTEX_SHADER); + i.shaderSource(s, e), i.compileShader(s); + const r = i.createShader(i.FRAGMENT_SHADER); + i.shaderSource(r, t), i.compileShader(r); + const a = i.createProgram(); + if (i.attachShader(a, s), i.attachShader(a, r), i.linkProgram(a), !i.getProgramParameter(a, i.LINK_STATUS)) + throw console.log(i.getProgramInfoLog(a)), i.getShaderParameter(s, i.COMPILE_STATUS) || console.log("Vertex shader compilation error:", i.getShaderInfoLog(s)), i.getShaderParameter(r, i.COMPILE_STATUS) || console.log("Fragment shader compilation error:", i.getShaderInfoLog(r)), R.error(i.getProgramInfoLog(a)), new Error("Shader failed to link, see console for log"); + return a; +}, te = class { + constructor(i, e, t) { + I(this, "program"), I(this, "uniforms", {}), I(this, "isMatcap"), I(this, "isCrosscut"), this.program = n0(i, e, t); + const s = /uniform[^;]+[ ](\w+);/g, r = /uniform[^;]+[ ](\w+);/, a = e.match(s), n = t.match(s); + a && a.forEach((o) => { + const l = o.match(r); + this.uniforms[l[1]] = -1; + }), n && n.forEach((o) => { + const l = o.match(r); + this.uniforms[l[1]] = -1; + }); + for (const o in this.uniforms) + this.uniforms[o] = i.getUniformLocation(this.program, o); + } + use(i) { + i.useProgram(this.program); + } +}, Or = (i, e) => { + const t = Math.floor(Math.log(i) / Math.log(10)), s = i / Math.pow(10, t); + let r; + return e ? s < 1.5 ? r = 1 : s < 3 ? r = 2 : s < 7 ? r = 5 : r = 10 : s <= 1 ? r = 1 : s <= 2 ? r = 2 : s <= 5 ? r = 5 : r = 10, r * Math.pow(10, t); +}; +function a0(i) { + return new Promise((e, t) => { + let s; + i instanceof File ? s = Promise.resolve(i) : s = new Promise((r, a) => { + i.file(r, a); + }), s.then((r) => { + const a = new FileReader(); + a.onload = () => { + typeof a.result == "string" ? e(a.result) : t(new Error("Expected a string from FileReader.result")); + }, a.onerror = () => { + t(a.error ?? new Error("Unknown FileReader error")); + }, a.readAsDataURL(r); + }).catch((r) => t(r)); + }); +} +function o0(i) { + const e = i.hdr.dims, t = i.permRAS, s = e[1] * e[2] * e[3], r = new Int16Array(s), a = [0, 0, 0]; + for (let f = 0; f < 3; f++) + for (let g = 0; g < 3; g++) + Math.abs(t[f]) - 1 === g && (a[g] = f * Math.sign(t[f])); + let n = 1; + const o = [1, 1, 1], l = [!1, !1, !1]; + for (let f = 0; f < a.length; f++) + for (let g = 0; g < a.length; g++) + Math.abs(a[g]) === f && (o[g] = n, (a[g] < 0 || Object.is(a[g], -0)) && (l[g] = !0), n *= e[g + 1]); + let c = j.range(0, e[1] - 1, 1); + l[0] && (c = j.range(e[1] - 1, 0, -1)); + for (let f = 0; f < e[1]; f++) + c[f] *= o[0]; + let h = j.range(0, e[2] - 1, 1); + l[1] && (h = j.range(e[2] - 1, 0, -1)); + for (let f = 0; f < e[2]; f++) + h[f] *= o[1]; + let u = j.range(0, e[3] - 1, 1); + l[2] && (u = j.range(e[3] - 1, 0, -1)); + for (let f = 0; f < e[3]; f++) + u[f] *= o[2]; + let d = 0; + for (let f = 0; f < e[3]; f++) + for (let g = 0; g < e[2]; g++) + for (let m = 0; m < e[1]; m++) + r[c[m] + h[g] + u[f]] = i.img[d], d++; + return r; +} +function l0(i) { + const e = [5960464477539063e-23, 152587890625e-16, 390625e-8, 1]; + return (i[0] * e[0] + i[1] * e[1] + i[2] * e[2] + i[3] * e[3]) / 255; +} +function zr(i, e) { + return i.scl_slope === 0 && (i.scl_slope = 1), e * i.scl_slope + i.scl_inter; +} +function Gt(i, e, t = 4) { + const s = Or(e - i, !1), r = Or(s / (t - 1), !0), a = Math.floor(i / r) * r, n = Math.ceil(e / r) * r; + return [r, a, n, a === i && n === e]; +} +function Gr(i, e) { + let t = Gt(i, e, 3); + return t[3] || (t = Gt(i, e, 5)), t[3] || (t = Gt(i, e, 4)), t[3] || (t = Gt(i, e, 3)), t[3] || (t = Gt(i, e, 5)), [t[0], t[1], t[2]]; +} +function et(i) { + return i * (Math.PI / 180); +} +function as(i, e, t, s) { + let r = -i, a = -e; + return isFinite(t) && isFinite(s) && (r = t, a = s), r > a && ([r, a] = [a, r]), [r, a]; +} +function os(i, e, t) { + return Math.min(Math.max(i, e), t); +} +function Ae(i, e = [0, 1, 2]) { + const t = le(); + return t[0] = i[e[0]], t[1] = i[e[1]], t[2] = i[e[2]], t; +} +function c0(i) { + const e = pe(1, 0, 0, 0), t = Je(); + return Me(t, e, i), t[0]; +} +function h0(i, e, t, s) { + const r = pe(i, e, t, 1), a = xe(s); + Re(a, a), r[0] = r[0] * 2 - 1, r[1] = r[1] * 2 - 1, r[2] = r[2] * 2 - 1; + const n = Je(); + return Me(n, r, a), n[3] === 0 || (n[0] /= n[3], n[1] /= n[3], n[2] /= n[3]), n; +} +var { version: Yr } = vl, ut = [ + "ASC", + "BYU", + "DFS", + "FSM", + "PIAL", + "ORIG", + "INFLATED", + "SMOOTHWM", + "SPHERE", + "WHITE", + "G", + "GEO", + "GII", + "ICO", + "MZ3", + "NV", + "OBJ", + "OFF", + "PLY", + "SRF", + "STL", + "TCK", + "TRACT", + "TRI", + "TRK", + "TT", + "TRX", + "VTK", + "WRL", + "X3D", + "JCON", + "JSON" +], Yt = 0, ls = 1, cs = 2, dt = 33984, _t = 33985, vi = 33986, _r = 33987, u0 = 33988, hs = 33989, xi = 33990, tt = 33991, Tt = 33992, qr = 33992, Hr = 33993, Wr = 33994, d0 = 33995, f0 = 33996, m0 = 33997, g0 = 33998, p0 = 33999, Kr = { + filename: "", + isSaveDrawing: !1, + volumeByIndex: 0 +}, ft, A0 = class { + /** + * @param options - options object to set modifiable Niivue properties + */ + constructor(i = Ge) { + I(this, "loaders", {}), I(this, "dicomLoader", null), I(this, "canvas", null), I(this, "_gl", null), I(this, "isBusy", !1), I(this, "needsRefresh", !1), I(this, "colormapTexture", null), I(this, "colormapLists", []), I(this, "volumeTexture", null), I(this, "gradientTexture", null), I(this, "gradientTextureAmount", 0), I(this, "useCustomGradientTexture", !1), I(this, "renderGradientValues", !1), I(this, "drawTexture", null), I(this, "paqdTexture", null), I(this, "drawUndoBitmaps", []), I(this, "drawLut", oe.makeDrawLut("$itksnap")), I(this, "drawOpacity", 0.8), I(this, "drawRimOpacity", -1), I(this, "clickToSegmentIsGrowing", !1), I(this, "clickToSegmentGrowingBitmap", null), I(this, "clickToSegmentXY", [0, 0]), I(this, "renderDrawAmbientOcclusion", 0.4), I(this, "colorbarHeight", 0), I(this, "drawPenLocation", [NaN, NaN, NaN]), I(this, "drawPenAxCorSag", -1), I(this, "drawFillOverwrites", !0), I(this, "drawPenFillPts", []), I(this, "drawShapeStartLocation", [NaN, NaN, NaN]), I(this, "drawShapePreviewBitmap", null), I(this, "overlayTexture", null), I(this, "overlayTextureID", null), I(this, "sliceMMShader"), I(this, "slice2DShader"), I(this, "sliceV1Shader"), I(this, "orientCubeShader"), I(this, "orientCubeShaderVAO", null), I(this, "rectShader"), I(this, "rectOutlineShader"), I(this, "renderShader"), I(this, "lineShader"), I(this, "line3DShader"), I(this, "passThroughShader"), I(this, "renderGradientShader"), I(this, "renderGradientValuesShader"), I(this, "renderSliceShader"), I(this, "renderVolumeShader"), I(this, "pickingMeshShader"), I(this, "pickingImageShader"), I(this, "colorbarShader"), I(this, "customSliceShader", null), I(this, "fontShader", null), I(this, "fiberShader"), I(this, "fontTexture", null), I(this, "circleShader"), I(this, "matCapTexture", null), I(this, "bmpShader", null), I(this, "bmpTexture", null), I(this, "thumbnailVisible", !1), I(this, "bmpTextureWH", 1), I(this, "growCutShader"), I(this, "orientShaderAtlasU", null), I(this, "orientShaderAtlasI", null), I(this, "orientShaderU", null), I(this, "orientShaderI", null), I(this, "orientShaderF", null), I(this, "orientShaderRGBU", null), I(this, "orientShaderPAQD", null), I(this, "surfaceShader", null), I(this, "blurShader", null), I(this, "sobelBlurShader", null), I(this, "sobelFirstOrderShader", null), I(this, "sobelSecondOrderShader", null), I(this, "genericVAO", null), I(this, "unusedVAO", null), I(this, "crosshairs3D", null), I(this, "DEFAULT_FONT_GLYPH_SHEET", kr), I(this, "DEFAULT_FONT_METRICS", Rr), I(this, "fontMetrics"), I(this, "fontMets", null), I(this, "fontPx", 12), I(this, "legendFontScaling", 1), I(this, "backgroundMasksOverlays", 0), I(this, "overlayOutlineWidth", 0), I(this, "overlayAlphaShader", 1), I(this, "position"), I(this, "extentsMin"), I(this, "extentsMax"), I(this, "resizeObserver", null), I(this, "resizeEventListener", null), I(this, "canvasObserver", null), I(this, "syncOpts", { + "3d": !1, + // legacy option + "2d": !1, + // legacy option + zoomPan: !1, + cal_min: !1, + cal_max: !1, + clipPlane: !1, + gamma: !1, + sliceType: !1, + crosshair: !1 + }), I(this, "readyForSync", !1), I(this, "uiData", { + mousedown: !1, + touchdown: !1, + mouseButtonLeftDown: !1, + mouseButtonCenterDown: !1, + mouseButtonRightDown: !1, + mouseDepthPicker: !1, + clickedTile: -1, + pan2DxyzmmAtMouseDown: [0, 0, 0, 1], + prevX: 0, + prevY: 0, + currX: 0, + currY: 0, + currentTouchTime: 0, + lastTouchTime: 0, + touchTimer: null, + doubleTouch: !1, + isDragging: !1, + dragStart: [0, 0], + dragEnd: [0, 0], + dragClipPlaneStartDepthAziElev: [0, 0, 0], + lastTwoTouchDistance: 0, + multiTouchGesture: !1, + windowX: 0, + windowY: 0, + activeDragMode: null, + activeDragButton: null, + angleFirstLine: [0, 0, 0, 0], + angleState: "none", + activeClipPlaneIndex: 0 + }), It(this, ft, null), I(this, "back", null), I(this, "overlays", []), I(this, "deferredVolumes", []), I(this, "deferredMeshes", []), I(this, "furthestVertexFromOrigin", 100), I(this, "volScale", []), I(this, "vox", []), I(this, "mousePos", [0, 0]), I(this, "screenSlices", []), I(this, "cuboidVertexBuffer"), I(this, "otherNV", null), I(this, "volumeObject3D", null), I(this, "pivot3D", [0, 0, 0]), I(this, "furthestFromPivot", 10), I(this, "currentClipPlaneIndex", 0), I(this, "lastCalled", (/* @__PURE__ */ new Date()).getTime()), I(this, "selectedObjectId", -1), I(this, "CLIP_PLANE_ID", 1), I(this, "VOLUME_ID", 254), I(this, "DISTANCE_FROM_CAMERA", -0.54), I(this, "graph", { + LTWH: [0, 0, 640, 480], + opacity: 0, + vols: [0], + // e.g. timeline for background volume only, e.g. [0,2] for first and third volumes + autoSizeMultiplanar: !1, + normalizeValues: !1, + isRangeCalMinMax: !1 + }), I(this, "customLayout", []), I(this, "meshShaders", [ + { + Name: "Phong", + Frag: qh + }, + { + Name: "Matte", + Frag: Wh + }, + { + Name: "Harmonic", + Frag: Xh + }, + { + Name: "Hemispheric", + Frag: Kh + }, + { + Name: "Crevice", + Frag: Yh + }, + { + Name: "Edge", + Frag: Oh + }, + { + Name: "Diffuse", + Frag: zh + }, + { + Name: "Outline", + Frag: Nh + }, + { + Name: "Specular", + Frag: Gh + }, + { + Name: "Toon", + Frag: Uh + }, + { + Name: "Flat", + Frag: Lr + }, + { + Name: "Matcap", + Frag: Hh + }, + { + Name: "Rim", + Frag: Ph + }, + { + Name: "Silhouette", + Frag: Lh + }, + { + Name: "Crosscut", + Frag: _h + } + ]), I(this, "dragModes", { + contrast: 1, + measurement: 2, + angle: 7, + none: 0, + pan: 3, + slicer3D: 4, + callbackOnly: 5 + /* callbackOnly */ + }), I( + this, + "sliceTypeAxial", + 0 + /* AXIAL */ + ), I( + this, + "sliceTypeCoronal", + 1 + /* CORONAL */ + ), I( + this, + "sliceTypeSagittal", + 2 + /* SAGITTAL */ + ), I( + this, + "sliceTypeMultiplanar", + 3 + /* MULTIPLANAR */ + ), I( + this, + "sliceTypeRender", + 4 + /* RENDER */ + ), I(this, "onDragRelease", () => { + }), I(this, "onMouseUp", () => { + }), I(this, "onLocationChange", () => { + }), I(this, "onIntensityChange", () => { + }), I(this, "onClickToSegment", () => { + }), I(this, "onImageLoaded", () => { + }), I(this, "onMeshLoaded", () => { + }), I(this, "onFrameChange", () => { + }), I(this, "onError", () => { + }), I(this, "onColormapChange", () => { + }), I(this, "onInfo", () => { + }), I(this, "onWarn", () => { + }), I(this, "onDebug", () => { + }), I(this, "onVolumeAddedFromUrl", () => { + }), I(this, "onVolumeWithUrlRemoved", () => { + }), I(this, "onVolumeUpdated", () => { + }), I(this, "onMeshAddedFromUrl", () => { + }), I(this, "onMeshAdded", () => { + }), I(this, "onMeshWithUrlRemoved", () => { + }), I(this, "onZoom3DChange", () => { + }), I(this, "onAzimuthElevationChange", () => { + }), I(this, "onClipPlaneChange", () => { + }), I(this, "onCustomMeshShaderAdded", () => { + }), I(this, "onMeshShaderChanged", () => { + }), I(this, "onMeshPropertyChanged", () => { + }), I(this, "onDicomLoaderFinishedWithImages", () => { + }), I(this, "onDocumentLoaded", () => { + }), I(this, "onOptsChange", () => { + }), I(this, "document", new es()), I(this, "mediaUrlMap", /* @__PURE__ */ new Map()), I(this, "initialized", !1), I(this, "currentDrawUndoBitmap"); + for (const e in i) + typeof i[e] == "function" ? this[e] = i[e] : this.opts[e] = Ge[e] === void 0 ? Ge[e] : i[e]; + this.opts.forceDevicePixelRatio === 0 ? this.uiData.dpr = window.devicePixelRatio || 1 : this.opts.forceDevicePixelRatio < 0 ? this.uiData.dpr = 1 : this.uiData.dpr = this.opts.forceDevicePixelRatio, this.currentDrawUndoBitmap = this.opts.maxDrawUndoBitmaps, this.opts.drawingEnabled && this.createEmptyDrawing(), this.opts.thumbnail.length > 0 && (this.thumbnailVisible = !0), R.setLogLevel(this.opts.logLevel), this.document.setOptsChangeCallback((e, t, s) => { + this.onOptsChange(e, t, s); + }); + } + /** Get the current scene configuration. */ + get scene() { + return this.document.scene; + } + /** Get the current visualization options. */ + get opts() { + return this.document.opts; + } + /** Get the slice mosaic layout string. */ + get sliceMosaicString() { + return this.document.opts.sliceMosaicString || ""; + } + /** Set the slice mosaic layout string. */ + set sliceMosaicString(i) { + this.document.opts.sliceMosaicString = i; + } + /** + * Get whether voxels below minimum intensity are drawn as dark or transparent. + * @returns {boolean} True if dark voxels are opaque, false if transparent. + */ + get isAlphaClipDark() { + return this.document.opts.isAlphaClipDark; + } + /** + * Set whether voxels below minimum intensity are drawn as dark or transparent. + * @param {boolean} newVal - True to make dark voxels opaque, false for transparent. + * @see {@link https://niivue.com/demos/features/segment.html | live demo usage} + */ + set isAlphaClipDark(i) { + this.document.opts.isAlphaClipDark = i; + } + /** + * Clean up event listeners and observers + * Call this when the Niivue instance is no longer needed. + * This will be called when the canvas is detached from the DOM + * @example niivue.cleanup(); + */ + cleanup() { + this.resizeEventListener && (window.removeEventListener("resize", this.resizeEventListener), this.resizeEventListener = null), this.resizeObserver && (this.resizeObserver.disconnect(), this.resizeObserver = null), this.canvasObserver && (this.canvasObserver.disconnect(), this.canvasObserver = null), K(this, ft) && (K(this, ft).abort(), st(this, ft, null)), this.document.removeOptsChangeCallback(); + } + get volumes() { + return this.document.volumes; + } + set volumes(i) { + this.document.volumes = i; + } + get meshes() { + return this.document.meshes; + } + set meshes(i) { + this.document.meshes = i; + } + get drawBitmap() { + return this.document.drawBitmap; + } + set drawBitmap(i) { + this.document.drawBitmap = i; + } + get volScaleMultiplier() { + return this.scene.volScaleMultiplier; + } + set volScaleMultiplier(i) { + this.setScale(i); + } + /** + * save webgl2 canvas as png format bitmap + * @param filename - filename for screen capture + * @example niivue.saveScene('test.png'); + * @see {@link https://niivue.com/demos/features/ui.html | live demo usage} + */ + async saveScene(i = "niivue.png") { + function e(s, r) { + const a = document.createElement("a"); + document.body.appendChild(a), a.style.display = "none"; + const n = window.URL.createObjectURL(s); + a.href = n, a.download = r, a.click(), a.remove(); + } + const t = this.canvas; + if (!t) + throw new Error("canvas not defined"); + this.drawScene(), t.toBlob((s) => { + s && (i === "" && (i = `niivue-screenshot-${(/* @__PURE__ */ new Date()).toString()}.png`, i = i.replace(/\s/g, "_")), e(s, i)); + }); + } + /** + * attach the Niivue instance to the webgl2 canvas by element id + * @param id - the id of an html canvas element + * @param isAntiAlias - determines if anti-aliasing is requested (if not specified, AA usage depends on hardware) + * @example niivue = new Niivue().attachTo('gl') + * @example await niivue.attachTo('gl') + * @see {@link https://niivue.com/demos/features/basic.multiplanar.html | live demo usage} + */ + async attachTo(i, e = null) { + return await this.attachToCanvas(document.getElementById(i), e), R.debug("attached to element with id: ", i), this; + } + /** + * attach the Niivue instance to a canvas element directly + * @param canvas - the canvas element reference + * @example + * niivue = new Niivue() + * await niivue.attachToCanvas(document.getElementById(id)) + * @see {@link https://niivue.com/demos/features/dsistudio.html | live demo usage} + */ + async attachToCanvas(i, e = null) { + return this.canvas = i, e === null && (e = navigator.hardwareConcurrency > 6, R.debug("AntiAlias ", e, " Threads ", navigator.hardwareConcurrency)), this.gl = this.canvas.getContext("webgl2", { + alpha: !0, + antialias: e + }), this.uiData.max2D = this.gl.getParameter(this.gl.MAX_TEXTURE_SIZE), this.uiData.max3D = this.gl.getParameter(this.gl.MAX_3D_TEXTURE_SIZE), R.info("NIIVUE VERSION ", Yr), R.debug(`Max texture size 2D: ${this.uiData.max2D} 3D: ${this.uiData.max3D}`), this.canvas.parentElement.style.backgroundColor = "black", this.opts.isResizeCanvas && (this.canvas.style.width = "100%", this.canvas.style.height = "100%", this.canvas.style.display = "block", this.canvas.width = this.canvas.offsetWidth, this.canvas.height = this.canvas.offsetHeight, this.resizeEventListener = () => { + requestAnimationFrame(() => { + this.resizeListener(); + }); + }, window.addEventListener("resize", this.resizeEventListener), this.resizeObserver = new ResizeObserver(() => { + requestAnimationFrame(() => { + this.resizeListener(); + }); + }), this.resizeObserver.observe(this.canvas.parentElement), this.canvasObserver = new MutationObserver((t) => { + for (const s of t) + if (s.type === "childList" && s.removedNodes.length > 0 && Array.from(s.removedNodes).includes(this.canvas)) { + this.cleanup(); + break; + } + }), this.canvasObserver.observe(this.canvas.parentElement, { childList: !0 })), this.opts.interactive && this.registerInteractions(), await this.init(), this.drawScene(), this; + } + /** + * Sync the scene controls (orientation, crosshair location, etc.) from one Niivue instance to another. useful for using one canvas to drive another. + * @param otherNV - the other Niivue instance that is the main controller + * @example + * niivue1 = new Niivue() + * niivue2 = new Niivue() + * niivue2.syncWith(niivue1) + * @deprecated use broadcastTo instead + * @see {@link https://niivue.com/demos/features/sync.mesh.html | live demo usage} + */ + syncWith(i, e = { "2d": !0, "3d": !0 }) { + i instanceof Array || (i = [i]), this.otherNV = i, this.syncOpts = { ...e }; + } + /** + * Sync the scene controls (orientation, crosshair location, etc.) from one Niivue instance to others. useful for using one canvas to drive another. + * @param otherNV - the other Niivue instance(s) + * @example + * niivue1 = new Niivue() + * niivue2 = new Niivue() + * niivue3 = new Niivue() + * niivue1.broadcastTo(niivue2) + * niivue1.broadcastTo([niivue2, niivue3]) + * @see {@link https://niivue.com/demos/features/sync.mesh.html | live demo usage} + */ + broadcastTo(i, e = { "2d": !0, "3d": !0 }) { + i instanceof Array || (i = [i]), this.otherNV = i, this.syncOpts = e; + } + /** + * Synchronizes 3D view settings (azimuth, elevation, scale) with another Niivue instance. + * @internal + */ + doSync3d(i) { + i.scene.renderAzimuth = this.scene.renderAzimuth, i.scene.renderElevation = this.scene.renderElevation, i.scene.volScaleMultiplier = this.scene.volScaleMultiplier; + } + /** + * Synchronizes 2D crosshair position and pan settings with another Niivue instance. + * @internal + */ + doSync2d(i) { + const e = this.frac2mm(this.scene.crosshairPos); + i.scene.crosshairPos = i.mm2frac(e), i.scene.pan2Dxyzmm = nt(this.scene.pan2Dxyzmm); + } + doSyncGamma(i) { + const e = this.scene.gamma, t = i.scene.gamma; + e !== t && i.setGamma(e); + } + /** + * Synchronizes gamma correction setting with another Niivue instance. + * @internal + */ + doSyncZoomPan(i) { + i.scene.pan2Dxyzmm = nt(this.scene.pan2Dxyzmm); + } + /** + * Synchronizes crosshair position with another Niivue instance. + * @internal + */ + doSyncCrosshair(i) { + const e = this.frac2mm(this.scene.crosshairPos); + i.scene.crosshairPos = i.mm2frac(e); + } + /** + * Synchronizes cal_min with another Niivue instance, updating GPU volume only if needed. + * @internal + */ + doSyncCalMin(i) { + this.volumes[0].cal_min !== i.volumes[0].cal_min && (i.volumes[0].cal_min = this.volumes[0].cal_min, i.updateGLVolume()); + } + /** + * Synchronizes cal_max with another Niivue instance, updating GPU volume only if needed. + * @internal + */ + doSyncCalMax(i) { + this.volumes[0].cal_max !== i.volumes[0].cal_max && (i.volumes[0].cal_max = this.volumes[0].cal_max, i.updateGLVolume()); + } + /** + * Synchronizes slice view type with another Niivue instance. + * @internal + */ + doSyncSliceType(i) { + i.setSliceType(this.opts.sliceType); + } + /** + * Synchronizes clip plane settings with another Niivue instance. + * @internal + */ + doSyncClipPlane(i) { + i.setClipPlane(this.scene.clipPlaneDepthAziElevs[this.uiData.activeClipPlaneIndex]); + } + /** + * Sync the scene controls (orientation, crosshair location, etc.) from one Niivue instance to another. useful for using one canvas to drive another. + * @internal + * @example + * niivue1 = new Niivue() + * niivue2 = new Niivue() + * niivue2.syncWith(niivue1) + * niivue2.sync() + */ + sync() { + if (!(!this.gl || !this.otherNV || typeof this.otherNV > "u") && this.gl.canvas.matches(":focus")) + for (let i = 0; i < this.otherNV.length; i++) + this.otherNV[i] !== this && (this.syncOpts.gamma && this.doSyncGamma(this.otherNV[i]), this.syncOpts.crosshair && this.doSyncCrosshair(this.otherNV[i]), this.syncOpts.zoomPan && this.doSyncZoomPan(this.otherNV[i]), this.syncOpts.sliceType && this.doSyncSliceType(this.otherNV[i]), this.syncOpts.cal_min && this.doSyncCalMin(this.otherNV[i]), this.syncOpts.cal_max && this.doSyncCalMax(this.otherNV[i]), this.syncOpts.clipPlane && this.doSyncClipPlane(this.otherNV[i]), this.syncOpts["2d"] && this.doSync2d(this.otherNV[i]), this.syncOpts["3d"] && this.doSync3d(this.otherNV[i]), this.otherNV[i].canvas !== this.canvas && this.otherNV[i].drawScene(), this.otherNV[i].createOnLocationChange()); + } + /** Not documented publicly for now + * @internal + * test if two arrays have equal values for each element + * @param a - the first array + * @param b - the second array + * @example Niivue.arrayEquals(a, b) + * + * TODO this should maybe just use array-equal from NPM + */ + arrayEquals(i, e) { + return Array.isArray(i) && Array.isArray(e) && i.length === e.length && i.every((t, s) => t === e[s]); + } + /** + * @internal + * Compute point size for screen text that scales with resolution and screen size. + * - Keeps physical font size consistent across different DPIs. + * - Uses fontSizeScaling to scale with canvas size above a reference threshold. + */ + textSizePoints() { + this.opts.textHeight >= 0 && (R.warn("textHeight is deprecated (use fontMinPx and fontSizeScaling)"), this.opts.fontMinPx = this.opts.textHeight * 217, this.opts.fontSizeScaling = 0.4, this.opts.textHeight = -1); + const i = this.uiData.dpr || 1, e = this.opts.fontMinPx, t = this.gl.canvas.width / i, s = this.gl.canvas.height / i, r = t * s, a = 800 * 600, n = Math.max(r / a, 1), o = Math.pow(n, this.opts.fontSizeScaling), l = e * o * i; + this.fontPx = l, R.debug( + `${t.toFixed(0)}x${s.toFixed(0)} pts (dpi=${i}) => areaScale=${n.toFixed(2)}, scale=${o.toFixed(2)}, minPx=${this.opts.fontMinPx} fontScale=${this.opts.fontSizeScaling} fontPx=${l.toFixed(2)}` + ); + } + /** + * callback function to handle resize window events, redraws the scene. + * @internal + */ + resizeListener() { + if (!(!this.canvas || !this.gl)) { + if (!this.opts.isResizeCanvas) { + this.opts.forceDevicePixelRatio >= 0 && R.warn("this.opts.forceDevicePixelRatio requires isResizeCanvas"), this.drawScene(); + return; + } + this.canvas.style.width = "100%", this.canvas.style.height = "100%", this.canvas.style.display = "block", this.opts.forceDevicePixelRatio === 0 ? this.uiData.dpr = window.devicePixelRatio || 1 : this.opts.forceDevicePixelRatio < 0 ? this.uiData.dpr = 1 : this.uiData.dpr = this.opts.forceDevicePixelRatio, R.debug("devicePixelRatio: " + this.uiData.dpr), "width" in this.canvas.parentElement ? (this.canvas.width = this.canvas.parentElement.width * this.uiData.dpr, this.canvas.height = this.canvas.parentElement.height * this.uiData.dpr) : (this.canvas.width = this.canvas.offsetWidth * this.uiData.dpr, this.canvas.height = this.canvas.offsetHeight * this.uiData.dpr), this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height), this.textSizePoints(), this.drawScene(); + } + } + /** + * callback to handle mouse move events relative to the canvas + * @internal + * @returns the mouse position relative to the canvas + */ + getRelativeMousePosition(i, e) { + if (e = e || i.target, !e) + return; + const t = e.getBoundingClientRect(); + return { + x: i.clientX - t.left, + y: i.clientY - t.top + }; + } + /** + * Returns mouse position relative to the canvas, excluding padding and borders. + * @internal + */ + getNoPaddingNoBorderCanvasRelativeMousePosition(i, e) { + return e = e || i.target, this.getRelativeMousePosition(i, e); + } + /** + * Disables the default context menu to allow custom right-click behavior. + * @internal + */ + mouseContextMenuListener(i) { + i.preventDefault(); + } + /** + * Handles mouse down events for interaction, segmentation, and connectome label selection. + * Routes to appropriate button handler based on click type. + * @internal + */ + mouseDownListener(i) { + if (this.uiData.mousedown = !0, this.eventInBounds(i)) + this.opts.bounds && (this.opts.showBoundsBorder = !0); + else { + this.opts.showBoundsBorder = !1, this.drawScene(); + return; + } + i.preventDefault(), this.drawPenLocation = [NaN, NaN, NaN], this.drawPenAxCorSag = -1, this.drawShapeStartLocation = [NaN, NaN, NaN]; + const e = this.getNoPaddingNoBorderCanvasRelativeMousePosition(i, this.gl.canvas); + if (this.opts.dragMode === 7 && this.uiData.angleState === "drawing_second_line" || (this.setDragStart(e.x, e.y), this.setDragEnd(e.x, e.y)), R.debug("mouse down"), R.debug(i), !e) + return; + const [t, s] = [e.x * this.uiData.dpr, e.y * this.uiData.dpr]; + this.opts.clickToSegment && (this.clickToSegmentXY = [t, s]); + const r = this.getLabelAtPoint([t, s]); + if (r) { + if (r.onClick) { + r.onClick(r, i); + return; + } + for (const a of this.meshes) { + if (a.type !== "connectome") { + if (Array.isArray(r.points) && r.points.length === 3 && r.points.every(Number.isFinite)) { + const [n, o, l] = r.points; + this.scene.crosshairPos = this.mm2frac([n, o, l]), this.updateGLVolume(); + } + continue; + } + for (const n of a.nodes) + n.label === r && (this.scene.crosshairPos = this.mm2frac([n.x, n.y, n.z]), this.updateGLVolume()); + } + } + this.uiData.clickedTile = this.tileIndex(t, s), i.button === Yt && i.shiftKey ? (this.uiData.mouseButtonCenterDown = !0, this.setActiveDragMode(Yt, !0, i.ctrlKey), this.handleMouseAction(this.uiData.activeDragMode, i, e)) : i.button === Yt ? (this.uiData.mouseButtonLeftDown = !0, this.setActiveDragMode(Yt, !1, i.ctrlKey), this.handleMouseAction(this.uiData.activeDragMode, i, e)) : i.button === cs ? (this.uiData.mouseButtonRightDown = !0, this.setActiveDragMode(cs, i.shiftKey, i.ctrlKey), this.handleMouseAction(this.uiData.activeDragMode, i, e)) : i.button === ls && (this.uiData.mouseButtonCenterDown = !0, this.setActiveDragMode(ls, i.shiftKey, i.ctrlKey), this.handleMouseAction(this.uiData.activeDragMode, i, e)), this.drawScene(); + } + /** + * Gets the appropriate drag mode for a mouse button based on configuration. + * @internal + */ + getMouseButtonDragMode(i, e, t) { + const s = this.opts.mouseEventConfig; + return i === Yt ? s?.leftButton ? e && s.leftButton.withShift !== void 0 ? s.leftButton.withShift : t && s.leftButton.withCtrl !== void 0 ? s.leftButton.withCtrl : s.leftButton.primary : t ? 8 : this.opts.dragModePrimary : i === cs ? s?.rightButton !== void 0 ? s.rightButton : this.opts.dragMode : i === ls ? s?.centerButton !== void 0 ? s.centerButton : this.opts.dragMode : this.opts.dragMode; + } + /** + * Gets the appropriate drag mode for touch events based on configuration. + * @internal + */ + getTouchDragMode(i) { + const e = this.opts.touchEventConfig; + return i ? e?.doubleTouch ?? this.opts.dragMode : e?.singleTouch ?? this.opts.dragModePrimary; + } + /** + * Sets the active drag mode for the current interaction. + * @internal + */ + setActiveDragMode(i, e, t) { + this.uiData.activeDragMode = this.getMouseButtonDragMode(i, e, t), this.uiData.activeDragButton = i; + } + /** + * Gets the currently active drag mode, or falls back to configured defaults. + * @internal + */ + getCurrentDragMode() { + return this.uiData.activeDragMode !== null ? this.uiData.activeDragMode : this.opts.dragMode; + } + /** + * Clears the active drag mode. + * @internal + */ + clearActiveDragMode() { + this.uiData.activeDragMode = null, this.uiData.activeDragButton = null; + } + /** + * Unified handler for mouse actions based on drag mode. + * @internal + */ + handleMouseAction(i, e, t) { + if (i === 8) + this.mouseDown(t.x, t.y), this.mouseClick(t.x, t.y); + else if (i === 9) + this.uiData.windowX = e.x, this.uiData.windowY = e.y; + else { + if (this.mousePos = [t.x * this.uiData.dpr, t.y * this.uiData.dpr], i === 0) + return; + if (i === 7) + if (this.uiData.angleState === "none") + this.uiData.angleState = "drawing_first_line"; + else if (this.uiData.angleState === "drawing_second_line") { + const s = [t.x * this.uiData.dpr, t.y * this.uiData.dpr], r = this.tileIndex(s[0], s[1]); + let a = { sliceIndex: -1, sliceType: 0, slicePosition: 0 }; + if (r >= 0 && r < this.screenSlices.length) { + const u = this.screenSlices[r].axCorSag; + let d = 0; + u === 0 ? d = this.scene.crosshairPos[2] : u === 1 ? d = this.scene.crosshairPos[1] : u === 2 && (d = this.scene.crosshairPos[0]), a = { + sliceIndex: r, + sliceType: u, + slicePosition: d + }; + } + const n = [ + this.uiData.angleFirstLine[2], + // start from end of first line + this.uiData.angleFirstLine[3], + s[0], + // to final click position + s[1] + ], o = this.canvasPos2frac([this.uiData.angleFirstLine[0], this.uiData.angleFirstLine[1]]), l = this.canvasPos2frac([this.uiData.angleFirstLine[2], this.uiData.angleFirstLine[3]]), c = this.canvasPos2frac([n[0], n[1]]), h = this.canvasPos2frac([n[2], n[3]]); + if (o[0] >= 0 && l[0] >= 0 && c[0] >= 0 && h[0] >= 0) { + const u = this.frac2mm(o), d = this.frac2mm(l), f = this.frac2mm(c), g = this.frac2mm(h), m = { + firstLineMM: { + start: G(u[0], u[1], u[2]), + end: G(d[0], d[1], d[2]) + }, + secondLineMM: { + start: G(f[0], f[1], f[2]), + end: G(g[0], g[1], g[2]) + }, + sliceIndex: a.sliceIndex, + sliceType: a.sliceType, + slicePosition: a.slicePosition, + angle: this.calculateAngleBetweenLines(this.uiData.angleFirstLine, n) + }; + this.document.completedAngles.push(m); + } + this.resetAngleMeasurement(), this.uiData.angleState = "complete", this.drawScene(); + return; + } else this.uiData.angleState === "complete" && (this.resetAngleMeasurement(), this.uiData.angleState = "drawing_first_line"); + this.setDragStart(t.x, t.y), this.uiData.isDragging || (this.uiData.pan2DxyzmmAtMouseDown = nt(this.scene.pan2Dxyzmm)), this.uiData.isDragging = !0, this.uiData.dragClipPlaneStartDepthAziElev = this.scene.clipPlaneDepthAziElevs[this.uiData.activeClipPlaneIndex]; + } + } + /** + * calculate the the min and max voxel indices from an array of two values (used in selecting intensities with the selection box) + * @internal + * @param array - an array of two values + * @returns an array of two values representing the min and max voxel indices + */ + calculateMinMaxVoxIdx(i) { + if (i.length > 2) + throw new Error("array must not contain more than two values"); + return [Math.floor(Math.min(i[0], i[1])), Math.floor(Math.max(i[0], i[1]))]; + } + /** + * Updates cal_min and cal_max based on intensity range within the drag-selected voxel region. + * Skips if no drag occurred, volume is missing, or selection has no variation. + * @internal + */ + calculateNewRange({ volIdx: i = 0 } = {}) { + if (this.opts.sliceType === 4 && this.sliceMosaicString.length < 1 || this.uiData.dragStart[0] === this.uiData.dragEnd[0] && this.uiData.dragStart[1] === this.uiData.dragEnd[1]) + return; + let e = this.canvasPos2frac([this.uiData.dragStart[0], this.uiData.dragStart[1]]); + if (e[0] < 0) + return; + const t = this.frac2vox(e, i); + if (e = this.canvasPos2frac([this.uiData.dragEnd[0], this.uiData.dragEnd[1]]), e[0] < 0) + return; + const s = this.frac2vox(e, i); + let r = -Number.MAX_VALUE, a = Number.MAX_VALUE; + const n = this.calculateMinMaxVoxIdx([t[0], s[0]]), o = this.calculateMinMaxVoxIdx([t[1], s[1]]), l = this.calculateMinMaxVoxIdx([t[2], s[2]]); + t[0] - s[0] === 0 ? n[1] = t[0] + 1 : t[1] - s[1] === 0 ? o[1] = t[1] + 1 : t[2] - s[2] === 0 && (l[1] = t[2] + 1); + const c = this.volumes[i].hdr, h = this.volumes[i].img; + if (!c || !h) + return; + const u = c.dims[1], d = c.dims[2]; + for (let m = l[0]; m < l[1]; m++) { + const p = m * u * d; + for (let v = o[0]; v < o[1]; v++) { + const A = v * u; + for (let x = n[0]; x < n[1]; x++) { + const w = p + A + x; + a > h[w] && (a = h[w]), r < h[w] && (r = h[w]); + } + } + } + if (a >= r) + return; + const f = zr(c, a), g = zr(c, r); + this.volumes[i].cal_min = f, this.volumes[i].cal_max = g, this.onIntensityChange(this.volumes[i]); + } + /** + * Triggers a drag-release callback with voxel, mm, and tile info from the drag gesture. + * @internal + */ + generateMouseUpCallback(i, e) { + const t = this.tileIndex(this.uiData.dragStart[0], this.uiData.dragStart[1]), s = this.tileIndex(this.uiData.dragEnd[0], this.uiData.dragEnd[1]); + let r = -1; + t === s && (r = s); + let a = -1; + r >= 0 && (a = this.screenSlices[r].axCorSag); + const n = this.frac2mm(i), o = this.frac2mm(e), l = le(); + or(l, G(n[0], n[1], n[2]), G(o[0], o[1], o[2])); + const c = ei(l), h = this.frac2vox(i), u = this.frac2vox(e); + this.onDragRelease({ + fracStart: i, + fracEnd: e, + voxStart: h, + voxEnd: u, + mmStart: n, + mmEnd: o, + mmLength: c, + tileIdx: r, + axCorSag: a + }); + } + /** + * Handles mouse up events, finalizing drag actions, invoking callbacks, and updating contrast if needed. + * @internal + */ + mouseUpListener() { + this.uiData.mousedown = !1; + function i(r) { + return Object.prototype.toString.call(r).indexOf("Function") > -1; + } + const e = { + mouseButtonRightDown: this.uiData.mouseButtonRightDown, + mouseButtonCenterDown: this.uiData.mouseButtonCenterDown, + isDragging: this.uiData.isDragging, + mousePos: this.mousePos, + fracPos: this.canvasPos2frac(this.mousePos) + // xyzMM: this.frac2mm(fracPos), + }; + this.uiData.mouseButtonRightDown = !1; + const t = this.uiData.mouseButtonCenterDown; + this.uiData.mouseButtonCenterDown = !1, this.uiData.mouseButtonLeftDown = !1; + const s = this.getCurrentDragMode(); + if (this.drawPenFillPts.length > 0 ? this.drawPenFilled() : this.opts.drawingEnabled && !isNaN(this.drawPenLocation[0]) ? this.drawAddUndoBitmap() : this.opts.drawingEnabled && !isNaN(this.drawShapeStartLocation[0]) && (this.opts.penType === 1 || this.opts.penType === 2) && (this.opts.penValue === 0 ? this.drawAddUndoBitmap() : this.drawAddUndoBitmap(this.drawFillOverwrites), this.drawShapePreviewBitmap = null), this.drawPenLocation = [NaN, NaN, NaN], this.drawPenAxCorSag = -1, this.drawShapeStartLocation = [NaN, NaN, NaN], this.drawShapePreviewBitmap && (this.drawBitmap = this.drawShapePreviewBitmap, this.drawShapePreviewBitmap = null, this.refreshDrawing(!0, !1)), i(this.onMouseUp) && this.onMouseUp(e), this.uiData.isDragging) { + if (this.uiData.isDragging = !1, s === 7) { + if (this.uiData.angleState === "drawing_first_line") { + this.uiData.angleFirstLine = [ + this.uiData.dragStart[0], + this.uiData.dragStart[1], + this.uiData.dragEnd[0], + this.uiData.dragEnd[1] + ], this.uiData.angleState = "drawing_second_line", this.uiData.isDragging = !0, this.drawScene(); + return; + } + if (this.uiData.angleState === "drawing_second_line") { + this.uiData.angleState = "complete", this.clearActiveDragMode(), this.drawScene(); + return; + } + } + s === 5 && this.drawScene(); + const r = this.canvasPos2frac([this.uiData.dragStart[0], this.uiData.dragStart[1]]), a = this.canvasPos2frac([this.uiData.dragEnd[0], this.uiData.dragEnd[1]]); + if (this.generateMouseUpCallback(r, a), s === 6) { + this.clearActiveDragMode(); + return; + } + if (s === 1) { + if (t) { + this.clearActiveDragMode(); + return; + } + if (this.uiData.dragStart[0] === this.uiData.dragEnd[0] && this.uiData.dragStart[1] === this.uiData.dragEnd[1]) { + this.clearActiveDragMode(); + return; + } + this.calculateNewRange({ volIdx: 0 }), this.refreshLayers(this.volumes[0], 0); + } + if (s === 2) { + const n = this.getCurrentSliceInfo(), o = this.canvasPos2frac([this.uiData.dragStart[0], this.uiData.dragStart[1]]), l = this.canvasPos2frac([this.uiData.dragEnd[0], this.uiData.dragEnd[1]]); + if (o[0] >= 0 && l[0] >= 0) { + const c = this.frac2mm(o), h = this.frac2mm(l); + this.document.completedMeasurements.push({ + startMM: G(c[0], c[1], c[2]), + endMM: G(h[0], h[1], h[2]), + sliceIndex: n.sliceIndex, + sliceType: n.sliceType, + slicePosition: n.slicePosition, + distance: Ua( + G(c[0], c[1], c[2]), + G(h[0], h[1], h[2]) + ) + }); + } + this.clearActiveDragMode(), this.drawScene(); + return; + } + } + this.clearActiveDragMode(), this.drawScene(); + } + /** + * Handles initial touch event to simulate mouse click if not in a multi-touch gesture. + * @internal + */ + checkMultitouch(i) { + if (this.uiData.touchdown && !this.uiData.multiTouchGesture) { + const e = this.canvas.getBoundingClientRect(); + this.mouseDown(i.touches[0].clientX - e.left, i.touches[0].clientY - e.top), this.mouseClick(i.touches[0].clientX - e.left, i.touches[0].clientY - e.top); + } + } + /** + * Handles touch start events, detecting double taps and preparing for gesture or contrast reset. + * @internal + */ + touchStartListener(i) { + i.preventDefault(), this.uiData.touchTimer || (this.uiData.touchTimer = setTimeout(() => { + this.resetBriCon(i); + }, this.opts.longTouchTimeout)), this.uiData.touchdown = !0, this.uiData.currentTouchTime = (/* @__PURE__ */ new Date()).getTime(); + const e = this.uiData.currentTouchTime - this.uiData.lastTouchTime; + if (e < this.opts.doubleTouchTimeout && e > 0) { + this.uiData.doubleTouch = !0, this.setDragStart( + i.targetTouches[0].clientX - i.target.getBoundingClientRect().left, + i.targetTouches[0].clientY - i.target.getBoundingClientRect().top + ), this.resetBriCon(i), this.uiData.lastTouchTime = this.uiData.currentTouchTime; + return; + } else + this.uiData.doubleTouch = !1, this.setDragStart(0, 0), this.setDragEnd(0, 0), this.uiData.lastTouchTime = this.uiData.currentTouchTime; + this.uiData.touchdown && i.touches.length < 2 ? this.uiData.multiTouchGesture = !1 : this.uiData.multiTouchGesture = !0, setTimeout(this.checkMultitouch.bind(this), 1, i); + } + /** + * Handles touch end events, finalizing gestures and contrast adjustments, then triggers mouse up logic. + * @internal + */ + touchEndListener(i) { + if (i.preventDefault(), this.uiData.touchdown = !1, this.uiData.lastTwoTouchDistance = 0, this.uiData.multiTouchGesture = !1, this.uiData.touchTimer && (clearTimeout(this.uiData.touchTimer), this.uiData.touchTimer = null), this.uiData.isDragging) { + this.uiData.isDragging = !1, this.getCurrentDragMode() === 1 && (this.calculateNewRange(), this.refreshLayers(this.volumes[0], 0)); + const e = this.canvasPos2frac([this.uiData.dragStart[0], this.uiData.dragStart[1]]), t = this.canvasPos2frac([this.uiData.dragEnd[0], this.uiData.dragEnd[1]]); + this.generateMouseUpCallback(e, t); + } + this.mouseUpListener(); + } + /** + * Adjusts window/level (cal_min and cal_max) based on mouse or touch drag direction. + * @internal + */ + windowingHandler(i, e, t = 0) { + const s = this.uiData.windowX, r = this.uiData.windowY; + let a = this.volumes[0].cal_min, n = this.volumes[0].cal_max; + const o = this.volumes[0].global_min, l = this.volumes[0].global_max; + e < r ? (a += 1, n += 1) : e > r && (a -= 1, n -= 1), i > s ? (a -= 1, n += 1) : i < s && (a += 1, n -= 1), n - a < 1 && (n = a + 1), a < o && (a = o), n > l && (n = l), a > n && (a = n - 1), this.volumes[t].cal_min = a, this.volumes[t].cal_max = n, this.refreshLayers(this.volumes[t], 0), this.uiData.windowX = i, this.uiData.windowY = e; + } + /** + * Handles mouse leaving the canvas, resetting segmentation, drawing, and drag states. + * @internal + */ + mouseLeaveListener() { + this.clickToSegmentIsGrowing && (R.debug("Mouse left canvas, stopping clickToSegment preview."), this.clickToSegmentIsGrowing = !1, this.refreshDrawing(!0, !1)), this.opts.drawingEnabled && !isNaN(this.drawPenLocation[0]) && (R.debug("Mouse left canvas during drawing, resetting pen state."), this.drawPenLocation = [NaN, NaN, NaN], this.drawPenAxCorSag = -1, this.drawPenFillPts = []), this.opts.drawingEnabled && !isNaN(this.drawShapeStartLocation[0]) && (R.debug("Mouse left canvas during shape drawing, resetting shape state."), this.drawShapeStartLocation = [NaN, NaN, NaN], this.drawShapePreviewBitmap && (this.drawBitmap = this.drawShapePreviewBitmap, this.drawShapePreviewBitmap = null, this.refreshDrawing(!0, !1))), (this.uiData.isDragging || this.uiData.mousedown) && (R.debug("Mouse left canvas during drag, resetting drag state."), this.uiData.isDragging = !1, this.uiData.mouseButtonLeftDown = !1, this.uiData.mouseButtonCenterDown = !1, this.uiData.mouseButtonRightDown = !1, this.uiData.mousedown = !1, this.drawScene()), this.mousePos = [-1, -1]; + } + /** + * Handles mouse move events for dragging, crosshair movement, windowing, and click-to-segment preview. + * @internal + */ + mouseMoveListener(i) { + this.uiData.mousedown && this.drawScene(); + const e = this.getNoPaddingNoBorderCanvasRelativeMousePosition(i, this.gl.canvas); + if (e) { + if (!this.eventInBounds(i)) { + this.updateMousePos(e.x, e.y); + return; + } + if (this.uiData.mousedown) { + const t = e.x * this.uiData.dpr, s = e.y * this.uiData.dpr; + if (this.tileIndex(t, s) !== this.uiData.clickedTile) + return; + const a = this.getCurrentDragMode(); + if (a === 8) { + this.mouseMove(e.x, e.y), this.mouseClick(e.x, e.y), this.drawScene(), this.uiData.prevX = this.uiData.currX, this.uiData.prevY = this.uiData.currY; + return; + } + if (a === 9) { + this.windowingHandler(e.x, e.y), this.drawScene(), this.uiData.prevX = this.uiData.currX, this.uiData.prevY = this.uiData.currY; + return; + } + this.setDragEnd(e.x, e.y), this.drawScene(), this.uiData.prevX = this.uiData.currX, this.uiData.prevY = this.uiData.currY; + } else if (this.getCurrentDragMode() === 7 && this.uiData.angleState === "drawing_second_line") { + const t = this.getNoPaddingNoBorderCanvasRelativeMousePosition(i, this.gl.canvas); + if (!t) + return; + this.setDragEnd(t.x, t.y), this.drawScene(); + } else if (!this.uiData.mousedown && this.opts.clickToSegment) { + const t = this.getNoPaddingNoBorderCanvasRelativeMousePosition(i, this.gl.canvas); + if (!t) + return; + const s = t.x * this.uiData.dpr, r = t.y * this.uiData.dpr; + this.mousePos = [s, r]; + const a = this.tileIndex(s, r); + a >= 0 && this.opts.drawingEnabled && this.screenSlices[a].axCorSag <= 2 && (this.clickToSegmentXY = [s, r], this.clickToSegmentIsGrowing = !0, this.doClickToSegment({ + x: s, + // Screen X + y: r, + // Screen Y + tileIndex: a + })); + } + } + } + /** + * Resets brightness and contrast to robust min/max unless in render mode or during interaction. + * @internal + */ + resetBriCon(i = null) { + if (this.uiData.isDragging) + return; + if (!this.eventInBounds(i)) { + this.opts.showBoundsBorder = !1; + return; + } + let e = !1; + this.opts.sliceType === 4 && (e = !0); + let t = 0, s = 0; + if (i !== null && ("targetTouches" in i ? (t = i.targetTouches[0].clientX - i.target.getBoundingClientRect().left, s = i.targetTouches[0].clientY - i.target.getBoundingClientRect().top) : (t = i.offsetX, s = i.offsetY), t *= this.uiData.dpr, s *= this.uiData.dpr, this.inRenderTile(t, s) >= 0 && (e = !0)), e) { + this.uiData.mouseDepthPicker = !0, this.drawScene(), this.drawScene(); + return; + } + this.getCurrentDragMode() !== 4 && (this.volumes.length < 1 || this.uiData.doubleTouch || (this.volumes[0].cal_min = this.volumes[0].robust_min, this.volumes[0].cal_max = this.volumes[0].robust_max, this.onIntensityChange(this.volumes[0]), this.refreshLayers(this.volumes[0], 0), this.drawScene())); + } + /** + * Sets the drag start position in canvas coordinates. + * @internal + */ + setDragStart(i, e) { + i *= this.uiData.dpr, e *= this.uiData.dpr, this.uiData.dragStart[0] = i, this.uiData.dragStart[1] = e; + } + /** + * Sets the drag end position in canvas coordinates. + * @internal + */ + setDragEnd(i, e) { + i *= this.uiData.dpr, e *= this.uiData.dpr, this.uiData.dragEnd[0] = i, this.uiData.dragEnd[1] = e; + } + /** + * Handles touch movement for crosshair, windowing, and pinch-to-zoom interactions. + * @internal + */ + touchMoveListener(i) { + if (this.uiData.touchdown && i.touches.length < 2) { + const e = this.canvas.getBoundingClientRect(); + if (this.uiData.isDragging || (this.uiData.pan2DxyzmmAtMouseDown = nt(this.scene.pan2Dxyzmm)), this.uiData.isDragging = !0, this.uiData.doubleTouch && this.uiData.isDragging) { + this.setDragEnd( + i.targetTouches[0].clientX - i.target.getBoundingClientRect().left, + i.targetTouches[0].clientY - i.target.getBoundingClientRect().top + ), this.drawScene(); + return; + } + const t = this.getTouchDragMode(!1); + t === 8 ? (this.mouseClick(i.touches[0].clientX - e.left, i.touches[0].clientY - e.top), this.mouseMove(i.touches[0].clientX - e.left, i.touches[0].clientY - e.top)) : t === 9 && (this.windowingHandler(i.touches[0].pageX, i.touches[0].pageY), this.drawScene()); + } else + this.handlePinchZoom(i); + } + /** + * Handles pinch-to-zoom gestures for scrolling 2D slices. + * @internal + */ + handlePinchZoom(i) { + if (i.targetTouches.length === 2 && i.changedTouches.length === 2) { + const e = Math.hypot(i.touches[0].pageX - i.touches[1].pageX, i.touches[0].pageY - i.touches[1].pageY), t = this.canvas.getBoundingClientRect(); + this.mousePos = [i.touches[0].clientX - t.left, i.touches[0].clientY - t.top], e < this.uiData.lastTwoTouchDistance ? this.sliceScroll2D(-0.01, i.touches[0].clientX - t.left, i.touches[0].clientY - t.top) : this.sliceScroll2D(0.01, i.touches[0].clientX - t.left, i.touches[0].clientY - t.top), this.uiData.lastTwoTouchDistance = e; + } + } + /** + * Cycles active clip plane + * @internal + * @returns active clip plane index + */ + cycleActiveClipPlane() { + const i = this.scene.clipPlanes.length || 6; + this.uiData.activeClipPlaneIndex == null ? this.uiData.activeClipPlaneIndex = 0 : this.uiData.activeClipPlaneIndex = (this.uiData.activeClipPlaneIndex + 1) % i; + const e = this.uiData.activeClipPlaneIndex; + return this.scene.clipPlanes[e] || (this.scene.clipPlanes[e] = [0, 0, 0, 2]), this.scene.clipPlaneDepthAziElevs[e] || (this.scene.clipPlaneDepthAziElevs[e] = [2, 0, 0]), e; + } + /** + * Handles keyboard shortcuts for toggling clip planes and slice view modes with debounce logic. + * @internal + */ + keyUpListener(i) { + if (!this.cursorInBounds()) { + this.opts.showBoundsBorder = !1, this.drawScene(); + return; + } + const e = (/* @__PURE__ */ new Date()).getTime(), t = e - this.lastCalled; + if (i.code === this.opts.cycleClipPlaneHotKey && t > this.opts.keyDebounceTime) { + const s = this.cycleActiveClipPlane(); + console.log("Active clip plane cycled to:", s), console.log("clip planes", this.scene.clipPlanes), this.lastCalled = e; + } + if (i.code === this.opts.clipPlaneHotKey) { + if (t > this.opts.keyDebounceTime) { + switch (this.currentClipPlaneIndex = (this.currentClipPlaneIndex + 1) % 7, this.currentClipPlaneIndex) { + case 0: + this.scene.clipPlaneDepthAziElevs[this.uiData.activeClipPlaneIndex] = [2, 0, 0]; + break; + case 1: + this.scene.clipPlaneDepthAziElevs[this.uiData.activeClipPlaneIndex] = [0, 270, 0]; + break; + case 2: + this.scene.clipPlaneDepthAziElevs[this.uiData.activeClipPlaneIndex] = [0, 90, 0]; + break; + case 3: + this.scene.clipPlaneDepthAziElevs[this.uiData.activeClipPlaneIndex] = [0, 0, 0]; + break; + case 4: + this.scene.clipPlaneDepthAziElevs[this.uiData.activeClipPlaneIndex] = [0, 180, 0]; + break; + case 5: + this.scene.clipPlaneDepthAziElevs[this.uiData.activeClipPlaneIndex] = [0, 0, -90]; + break; + case 6: + this.scene.clipPlaneDepthAziElevs[this.uiData.activeClipPlaneIndex] = [0, 0, 90]; + break; + } + this.setClipPlane(this.scene.clipPlaneDepthAziElevs[this.uiData.activeClipPlaneIndex]); + } + this.lastCalled = e; + } else if (i.code === this.opts.viewModeHotKey) { + const s = (/* @__PURE__ */ new Date()).getTime(); + s - this.lastCalled > this.opts.keyDebounceTime && (this.setSliceType((this.opts.sliceType + 1) % 5), this.lastCalled = s); + } + this.drawScene(); + } + /** + * Handles key down events for navigation, rendering controls, slice movement, and mode switching. + * @internal + */ + keyDownListener(i) { + if (!this.cursorInBounds()) { + this.opts.showBoundsBorder = !1, this.drawScene(); + return; + } + i.code === "KeyH" && this.opts.sliceType === 4 ? this.setRenderAzimuthElevation(this.scene.renderAzimuth - 1, this.scene.renderElevation) : i.code === "KeyL" && this.opts.sliceType === 4 ? this.setRenderAzimuthElevation(this.scene.renderAzimuth + 1, this.scene.renderElevation) : i.code === "KeyJ" && this.opts.sliceType === 4 ? this.setRenderAzimuthElevation(this.scene.renderAzimuth, this.scene.renderElevation + 1) : i.code === "KeyK" && this.opts.sliceType === 4 ? this.setRenderAzimuthElevation(this.scene.renderAzimuth, this.scene.renderElevation - 1) : i.code === "KeyH" && this.opts.sliceType !== 4 ? this.moveCrosshairInVox(-1, 0, 0) : i.code === "KeyL" && this.opts.sliceType !== 4 ? this.moveCrosshairInVox(1, 0, 0) : i.code === "KeyU" && this.opts.sliceType !== 4 && i.ctrlKey ? this.moveCrosshairInVox(0, 0, 1) : i.code === "KeyD" && this.opts.sliceType !== 4 && i.ctrlKey ? this.moveCrosshairInVox(0, 0, -1) : i.code === "KeyJ" && this.opts.sliceType !== 4 ? this.moveCrosshairInVox(0, -1, 0) : i.code === "KeyK" && this.opts.sliceType !== 4 ? this.moveCrosshairInVox(0, 1, 0) : i.code === "KeyM" && this.opts.sliceType !== 4 ? (this.opts.dragMode++, this.opts.dragMode >= 4 && (this.opts.dragMode = 0), R.info("drag mode changed to ", Jn[this.opts.dragMode])) : i.code === "ArrowLeft" ? this.setFrame4D(this.volumes[0].id, this.volumes[0].frame4D - 1) : i.code === "ArrowRight" ? this.setFrame4D(this.volumes[0].id, this.volumes[0].frame4D + 1) : i.code === "Slash" && i.shiftKey && alert(`NIIVUE VERSION: ${Yr}`), this.drawScene(); + } + /** + * Handles scroll wheel events for slice scrolling, ROI box resizing, zooming, or segmentation thresholding. + * @internal + */ + wheelListener(i) { + if (this.thumbnailVisible || this.opts.sliceMosaicString.length > 0) + return; + if (this.eventInBounds(i)) + this.opts.bounds && (this.opts.showBoundsBorder = !0); + else { + this.opts.showBoundsBorder = !1, this.drawScene(); + return; + } + i.preventDefault(), i.stopPropagation(); + const e = this.uiData.dragStart.reduce((l, c) => l + c, 0), t = this.uiData.dragEnd.reduce((l, c) => l + c, 0), s = e > 0 && t > 0; + if (this.getCurrentDragMode() === 6 && s) { + const l = i.deltaY > 0 ? 1 : -1; + this.uiData.dragStart[0] < this.uiData.dragEnd[0] ? (this.uiData.dragStart[0] -= l, this.uiData.dragEnd[0] += l) : (this.uiData.dragStart[0] += l, this.uiData.dragEnd[0] -= l), this.uiData.dragStart[1] < this.uiData.dragEnd[1] ? (this.uiData.dragStart[1] -= l, this.uiData.dragEnd[1] += l) : (this.uiData.dragStart[1] += l, this.uiData.dragEnd[1] -= l), this.uiData.isDragging = !0, this.drawScene(), this.uiData.isDragging = !1; + const c = this.tileIndex(this.uiData.dragStart[0], this.uiData.dragStart[1]); + c >= 0 ? this.generateMouseUpCallback( + this.screenXY2TextureFrac(this.uiData.dragStart[0], this.uiData.dragStart[1], c), + this.screenXY2TextureFrac(this.uiData.dragEnd[0], this.uiData.dragEnd[1], c) + ) : R.warn("Could not generate drag release callback for ROI selection: Invalid tile index."); + return; + } + let r = i.deltaY < 0 ? -0.01 : 0.01; + if (this.opts.invertScrollDirection && (r = -r), this.opts.clickToSegment) { + r < 0 ? (this.opts.clickToSegmentPercent -= 0.01, this.opts.clickToSegmentPercent = Math.max(this.opts.clickToSegmentPercent, 0)) : (this.opts.clickToSegmentPercent += 0.01, this.opts.clickToSegmentPercent = Math.min(this.opts.clickToSegmentPercent, 1)); + const l = this.clickToSegmentXY[0], c = this.clickToSegmentXY[1], h = this.tileIndex(l, c); + h >= 0 && this.screenSlices[h].axCorSag <= 2 && (R.debug(`Adjusting clickToSegment threshold: ${this.opts.clickToSegmentPercent.toFixed(3)}`), this.clickToSegmentIsGrowing = !0, this.doClickToSegment({ x: l, y: c, tileIndex: h })); + return; + } + const a = this.canvas.getBoundingClientRect(), n = i.clientX - a.left, o = i.clientY - a.top; + if (this.getCurrentDragMode() === 3 && this.inRenderTile(this.uiData.dpr * n, this.uiData.dpr * o) === -1) { + const l = r < 0 ? 1 : -1; + let c = this.scene.pan2Dxyzmm[3] * (1 + 10 * (0.01 * l)); + c = Math.round(c * 10) / 10; + const h = this.scene.pan2Dxyzmm[3] - c; + this.opts.yoke3Dto2DZoom && (this.scene.volScaleMultiplier = c), this.scene.pan2Dxyzmm[3] = c; + const u = this.frac2mm(this.scene.crosshairPos); + this.scene.pan2Dxyzmm[0] += h * u[0], this.scene.pan2Dxyzmm[1] += h * u[1], this.scene.pan2Dxyzmm[2] += h * u[2], this.drawScene(), this.canvas.focus(), this.sync(); + return; + } + this.sliceScroll2D(r, n, o); + } + /** + * Registers all mouse, touch, keyboard, and drag event listeners for canvas interaction. + * n.b. any event listeners registered here should also be removed in `cleanup()` + * @internal + */ + registerInteractions() { + if (!this.canvas) + throw new Error("canvas undefined"); + st(this, ft, new AbortController()); + const { signal: i } = K(this, ft); + this.canvas.addEventListener("mousedown", this.mouseDownListener.bind(this), { signal: i }), this.canvas.addEventListener("mouseup", this.mouseUpListener.bind(this), { signal: i }), this.canvas.addEventListener("mousemove", this.mouseMoveListener.bind(this), { signal: i }), this.canvas.addEventListener("mouseleave", this.mouseLeaveListener.bind(this), { signal: i }), this.canvas.addEventListener("touchstart", this.touchStartListener.bind(this), { signal: i }), this.canvas.addEventListener("touchend", this.touchEndListener.bind(this), { signal: i }), this.canvas.addEventListener("touchmove", this.touchMoveListener.bind(this), { signal: i }), this.canvas.addEventListener("wheel", this.wheelListener.bind(this), { signal: i }), this.canvas.addEventListener("contextmenu", this.mouseContextMenuListener.bind(this), { signal: i }), this.canvas.addEventListener("dblclick", this.resetBriCon.bind(this), { signal: i }), this.canvas.addEventListener("dragenter", this.dragEnterListener.bind(this), { signal: i }), this.canvas.addEventListener("dragover", this.dragOverListener.bind(this), { signal: i }), this.canvas.addEventListener( + "drop", + (e) => { + this.dropListener(e).catch(console.error); + }, + { signal: i } + ), this.canvas.setAttribute("tabindex", "0"), this.canvas.addEventListener("keyup", this.keyUpListener.bind(this), { signal: i }), this.canvas.addEventListener("keydown", this.keyDownListener.bind(this), { signal: i }); + } + /** + * Prevents default behavior when a dragged item enters the canvas. + * @internal + */ + dragEnterListener(i) { + i.stopPropagation(), i.preventDefault(); + } + /** + * Prevents default behavior when a dragged item is over the canvas. + * @internal + */ + dragOverListener(i) { + i.stopPropagation(), i.preventDefault(); + } + /** + * Extracts and normalizes the file extension, handling special cases like .gz and .cbor. + * @internal + */ + getFileExt(i, e = !0) { + R.debug("fullname: ", i); + const t = /(?:\.([^.]+))?$/; + let s = t.exec(i)[1]; + if (s = s.toUpperCase(), s === "GZ") + s = t.exec(i.slice(0, -3))[1], s = s.toUpperCase(); + else if (s === "CBOR") { + const r = s; + s = t.exec(i.slice(0, -5))[1], s = s.toUpperCase(), s = `${s}.${r}`; + } + return e ? s : s.toLowerCase(); + } + /** + * Add an image and notify subscribers + * @see {@link https://niivue.com/demos/features/document.3d.html | live demo usage} + */ + async addVolumeFromUrl(i) { + const e = await Se.loadFromUrl(i); + return this.document.addImageOptions(e, i), e.onColormapChange = this.onColormapChange, this.mediaUrlMap.set(e, i.url), this.onVolumeAddedFromUrl && this.onVolumeAddedFromUrl(i, e), this.addVolume(e), e; + } + async addVolumesFromUrl(i) { + const e = i.map(async (s) => { + const r = this.getFileExt(s.name || s.url); + if (r === "DCM") + throw new Error("DICOM files must be loaded using useDicomLoader"); + if (this.loaders[r]) { + let o = s.url; + const l = this.loaders[r].toExt; + let c = s.name || s.url; + if (c = c.split("/").pop(), typeof s.url == "string") { + const u = s.url; + try { + const d = await fetch(u); + if (!d.ok) + throw new Error(`Failed to load file: ${d.statusText}`); + o = await d.arrayBuffer(); + } catch (d) { + throw new Error(`Failed to load url ${u}: ${d}`); + } + } + const h = await this.loaders[r].loader(o); + s.url = h, s.name = `${c}.${l}`; + } + const a = { + url: s.url, + headers: s.headers, + name: s.name, + colormap: s.colormap ? s.colormap : s.colorMap, + colormapNegative: s.colormapNegative ? s.colormapNegative : s.colorMapNegative, + opacity: s.opacity, + urlImgData: s.urlImgData, + cal_min: s.cal_min, + cal_max: s.cal_max, + trustCalMinMax: this.opts.trustCalMinMax, + isManifest: s.isManifest, + frame4D: s.frame4D, + limitFrames4D: s.limitFrames4D || this.opts.limitFrames4D, + colorbarVisible: s.colorbarVisible + }, n = await Se.loadFromUrl(a); + return this.document.addImageOptions(n, a), n.onColormapChange = this.onColormapChange, this.mediaUrlMap.set(n, a.url), this.onVolumeAddedFromUrl && this.onVolumeAddedFromUrl(a, n), n; + }), t = await Promise.all(e); + for (let s = 0; s < t.length; s++) + this.addVolume(t[s]); + return t; + } + /** + * Returns the media object associated with the given URL, if any. + * @internal + */ + getMediaByUrl(i) { + return [...this.mediaUrlMap.entries()].filter((e) => e[1] === i).map((e) => e[0]).pop(); + } + /** + * Remove volume by url + * @param url - Volume added by url to remove + * @see {@link https://niivue.com/demos/features/document.3d.html | live demo usage} + */ + removeVolumeByUrl(i) { + const e = this.getMediaByUrl(i); + if (e) + this.removeVolume(e); + else + throw new Error("No volume with URL present"); + } + /** + * Recursively traverses a file tree, populating file paths for directory uploads. + * Adds `_webkitRelativePath` to each file for compatibility with tools like dcm2niix. + * @internal + */ + async traverseFileTree(i, e = "", t) { + return new Promise((s) => { + if (i.isFile) + i.file((r) => { + r.fullPath = e + r.name, r._webkitRelativePath = e + r.name, t.push(r), s(t); + }); + else if (i.isDirectory) { + const r = i.createReader(), a = () => { + r.readEntries((n) => { + if (n.length > 0) { + const o = []; + for (const l of n) + o.push(this.traverseFileTree(l, e + i.name + "/", t)); + Promise.all(o).then(a).catch((l) => { + throw l; + }); + } else + s(t); + }); + }; + a(); + } + }); + } + /** + * Recursively reads a directory and logs the File objects contained within. + * Used for processing dropped folders via drag-and-drop. + * @internal + */ + readDirectory(i) { + const e = i.createReader(); + let t = []; + const s = async (a) => { + const n = [], o = async (l) => new Promise((c, h) => l.file(c, h)); + for (let l = 0; l < a.length; l++) + n.push(await o(a[l])); + return n; + }, r = () => { + e.readEntries((a) => { + a.length ? (t = t.concat(a), r()) : s(t).then(async () => { + }).catch((n) => { + throw n; + }); + }); + }; + return r(), t; + } + /** + * Returns boolean: true if filename ends with mesh extension (TRK, pial, etc) + * @param url - filename + * @internal + */ + isMeshExt(i) { + const e = this.getFileExt(i); + return R.debug("dropped ext"), R.debug(e), ut.includes(e); + } + /** + * Load an image or mesh from an array buffer + * @param buffer - ArrayBuffer with the entire contents of a mesh or volume + * @param name - string of filename, extension used to infer type (NIfTI, MGH, MZ3, etc) + * @see {@link http://192.168.0.150:8080/features/draganddrop.html | live demo usage} + */ + async loadFromArrayBuffer(i, e) { + const t = this.getFileExt(e); + if (ut.includes(t)) { + await this.addMeshFromUrl({ url: e, buffer: i }); + return; + } + const s = Si(e); + s.buffer = i, s.name = e, await this.addVolumeFromUrl(s); + } + /** + * Load a mesh or image volume from a File object + * @param file - File object selected by the user (e.g. from an HTML input element) + * @returns a Promise that resolves when the file has been loaded and added to the scene + * @see {@link https://niivue.com/demos/features/selectfont.html | live demo usage} + */ + async loadFromFile(i) { + const e = this.getFileExt(i.name); + if (ut.includes(e)) { + await kt.loadFromFile({ file: i, gl: this.gl, name: i.name }).then((t) => { + this.addMesh(t); + }); + return; + } + await Se.loadFromFile({ + file: i, + name: i.name + }).then((t) => { + this.addVolume(t); + }); + } + /** + * Registers a custom external file loader for handling specific file types in Niivue. + * + * This method allows you to define how certain file extensions are handled when loaded into Niivue. + * The provided `loader` function should return an object containing an `ArrayBuffer` of the file's contents + * and the file extension (used for inferring how Niivue should process the data). + * + * Optionally, `positions` and `indices` can be returned to support loading mesh data (e.g. `.mz3` format). + * + * @example + * const myCustomLoader = async (file) => { + * const arrayBuffer = await file.arrayBuffer() + * return { + * arrayBuffer, + * fileExt: 'iwi.cbor', + * positions: new Float32Array(...), + * indices: new Uint32Array(...) + * } + * } + * + * nv.useLoader(myCustomLoader, 'iwi.cbor', 'nii') + * + * @param loader - A function that accepts a `File` or `ArrayBuffer` and returns an object with `arrayBuffer` and `fileExt` properties. May also return `positions` and `indices` for meshes. + * @param fileExt - The original file extension (e.g. 'iwi.cbor') to associate with this loader. + * @param toExt - The target file extension Niivue should treat the file as (e.g. 'nii' or 'mz3'). + */ + useLoader(i, e, t) { + this.loaders = { + ...this.loaders, + [e.toUpperCase()]: { + loader: i, + toExt: t + } + }; + } + /** + * Set a custom loader for handling DICOM files. + */ + useDicomLoader(i) { + this.dicomLoader = i; + } + /** + * Get the currently assigned DICOM loader. + */ + getDicomLoader() { + return this.dicomLoader; + } + // dicom loading is a special case because it can take a list + // of files (e.g. from a user supplied DICOM directory) or a single file. + // Our preferred DICOM loader is the WASM port of dcm2niix (implemented in a separate niivue loader module). + // useDicomLoader(loader: unknown, toExt: string) { + // this.loaders = { + // ...this.loaders, + // ['DCM']: { + // loader, + // toExt, + // }, + // } + // } + /** + * Handles file and URL drag-and-drop events on the canvas. + * Supports loading of volumes, meshes, NVD documents, and DICOM directories. + * Honors modifier keys (e.g., Shift to replace, Alt for drawing overlays). + * @internal + */ + async dropListener(i) { + if (this.eventInBounds(i)) + this.opts.bounds && (this.opts.showBoundsBorder = !0); + else { + this.opts.showBoundsBorder = !1; + return; + } + if (i.stopPropagation(), i.preventDefault(), !this.opts.dragAndDropEnabled) + return; + const e = [], t = i.dataTransfer; + if (!t) + return; + const s = t.getData("text/uri-list"); + if (s) { + const r = Si(s), a = this.getFileExt(s); + R.debug("dropped ext"), R.debug(a), ut.includes(a) ? this.addMeshFromUrl({ url: s }).catch((n) => { + throw n; + }) : a === "NVD" ? this.loadDocumentFromUrl(s).catch((n) => { + throw n; + }) : this.addVolumeFromUrl(r).catch((n) => { + throw n; + }); + } else { + const r = t.items; + if (r.length > 0) { + !i.shiftKey && !i.altKey && (this.volumes = [], this.overlays = [], this.meshes = []), this.closeDrawing(), this.closePAQD(); + for (const a of Array.from(r)) { + const n = a.webkitGetAsEntry(); + if (R.debug(n), !n) + throw new Error("could not get entry from file"); + if (n.isFile) { + const o = this.getFileExt(n.name); + let l; + if (n.name.lastIndexOf("HEAD") !== -1) + for (const c of Array.from(r)) { + const h = c.webkitGetAsEntry(); + if (!h) + throw new Error("could not get paired entry"); + const u = n.name.substring(0, n.name.lastIndexOf("HEAD")), d = h.name.substring(0, h.name.lastIndexOf("BRIK")); + u === d && (l = h); + } + if (n.name.toUpperCase().lastIndexOf("HDR") !== -1) + for (const c of Array.from(r)) { + const h = c.webkitGetAsEntry(); + if (!h) + throw new Error("could not get paired entry"); + const u = n.name.substring(0, n.name.toUpperCase().lastIndexOf("HDR")), d = h.name.substring( + 0, + h.name.toUpperCase().lastIndexOf("IMG") + ); + u === d && (l = h); + } + if (n.name.lastIndexOf("BRIK") !== -1 || n.name.toUpperCase().lastIndexOf("IMG") !== -1) + continue; + if (this.loaders[o]) { + const c = await a0(n); + await this.loadImages([ + { + url: c, + name: `${n.name}` + } + ]); + continue; + } + if (ut.includes(o)) { + n.file((c) => { + (async () => { + try { + const h = await kt.loadFromFile({ + file: c, + gl: this.gl, + name: c.name + }); + this.addMesh(h); + } catch (h) { + console.error("Error loading mesh:", h); + } + })().catch((h) => console.error(h)); + }); + continue; + } else if (o === "NVD") { + n.file((c) => { + (async () => { + try { + const h = await es.loadFromFile(c); + await this.loadDocument(h), R.debug("loaded document"); + } catch (h) { + console.error(h); + } + })().catch((h) => console.error(h)); + }); + break; + } + n.file((c) => { + (async () => { + try { + if (l) + l.file((h) => { + (async () => { + try { + const u = await Se.loadFromFile({ + file: c, + urlImgData: h, + limitFrames4D: this.opts.limitFrames4D + }); + this.addVolume(u); + } catch (u) { + console.error(u); + } + })().catch(console.error); + }); + else { + const h = await Se.loadFromFile({ + file: c, + urlImgData: l, + limitFrames4D: this.opts.limitFrames4D + }); + i.altKey ? (R.debug("alt key detected: assuming this is a drawing overlay"), this.drawClearAllUndoBitmaps(), this.loadDrawing(h)) : this.addVolume(h); + } + } catch (h) { + console.error(h); + } + })().catch(console.error); + }); + } else n.isDirectory && this.traverseFileTree(n, "", e).then((o) => { + const l = this.getDicomLoader().loader; + if (!l) + throw new Error("No loader for DICOM files"); + l(o).then(async (c) => { + const h = c.map( + (u) => Se.loadFromUrl({ + url: u.data, + name: u.name, + limitFrames4D: this.opts.limitFrames4D + }) + ); + Promise.all(h).then(async (u) => { + await this.onDicomLoaderFinishedWithImages(u); + }).catch((u) => { + throw u; + }); + }).catch((c) => { + console.error("Error loading DICOM files:", c); + }); + }).catch((o) => { + throw o; + }); + } + } + } + this.drawScene(); + } + /** + * insert a gap between slices of a mutliplanar view. + * @param pixels - spacing between tiles of multiplanar view + * @example niivue.setMultiplanarPadPixels(4) + * @see {@link https://niivue.com/demos/features/atlas.html | live demo usage} + */ + setMultiplanarPadPixels(i) { + this.opts.multiplanarPadPixels = i, this.drawScene(); + } + /** + * control placement of 2D slices. + * @param layout - AUTO: 0, COLUMN: 1, GRID: 2, ROW: 3, + * @example niivue.setMultiplanarLayout(2) + * @see {@link https://niivue.com/demos/features/layout.html | live demo usage} + */ + setMultiplanarLayout(i) { + typeof i == "string" && (i = parseInt(i)), this.opts.multiplanarLayout = i, this.drawScene(); + } + /** + * determine if text appears at corner (true) or sides of 2D slice. + * @param isCornerOrientationText - controls position of text + * @example niivue.setCornerOrientationText(true) + * @see {@link https://niivue.com/demos/features/worldspace2.html | live demo usage} + */ + setCornerOrientationText(i) { + this.opts.isCornerOrientationText = i, this.updateGLVolume(); + } + /** + * Show or hide orientation labels (e.g., L/R, A/P) in 2D slice views + * @param isOrientationTextVisible - whether orientation text should be displayed + * @example niivue.setIsOrientationTextVisible(false) + * @see {@link https://niivue.com/demos/features/basic.multiplanar.html | live demo usage} + */ + setIsOrientationTextVisible(i) { + this.opts.isOrientationTextVisible = i, this.drawScene(); + } + /** + * Show or hide all four orientation labels (e.g., L/R, A/P, S/I) in 2D slice views + * @param showAllOrientationMarkers - whether all four orientation markers should be displayed + * @example niivue.setShowAllOrientationMarkers(true) + */ + setShowAllOrientationMarkers(i) { + this.opts.showAllOrientationMarkers = i, this.drawScene(); + } + /** + * determine proportion of screen real estate devoted to rendering in multiplanar view. + * @param fraction - proportion of screen devoted to primary (hero) image (0 to disable) + * @example niivue.setHeroImage(0.5) + * @see {@link https://niivue.com/demos/features/layout.html | live demo usage} + */ + setHeroImage(i) { + this.opts.heroImageFraction = i, this.drawScene(); + } + /** + * Set a custom slice layout. This overrides the built-in layouts. + * @param layout - Array of layout specifications for each slice view + * @example + * niivue.setCustomLayout([ + * // Left 50% - Sag + * {sliceType: 2, position: [0, 0, 0.5, 1.0]}, + * // Top right - Cor + * {sliceType: 1, position: [0.5, 0, 0.5, 0.5]}, + * // Bottom right - Ax + * {sliceType: 0, position: [0.5, 0.5, 0.5, 0.5]} + * ]) + * + * produces: + * +----------------+----------------+ + * | | | + * | | coronal | + * | | | + * | | | + * | sagittal +----------------+ + * | | | + * | | axial | + * | | | + * | | | + * +----------------+----------------+ + */ + setCustomLayout(i) { + for (let e = 0; e < i.length; e++) { + const [t, s, r, a] = i[e].position, n = t + r, o = s + a; + for (let l = e + 1; l < i.length; l++) { + const [c, h, u, d] = i[l].position, f = c + u, g = h + d, m = t < f && n > c, p = s < g && o > h; + if (m && p) + throw new Error(`Custom layout is invalid. Tile ${e} overlaps with tile ${l}.`); + } + } + this.customLayout = i, this.drawScene(); + } + /** + * Clear custom layout and rely on built-in layouts + */ + clearCustomLayout() { + this.customLayout = null, this.drawScene(); + } + /** + * Get the current custom layout if set + * @returns The current custom layout or null if using built-in layouts + */ + getCustomLayout() { + return this.customLayout; + } + /** + * control whether 2D slices use radiological or neurological convention. + * @param isRadiologicalConvention - new display convention + * @example niivue.setRadiologicalConvention(true) + * @see {@link https://niivue.com/demos/features/worldspace.html | live demo usage} + */ + setRadiologicalConvention(i) { + this.opts.isRadiologicalConvention = i, this.updateGLVolume(); + } + /** + * Reset scene to default settings. + * @param options - @see NiiVueOptions + * @param resetBriCon - also reset contrast (default false). + * @example niivue.nv1.setDefaults(opts, true); + * @see {@link https://niivue.com/demos/features/connectome.html | live demo usage} + */ + setDefaults(i = {}, e = !1) { + this.document.opts = { ...Ge }, this.scene.sceneData = { ...Xt }; + for (const t in i) + typeof i[t] == "function" ? this[t] = i[t] : this.opts[t] = Ge[t] === void 0 ? Ge[t] : i[t]; + if (this.scene.pan2Dxyzmm = [0, 0, 0, 1], e && this.volumes && this.volumes.length > 0) + for (let t = 0; t < this.volumes.length; t++) + this.volumes[t].cal_min = this.volumes[t].robust_min, this.volumes[t].cal_max = this.volumes[t].robust_max; + this.updateGLVolume(); + } + /** + * Limit visibility of mesh in front of a 2D image. Requires world-space mode. + * @param meshThicknessOn2D - distance from voxels for clipping mesh. Use Infinity to show entire mesh or 0.0 to hide mesh. + * @example niivue.setMeshThicknessOn2D(42) + * @see {@link https://niivue.com/demos/features/worldspace2.html | live demo usage} + */ + setMeshThicknessOn2D(i) { + this.opts.meshThicknessOn2D = i, this.updateGLVolume(); + } + /** + * Create a custom multi-slice mosaic (aka lightbox, montage) view. + * @param str - description of mosaic. + * @example niivue.setSliceMosaicString("A 0 20 C 30 S 42") + * @see {@link https://niivue.com/demos/features/mosaics.html | live demo usage} + */ + setSliceMosaicString(i) { + this.sliceMosaicString = i, this.updateGLVolume(); + } + /** + * control 2D slice view mode. + * @param isSliceMM - control whether 2D slices use world space (true) or voxel space (false). Beware that voxel space mode limits properties like panning, zooming and mesh visibility. + * @example niivue.setSliceMM(true) + * @see {@link https://niivue.com/demos/features/worldspace2.html | live demo usage} + */ + setSliceMM(i) { + this.opts.isSliceMM = i, this.updateGLVolume(); + } + /** + * control whether voxel overlays are combined using additive (emission) or traditional (transmission) blending. + * @param isAdditiveBlend - emission (true) or transmission (false) mixing + * @example niivue.isAdditiveBlend(true) + * @see {@link https://niivue.com/demos/features/additive.voxels.html | live demo usage} + */ + setAdditiveBlend(i) { + this.opts.isAdditiveBlend = i, this.updateGLVolume(); + } + /** + * Detect if display is using radiological or neurological convention. + * @returns radiological convention status + * @example let rc = niivue.getRadiologicalConvention() + */ + getRadiologicalConvention() { + return this.opts.isRadiologicalConvention; + } + /** + * Force WebGL canvas to use high resolution display, regardless of browser defaults. + * @param forceDevicePixelRatio - -1: block high DPI; 0= allow high DPI: >0 use specified pixel ratio + * @example niivue.setHighResolutionCapable(true); + * @see {@link https://niivue.com/demos/features/sync.mesh.html | live demo usage} + */ + setHighResolutionCapable(i) { + typeof i == "boolean" && (i = i ? 0 : -1), this.opts.forceDevicePixelRatio = i, this.resizeListener(), this.drawScene(); + } + /** + * Start watching for changes to configuration options. + * This is a convenience method that sets up the onOptsChange callback. + * @param callback - Function to call when any option changes + * @example + * niivue.watchOptsChanges((propertyName, newValue, oldValue) => { + * console.log(`Option ${propertyName} changed from ${oldValue} to ${newValue}`) + * }) + * @see {@link https://niivue.com/demos/ | live demo usage} + */ + watchOptsChanges(i) { + this.onOptsChange = i; + } + /** + * Stop watching for changes to configuration options. + * This removes the current onOptsChange callback. + * @example niivue.unwatchOptsChanges() + * @see {@link https://niivue.com/demos/ | live demo usage} + */ + unwatchOptsChanges() { + this.onOptsChange = () => { + }; + } + /** + * add a new volume to the canvas + * @param volume - the new volume to add to the canvas + * @example + * niivue = new Niivue() + * niivue.addVolume(NVImage.loadFromUrl({url:'../someURL.nii.gz'})) + * @see {@link https://niivue.com/demos/features/conform.html | live demo usage} + */ + addVolume(i) { + this.volumes.push(i); + const e = this.volumes.length === 1 ? 0 : this.volumes.length - 1; + this.setVolume(i, e), this.onImageLoaded(i), R.debug("loaded volume", i.name), R.debug(i); + } + /** + * add a new mesh to the canvas + * @param mesh - the new mesh to add to the canvas + * @example + * niivue = new Niivue() + * niivue.addMesh(NVMesh.loadFromUrl({url:'../someURL.gii'})) + * @see {@link https://niivue.com/demos/features/document.3d.html | live demo usage} + */ + addMesh(i) { + this.meshes.push(i); + const e = this.meshes.length === 1 ? 0 : this.meshes.length - 1; + this.setMesh(i, e), this.onMeshLoaded(i); + } + /** + * get the index of a volume by its unique id. unique ids are assigned to the NVImage.id property when a new NVImage is created. + * @param id - the id string to search for + * @example + * niivue = new Niivue() + * niivue.getVolumeIndexByID(someVolume.id) + */ + getVolumeIndexByID(i) { + const e = this.volumes.length; + for (let t = 0; t < e; t++) + if (this.volumes[t].id === i) + return t; + return -1; + } + /** + * Saves the current drawing state as an RLE-compressed bitmap for undo history. + * Uses a circular buffer to limit undo memory usage. + * @internal + */ + drawAddUndoBitmap(i = !0) { + if (!this.drawBitmap || this.drawBitmap.length < 1) { + R.debug("drawAddUndoBitmap error: No drawing open"); + return; + } + if (!i && this.drawUndoBitmaps.length > 0) { + const e = this.drawBitmap.length, t = bs(this.drawUndoBitmaps[this.currentDrawUndoBitmap], e); + for (let s = 0; s < e; s++) + t[s] > 0 && (this.drawBitmap[s] = t[s]); + this.refreshDrawing(!1); + } + this.currentDrawUndoBitmap++, this.currentDrawUndoBitmap >= this.opts.maxDrawUndoBitmaps && (this.currentDrawUndoBitmap = 0), this.drawUndoBitmaps[this.currentDrawUndoBitmap] = $c(this.drawBitmap); + } + /** + * Clears all stored drawing undo bitmaps and resets the undo index. + * @internal + */ + drawClearAllUndoBitmaps() { + if (this.currentDrawUndoBitmap = this.opts.maxDrawUndoBitmaps, !(!this.drawUndoBitmaps || this.drawUndoBitmaps.length < 1)) + for (let i = this.drawUndoBitmaps.length - 1; i >= 0; i--) + this.drawUndoBitmaps[i] = new Uint8Array(); + } + /** + * Restore drawing to previous state + * @example niivue.drawUndo(); + * @see {@link https://niivue.com/demos/features/draw.ui.html | live demo usage} + */ + drawUndo() { + const { drawBitmap: i, currentDrawUndoBitmap: e } = eh({ + drawUndoBitmaps: this.drawUndoBitmaps, + currentDrawUndoBitmap: this.currentDrawUndoBitmap, + drawBitmap: this.drawBitmap + }); + this.drawBitmap = i, this.currentDrawUndoBitmap = e, this.refreshDrawing(!0); + } + /** + * Loads a drawing overlay and aligns it with the current background image. + * Converts the input image to match the background's orientation and stores it as a drawable bitmap. + * Initializes the undo history and prepares the drawing texture. + * + * @param drawingBitmap - A `NVImage` object representing the drawing to load. Must match the dimensions of the background image. + * @returns `true` if the drawing was successfully loaded and aligned; `false` if dimensions are incompatible. + */ + loadDrawing(i) { + if (this.drawBitmap && R.debug("Overwriting open drawing!"), !this.back) + throw new Error("back undefined"); + this.drawClearAllUndoBitmaps(); + const e = i.hdr.dims; + if (e[1] !== this.back.hdr.dims[1] || e[2] !== this.back.hdr.dims[2] || e[3] !== this.back.hdr.dims[3]) + return R.debug("drawing dimensions do not match background image"), !1; + i.img.constructor !== Uint8Array && R.debug("Drawings should be UINT8"); + const t = i.permRAS, s = e[1] * e[2] * e[3]; + this.drawBitmap = new Uint8Array(s), this.opts.is2DSliceShader ? this.drawTexture = this.r8Tex2D(this.drawTexture, tt, this.back.dims, !0) : this.drawTexture = this.r8Tex(this.drawTexture, tt, this.back.dims, !0); + const r = [0, 0, 0]; + for (let g = 0; g < 3; g++) + for (let m = 0; m < 3; m++) + Math.abs(t[g]) - 1 === m && (r[m] = g * Math.sign(t[g])); + let a = 1; + const n = [1, 1, 1], o = [!1, !1, !1]; + for (let g = 0; g < r.length; g++) + for (let m = 0; m < r.length; m++) + Math.abs(r[m]) === g && (n[m] = a, (r[m] < 0 || Object.is(r[m], -0)) && (o[m] = !0), a *= e[m + 1]); + let l = j.range(0, e[1] - 1, 1); + o[0] && (l = j.range(e[1] - 1, 0, -1)); + for (let g = 0; g < e[1]; g++) + l[g] *= n[0]; + let c = j.range(0, e[2] - 1, 1); + o[1] && (c = j.range(e[2] - 1, 0, -1)); + for (let g = 0; g < e[2]; g++) + c[g] *= n[1]; + let h = j.range(0, e[3] - 1, 1); + o[2] && (h = j.range(e[3] - 1, 0, -1)); + for (let g = 0; g < e[3]; g++) + h[g] *= n[2]; + const u = i.img, d = this.drawBitmap; + let f = 0; + for (let g = 0; g < e[3]; g++) + for (let m = 0; m < e[2]; m++) + for (let p = 0; p < e[1]; p++) + d[l[p] + c[m] + h[g]] = u[f], f++; + return this.drawAddUndoBitmap(), this.refreshDrawing(!1), this.drawScene(), !0; + } + /** + * Binarize a volume by converting all non-zero voxels to 1 + * @param volume - the image volume to modify in place + * @see {@link https://niivue.com/demos/features/clusterize.html | live demo usage} + */ + binarize(i) { + const e = i.hdr.dims, t = e[1] * e[2] * e[3], s = new Uint8Array(t); + for (let r = 0; r < t; r++) + i.img[r] !== 0 && (s[r] = 1); + i.img = s, i.hdr.datatypeCode = 2, i.hdr.cal_min = 0, i.hdr.cal_max = 1; + } + /** + * Open drawing + * @param fnm - filename of NIfTI format drawing + * @param isBinarize - if true will force drawing voxels to be either 0 or 1. + * @example niivue.loadDrawingFromUrl("../images/lesion.nii.gz"); + * @see {@link https://niivue.com/demos/features/draw.ui.html | live demo usage} + */ + async loadDrawingFromUrl(i, e = !1) { + this.drawBitmap && R.debug("Overwriting open drawing!"), this.drawClearAllUndoBitmaps(); + let t = !1; + try { + const s = await Se.loadFromUrl(Si(i)); + e && await this.binarize(s), t = this.loadDrawing(s); + } catch { + R.error("loadDrawingFromUrl() failed to load " + i), this.drawClearAllUndoBitmaps(); + } + return t; + } + /** + * Computes one or more Otsu threshold levels for the primary volume. + * Returns raw intensity values corresponding to bin-based thresholds. + * @internal + */ + findOtsu(i = 2) { + if (this.volumes.length < 1) + return []; + const e = this.volumes[0].img, t = e.length; + if (t < 1) + return []; + const s = 256, r = s - 1, a = new Array(s).fill(0), n = this.volumes[0].cal_min, o = this.volumes[0].cal_max; + if (o <= n) + return []; + const l = (o - n) / s; + function c(v) { + return v * l + n; + } + const h = (s - 1) / Math.abs(o - n), u = this.volumes[0].hdr.scl_inter, d = this.volumes[0].hdr.scl_slope; + for (let v = 0; v < t; v++) { + let A = e[v] * d + u; + A = Math.min(Math.max(A, n), o), A = Math.round((A - n) * h), a[A]++; + } + const f = Array(s).fill(0).map(() => Array(s).fill(0)), g = Array(s).fill(0).map(() => Array(s).fill(0)); + for (let v = 1; v < s; ++v) + f[v][v] = a[v], g[v][v] = v * a[v]; + for (let v = 1; v < s - 1; ++v) + f[1][v + 1] = f[1][v] + a[v + 1], g[1][v + 1] = g[1][v] + (v + 1) * a[v + 1]; + for (let v = 2; v < s; v++) + for (let A = v + 1; A < s; A++) + f[v][A] = f[1][A] - f[1][v - 1], g[v][A] = g[1][A] - g[1][v - 1]; + for (let v = 1; v < s; ++v) + for (let A = v + 1; A < s; A++) + f[v][A] !== 0 && (f[v][A] = g[v][A] * g[v][A] / f[v][A]); + let m = 0; + const p = [1 / 0, 1 / 0, 1 / 0]; + if (i > 3) + for (let v = 0; v < s - 3; v++) + for (let A = v + 1; A < s - 2; A++) + for (let x = A + 1; x < s - 1; x++) { + const w = f[0][v] + f[v + 1][A] + f[A + 1][x] + f[x + 1][r]; + w > m && (p[0] = v, p[1] = A, p[2] = x, m = w); + } + else if (i === 3) + for (let v = 0; v < s - 2; v++) + for (let A = v + 1; A < s - 1; A++) { + const x = f[0][v] + f[v + 1][A] + f[A + 1][r]; + x > m && (p[0] = v, p[1] = A, m = x); + } + else + for (let v = 0; v < s - 1; v++) { + const A = f[0][v] + f[v + 1][r]; + A > m && (p[0] = v, m = A); + } + return [c(p[0]), c(p[1]), c(p[2])]; + } + /** + * remove dark voxels in air + * @param levels - (2-4) segment brain into this many types. For example drawOtsu(2) will create a binary drawing where bright voxels are colored and dark voxels are clear. + * @example niivue.drawOtsu(3); + * @see {@link https://niivue.com/demos/features/draw.ui.html | live demo usage} + */ + drawOtsu(i = 2) { + if (this.volumes.length === 0) + return; + const e = this.volumes[0].img.length, t = this.findOtsu(i); + if (t.length < 3) + return; + this.drawBitmap || this.createEmptyDrawing(); + const s = this.drawBitmap, r = this.volumes[0].img; + for (let a = 0; a < e; a++) { + if (s[a] !== 0) + continue; + const n = r[a]; + n > t[0] && (s[a] = 1), n > t[1] && (s[a] = 2), n > t[2] && (s[a] = 3); + } + this.drawAddUndoBitmap(), this.refreshDrawing(!0); + } + /** + * remove dark voxels in air + * @param level - (1-5) larger values for more preserved voxels + * @param volIndex - volume to dehaze + * @example niivue.removeHaze(3, 0); + * @see {@link https://niivue.com/demos/features/draw.ui.html | live demo usage} + */ + removeHaze(i = 5, e = 0) { + const t = this.volumes[e].img, s = this.volumes[e].hdr, r = t.length; + let a = 2; + (i === 5 || i === 1) && (a = 4), (i === 4 || i === 2) && (a = 3); + const n = this.findOtsu(a); + if (n.length < 3) + return; + let o = n[0]; + i === 1 && (o = n[2]), i === 2 && (o = n[1]); + const l = s.scl_inter, c = s.scl_slope, h = this.volumes[e].global_min; + for (let u = 0; u < r; u++) + t[u] * c + l < o && (t[u] = h); + this.refreshLayers(this.volumes[e], 0), this.drawScene(); + } + /** + * Save voxel-based image to disk. + * + * @param options - configuration object with the following fields: + * - `filename`: name of the NIfTI image to create + * - `isSaveDrawing`: whether to save the drawing layer or the background image + * - `volumeByIndex`: which image layer to save (0 for background) + * @returns `true` if successful when writing to disk, or a `Uint8Array` if exported as binary data + * + * @example + * niivue.saveImage({ filename: "myimage.nii.gz", isSaveDrawing: true }); + * niivue.saveImage({ filename: "myimage.nii.gz", isSaveDrawing: true }); + * @see {@link https://niivue.com/demos/features/draw.ui.html | live demo usage} + */ + async saveImage(i = Kr) { + const e = { + ...Kr, + ...i + }, { filename: t, isSaveDrawing: s, volumeByIndex: r } = e; + return R.debug("saveImage", t, s, r), this.back?.dims === void 0 ? (R.debug("No voxelwise image open"), !1) : s ? this.drawBitmap ? await this.volumes[0].saveToDisk(t, this.drawBitmap) : (R.debug("No drawing open"), !1) : (R.debug("saving image"), this.volumes[r].saveToDisk(t)); + } + /** + * Returns the index of a mesh given its ID or index. + * + * @param id - The mesh ID as a string, or an index number. + * @returns The mesh index, or -1 if not found or out of range. + */ + getMeshIndexByID(i) { + if (typeof i == "number") + return i >= this.meshes.length ? -1 : i; + const e = this.meshes.length; + for (let t = 0; t < e; t++) + if (this.meshes[t].id === i) + return t; + return -1; + } + /** + * change property of mesh, tractogram or connectome + * @param id - identity of mesh to change + * @param key - attribute to change + * @param val - for attribute + * @example niivue.setMeshProperty(niivue.meshes[0].id, 'fiberLength', 42) + * @see {@link https://niivue.com/demos/features/meshes.html | live demo usage} + */ + setMeshProperty(i, e, t) { + const s = this.getMeshIndexByID(i); + if (s < 0) { + R.warn("setMeshProperty() id not loaded", i); + return; + } + this.meshes[s].setProperty(e, t, this.gl), this.updateGLVolume(), this.onMeshPropertyChanged(s, e, t); + } + /** + * returns the index of the mesh vertex that is closest to the provided coordinates + * @param mesh - identity of mesh to change + * @param Xmm - location in left/right dimension + * @param Ymm - location in posterior/anterior dimension + * @param Zmm - location in foot/head dimension + * @returns the an array where ret[0] is the mesh index and ret[1] is distance from vertex to coordinates + * @example niivue.indexNearestXYZmm(niivue.meshes[0].id, -22, 42, 13) + * @see {@link https://niivue.com/demos/features/clipplanes.html | live demo usage} + */ + indexNearestXYZmm(i, e, t, s) { + const r = this.getMeshIndexByID(i); + return r < 0 ? (R.warn("indexNearestXYZmm() id not loaded", i), [NaN, NaN]) : this.meshes[r].indexNearestXYZmm(e, t, s); + } + /** + * reduce complexity of FreeSurfer mesh + * @param mesh - identity of mesh to change + * @param order - decimation order 0..6 + * @example niivue.decimateHierarchicalMesh(niivue.meshes[0].id, 4) + * @returns boolean false if mesh is not hierarchical or of lower order + * @see {@link https://niivue.com/demos/features/meshes.html | live demo usage} + */ + decimateHierarchicalMesh(i, e = 3) { + const t = this.getMeshIndexByID(i); + if (t < 0) { + R.warn("reverseFaces() id not loaded", i); + return; + } + const s = this.meshes[t].decimateHierarchicalMesh(this.gl, e); + return this.updateGLVolume(), s; + } + /** + * reverse triangle winding of mesh (swap front and back faces) + * @param mesh - identity of mesh to change + * @example niivue.reverseFaces(niivue.meshes[0].id) + * @see {@link https://niivue.com/demos/features/meshes.html | live demo usage} + */ + reverseFaces(i) { + const e = this.getMeshIndexByID(i); + if (e < 0) { + R.warn("reverseFaces() id not loaded", i); + return; + } + this.meshes[e].reverseFaces(this.gl), this.updateGLVolume(); + } + /** + * reverse triangle winding of mesh (swap front and back faces) + * @param mesh - identity of mesh to change + * @param layer - selects the mesh overlay (e.g. GIfTI or STC file) + * @param key - attribute to change + * @param val - value for attribute + * @example niivue.setMeshLayerProperty(niivue.meshes[0].id, 0, 'frame4D', 22) + * @see {@link https://niivue.com/demos/features/mesh.4D.html | live demo usage} + */ + async setMeshLayerProperty(i, e, t, s) { + const r = this.getMeshIndexByID(i); + if (r < 0) { + R.warn("setMeshLayerProperty() id not loaded", i); + return; + } + await this.meshes[r].setLayerProperty(e, t, s, this.gl), this.updateGLVolume(); + } + /** + * adjust offset position and scale of 2D sliceScale + * @param xyzmmZoom - first three components are spatial, fourth is scaling + * @example niivue.setPan2Dxyzmm([5,-4, 2, 1.5]) + */ + setPan2Dxyzmm(i) { + this.scene.pan2Dxyzmm = i, this.opts.yoke3Dto2DZoom && (this.scene.volScaleMultiplier = i[3]), this.drawScene(); + } + /** + * set rotation of 3D render view + * @example niivue.setRenderAzimuthElevation(45, 15) + * @see {@link https://niivue.com/demos/features/mask.html | live demo usage} + */ + setRenderAzimuthElevation(i, e) { + this.scene.renderAzimuth = i, this.scene.renderElevation = e, this.onAzimuthElevationChange(i, e), this.drawScene(); + } + /** + * get the index of an overlay by its unique id. unique ids are assigned to the NVImage.id property when a new NVImage is created. + * @param id - the id string to search for + * @see NiiVue#getVolumeIndexByID + * @example + * niivue = new Niivue() + * niivue.getOverlayIndexByID(someVolume.id) + */ + getOverlayIndexByID(i) { + const e = this.overlays.length; + for (let t = 0; t < e; t++) + if (this.overlays[t].id === i) + return t; + return -1; + } + /** + * set the index of a volume. This will change it's ordering and appearance if there are multiple volumes loaded. + * @param volume - the volume to update + * @param toIndex - the index to move the volume to. The default is the background (0 index) + * @example + * niivue = new Niivue() + * niivue.setVolume(someVolume, 1) // move it to the second position in the array of loaded volumes (0 is the first position) + */ + setVolume(i, e = 0) { + const t = this.volumes.length; + if (e > t) + return; + const s = this.getVolumeIndexByID(i.id); + e === 0 ? (this.volumes.splice(s, 1), this.volumes.unshift(i), this.back = this.volumes[0], this.overlays = this.volumes.slice(1)) : e < 0 ? (this.volumes.splice(this.getVolumeIndexByID(i.id), 1), this.back = this.volumes[0], this.volumes.length > 1 ? this.overlays = this.volumes.slice(1) : this.overlays = []) : (this.volumes.splice(s, 1), this.volumes.splice(e, 0, i), this.overlays = this.volumes.slice(1), this.back = this.volumes[0]), this.updateGLVolume(); + } + /** + * Reorders a mesh within the internal mesh list. + * + * @param mesh - The `NVMesh` instance to reposition. + * @param toIndex - Target index to move the mesh to. + * - If `0`, moves mesh to the front. + * - If `< 0`, removes the mesh. + * - If within bounds, inserts mesh at the specified index. + */ + setMesh(i, e = 0) { + this.meshes.forEach((r) => { + R.debug("MESH: ", r.name); + }); + const t = this.meshes.length; + if (e > t) + return; + const s = this.getMeshIndexByID(i.id); + e === 0 ? (this.meshes.splice(s, 1), this.meshes.unshift(i)) : e < 0 ? this.meshes.splice(this.getMeshIndexByID(i.id), 1) : (this.meshes.splice(s, 1), this.meshes.splice(e, 0, i)), this.updateGLVolume(), this.meshes.forEach((r) => { + R.debug(r.name); + }); + } + /** + * Remove a volume + * @param volume - volume to delete + * @example + * niivue = new Niivue() + * niivue.removeVolume(this.volumes[3]) + * @see {@link https://niivue.com/demos/features/document.3d.html | live demo usage} + */ + removeVolume(i) { + if (this.setVolume(i, -1), this.mediaUrlMap.has(i)) { + const e = this.mediaUrlMap.get(i); + this.onVolumeWithUrlRemoved(e), this.mediaUrlMap.delete(i); + } + this.drawScene(); + } + /** + * Remove a volume from the scene by its index + * @param index - index of the volume to remove + * @throws if the index is out of bounds + * @see {@link https://niivue.com/demos/features/clusterize.html | live demo usage} + */ + removeVolumeByIndex(i) { + if (i >= this.volumes.length) + throw new Error("Index of volume out of bounds"); + this.removeVolume(this.volumes[i]); + } + /** + * Remove a triangulated mesh, connectome or tractogram + * @param mesh - mesh to delete + * @example + * niivue = new Niivue() + * niivue.removeMesh(this.meshes[3]) + * @see {@link https://niivue.com/demos/features/connectome.html | live demo usage} + */ + removeMesh(i) { + if (i.unloadMesh(this.gl), this.setMesh(i, -1), this.mediaUrlMap.has(i)) { + const e = this.mediaUrlMap.get(i); + this.onMeshWithUrlRemoved(e), this.mediaUrlMap.delete(i); + } + } + /** + * Remove a triangulated mesh, connectome or tractogram + * @param url - URL of mesh to delete + * @example + * niivue.removeMeshByUrl('../images/cit168.mz3') + */ + removeMeshByUrl(i) { + const e = this.getMediaByUrl(i); + e && (this.removeMesh(e), this.mediaUrlMap.delete(e), this.onMeshWithUrlRemoved(i)); + } + /** + * Move a volume to the bottom of the stack of loaded volumes. The volume will become the background + * @param volume - the volume to move + * @example + * niivue = new Niivue() + * niivue.moveVolumeToBottom(this.volumes[3]) // move the 4th volume to the 0 position. It will be the new background + */ + moveVolumeToBottom(i) { + this.setVolume(i, 0); + } + /** + * Move a volume up one index position in the stack of loaded volumes. This moves it up one layer + * @param volume - the volume to move + * @example + * niivue = new Niivue() + * niivue.moveVolumeUp(this.volumes[0]) // move the background image to the second index position (it was 0 index, now will be 1) + */ + moveVolumeUp(i) { + const e = this.getVolumeIndexByID(i.id); + this.setVolume(i, e + 1); + } + /** + * Move a volume down one index position in the stack of loaded volumes. This moves it down one layer + * @param volume - the volume to move + * @example + * niivue = new Niivue() + * niivue.moveVolumeDown(this.volumes[1]) // move the second image to the background position (it was 1 index, now will be 0) + */ + moveVolumeDown(i) { + const e = this.getVolumeIndexByID(i.id); + this.setVolume(i, e - 1); + } + /** + * Move a volume to the top position in the stack of loaded volumes. This will be the top layer + * @param volume - the volume to move + * @example + * niivue = new Niivue() + * niivue.moveVolumeToTop(this.volumes[0]) // move the background image to the top layer position + */ + moveVolumeToTop(i) { + this.setVolume(i, this.volumes.length - 1); + } + /** + * Records the current mouse position in screen space (adjusted for device pixel ratio). + * @internal + */ + mouseDown(i, e) { + i *= this.uiData.dpr, e *= this.uiData.dpr, this.mousePos = [i, e]; + } + /** + * Updates mouse position + * @internal + */ + updateMousePos(i, e) { + return i *= this.uiData.dpr, e *= this.uiData.dpr, this.mousePos = [i, e], [i, e]; + } + /** + * and modifies 3D render view if the pointer is in the render tile. + * + * @internal + */ + mouseMove(i, e) { + i *= this.uiData.dpr, e *= this.uiData.dpr; + const t = (i - this.mousePos[0]) / this.uiData.dpr, s = (e - this.mousePos[1]) / this.uiData.dpr; + this.mousePos = [i, e], !(this.inRenderTile(i, e) < 0) && (Math.abs(t) < 1 && Math.abs(s) < 1 || (this.scene.renderAzimuth += t, this.scene.renderElevation += s, this.drawScene())); + } + /** + * convert spherical AZIMUTH, ELEVATION to Cartesian + * @param azimuth - azimuth number + * @param elevation - elevation number + * @returns the converted [x, y, z] coordinates + * @example + * niivue = new Niivue() + * xyz = niivue.sph2cartDeg(42, 42) + * @internal + */ + sph2cartDeg(i, e) { + const t = -e * (Math.PI / 180), s = (i - 90) % 360 * (Math.PI / 180), r = [Math.cos(t) * Math.cos(s), Math.cos(t) * Math.sin(s), Math.sin(t)], a = Math.sqrt(r[0] * r[0] + r[1] * r[1] + r[2] * r[2]); + return a <= 0 || (r[0] /= a, r[1] /= a, r[2] /= a), r; + } + /** + * Set multiple clip planes from their depth/azimuth/elevation definitions. + * + * depth: distance of clip plane from center of volume, range 0..~1.73 + * (e.g. 2.0 for no clip plane) + * azimuth: camera position in degrees around object, typically 0..360 + * (or -180..+180) + * elevation: camera height in degrees, range -90..90 + * + * This replaces the entire `clipPlanes` and `clipPlaneDepthAziElevs` arrays, + * ensuring they always have the same length. + * + * @param depthAziElevs - array of `[depth, azimuthDeg, elevationDeg]` values + * @see {@link https://niivue.com/demos/features/clipplanesmulti.html | live demo usage} + */ + setClipPlanes(i) { + this.scene.clipPlanes = [], this.scene.clipPlaneDepthAziElevs = []; + for (let e = 0; e < i.length; e++) { + const t = i[e], s = this.sph2cartDeg(t[1], t[2]), r = -t[0], a = [s[0], s[1], s[2], r]; + this.scene.clipPlanes.push(a), this.scene.clipPlaneDepthAziElevs.push(t); + } + this.drawScene(); + } + /** + * Update the clip plane orientation in 3D view mode. + * @param depthAzimuthElevation - a 3-component array: + * - `depth`: distance of clip plane from center of volume (0 to ~1.73, or >2.0 to disable clipping) + * - `azimuth`: camera angle around the object in degrees (0–360 or -180–180) + * - `elevation`: camera height in degrees (-90 to 90) + * @example + * niivue = new Niivue() + * niivue.setClipPlane([42, 42]) + * @see {@link https://niivue.com/demos/features/mask.html | live demo usage} + */ + setClipPlane(i) { + if (!i || i.length === 0) + return; + const e = this.uiData.activeClipPlaneIndex ?? 0; + for (this.scene.clipPlanes || (this.scene.clipPlanes = []), this.scene.clipPlaneDepthAziElevs || (this.scene.clipPlaneDepthAziElevs = []); this.scene.clipPlanes.length <= e; ) + this.scene.clipPlanes.push([0, 0, 0, 2]); + for (; this.scene.clipPlaneDepthAziElevs.length <= e; ) + this.scene.clipPlaneDepthAziElevs.push([2, 0, 0]); + const t = this.sph2cartDeg(i[1] + 180, i[2]), s = [t[0], t[1], t[2], i[0]]; + this.scene.clipPlanes[e] = s, this.scene.clipPlaneDepthAziElevs[e] = i, this.onClipPlaneChange(s), this.drawScene(); + } + /** + * set the crosshair and colorbar outline color + * @param color - an RGBA array. values range from 0 to 1 + * @example + * niivue = new Niivue() + * niivue.setCrosshairColor([0, 1, 0, 0.5]) // set crosshair to transparent green + * @see {@link https://niivue.com/demos/features/colormaps.html | live demo usage} + */ + setCrosshairColor(i) { + this.opts.crosshairColor = i, this.drawScene(); + } + /** + * set thickness of crosshair + * @example niivue.crosshairWidth(2) + * @see {@link https://niivue.com/demos/features/colormaps.html | live demo usage} + */ + setCrosshairWidth(i) { + this.opts.crosshairWidth = i, this.crosshairs3D && (this.crosshairs3D.mm[0] = NaN), this.drawScene(); + } + /* + * set colors and labels for different drawing values + * @param {array} cmap a structure mapping indices to colors and labels + * @example + * let cmap = { + * R: [0, 255, 0], + * G: [0, 20, 0], + * B: [0, 20, 80], + * A: [0, 255, 255], + * labels: ["", "white-matter", "delete T1"], + * }; + * nv.setDrawColormap(cmap); + * @see {@link https://niivue.com/demos/features/draw.ui.html | live demo usage} + */ + setDrawColormap(i) { + this.drawLut = oe.makeDrawLut(i), this.updateGLVolume(); + } + /** + * does dragging over a 2D slice create a drawing? + * @param trueOrFalse - enabled (true) or not (false) + * @example niivue.setDrawingEnabled(true) + * @see {@link https://niivue.com/demos/features/draw.ui.html | live demo usage} + */ + setDrawingEnabled(i) { + this.opts.drawingEnabled = i, this.opts.drawingEnabled ? this.drawBitmap || this.createEmptyDrawing() : (this.clickToSegmentIsGrowing && (this.clickToSegmentIsGrowing = !1, this.refreshDrawing(!0, !1)), this.drawPenLocation = [NaN, NaN, NaN], this.drawPenAxCorSag = -1, this.drawPenFillPts = [], this.drawShapeStartLocation = [NaN, NaN, NaN], this.drawShapePreviewBitmap && (this.drawBitmap = this.drawShapePreviewBitmap, this.drawShapePreviewBitmap = null)), this.drawScene(); + } + /** + * determine color and style of drawing + * @param penValue - sets the color of the pen + * @param isFilledPen - determines if dragging creates flood-filled shape + * @example niivue.setPenValue(1, true) + * @see {@link https://niivue.com/demos/features/draw.ui.html | live demo usage} + */ + setPenValue(i, e = !1) { + this.opts.penValue = i, this.opts.isFilledPen = e, this.drawScene(); + } + /** + * control whether drawing is transparent (0), opaque (1) or translucent (between 0 and 1). + * @param opacity - translucency of drawing + * @example niivue.setDrawOpacity(0.7) + * @see {@link https://niivue.com/demos/features/draw.ui.html | live demo usage} + */ + setDrawOpacity(i) { + this.drawOpacity = i, this.drawScene(); + } + /** + * set the selection box color. A selection box is drawn when you right click and drag to change image contrast + * @param color - an RGBA array. values range from 0 to 1 + * @example + * niivue = new Niivue() + * niivue.setSelectionBoxColor([0, 1, 0, 0.5]) // set to transparent green + * @see {@link https://niivue.com/demos/features/colormaps.html | live demo usage} + */ + setSelectionBoxColor(i) { + this.opts.selectionBoxColor = i; + } + /** + * Update the drawing bounds for this Niivue instance. + * + * @param bounds - [x1, y1, x2, y2] in normalized (0–1) coordinates. + * + * Example: + * nv.setBounds([0,0,0.5,0.5]) // top-left quarter + * nv.setBounds([0.5,0.5,1,1]) // bottom-right quarter + */ + setBounds(i) { + if (!Array.isArray(i) || i.length !== 4) + throw new Error("setBounds: expected [x1,y1,x2,y2] array"); + this.opts.bounds = [ + [i[0], i[1]], + [i[2], i[3]] + ], this.gl && this.drawScene(); + } + /** + * Handles mouse wheel or trackpad scroll to change slices, zoom, or frame depending on context. + * @internal + */ + sliceScroll2D(i, e, t, s = !0) { + if (this.opts.scrollRequiresFocus && this.canvas !== document.activeElement) { + R.warn("Canvas element does not have focus. Scroll events will not be processed."); + return; + } + if (this.inGraphTile(e, t)) { + let r = this.volumes[0].frame4D; + i > 0 && r++, i < 0 && r--, this.setFrame4D(this.volumes[0].id, r); + return; + } + if (i !== 0 && this.opts.dragMode === 3 && this.inRenderTile(this.uiData.dpr * e, this.uiData.dpr * t) === -1) { + let r = this.scene.pan2Dxyzmm[3] * (1 + 10 * i); + r = Math.round(r * 10) / 10; + const a = this.scene.pan2Dxyzmm[3] - r; + this.opts.yoke3Dto2DZoom && (this.scene.volScaleMultiplier = r), this.scene.pan2Dxyzmm[3] = r; + const n = this.frac2mm(this.scene.crosshairPos); + this.scene.pan2Dxyzmm[0] += a * n[0], this.scene.pan2Dxyzmm[1] += a * n[1], this.scene.pan2Dxyzmm[2] += a * n[2], this.drawScene(), this.canvas.focus(), this.sync(); + return; + } + this.mouseClick(e, t, i, s); + } + /** + * set the slice type. This changes the view mode + * @param st - sliceType is an enum of slice types to use + * @example + * niivue = new Niivue() + * niivue.setSliceType(Niivue.sliceTypeMultiplanar) + * @see {@link https://niivue.com/demos/features/basic.multiplanar.html | live demo usage} + */ + setSliceType(i) { + return this.opts.sliceType = i, this.drawScene(), this; + } + /** + * set the opacity of a volume given by volume index + * @param volIdx - the volume index of the volume to change + * @param newOpacity - the opacity value. valid values range from 0 to 1. 0 will effectively remove a volume from the scene + * @example + * niivue = new Niivue() + * niivue.setOpacity(0, 0.5) // make the first volume transparent + * @see {@link https://niivue.com/demos/features/atlas.html | live demo usage} + */ + setOpacity(i, e) { + this.volumes[i].opacity = e, this.updateGLVolume(); + } + /** + * set the scale of the 3D rendering. Larger numbers effectively zoom. + * @param scale - the new scale value + * @example + * niivue.setScale(2) // zoom some + * @see {@link https://niivue.com/demos/features/shiny.volumes.html | live demo usage} + */ + setScale(i) { + this.scene.volScaleMultiplier = i, this.drawScene(); + } + /** + * Set the color of the 3D clip plane. + * @param {number[]} color - An array of RGBA values. + * - **R**, **G**, **B** components range from `0.0` to `1.0`. + * - **A** (alpha) component ranges from `-1.0` to `1.0`, where: + * - `0.0–1.0` → controls background translucency. + * - `-1.0–0.0` → applies translucent shading to the volume instead of the background. + * + * @example + * niivue.setClipPlaneColor([1, 1, 1, 0.5]); // white, translucent background + * niivue.setClipPlaneColor([1, 1, 1, -0.5]); // white, translucent shading + * @see {@link https://niivue.com/demos/features/clipplanes.html | Live demo usage} + */ + setClipPlaneColor(i) { + this.opts.clipPlaneColor = i, this.renderShader.use(this.gl), this.gl.uniform4fv(this.renderShader.uniforms.clipPlaneColor, this.opts.clipPlaneColor), this.drawScene(); + } + /** + * @deprecated This method has been removed. + * Use {@link setClipPlanes} instead, which generalizes clip plane configuration + * @see {@link https://niivue.com/demos/features/clipplanesmulti.html | Multiple clip plane demo} + */ + setClipPlaneThick(i) { + R.warn("setClipPlaneThick() has been removed. use setClipPlanes() instead."); + } + /** + * @deprecated This method has been removed. + * Use {@link setClipPlanes} instead, which generalizes clip plane configuration + * @see {@link https://niivue.com/demos/features/clipplanesmulti.html | Multiple clip plane demo} + */ + setClipVolume(i, e) { + R.warn("setClipVolume() has been removed. use setClipPlanes() instead."); + } + /** + * set proportion of volume rendering influenced by selected matcap. + * @param gradientAmount - amount of matcap (NaN or 0..1), default 0 (matte, surface normal does not influence color). NaN renders the gradients. + * @example + * niivue.setVolumeRenderIllumination(0.6); + * @see {@link https://niivue.com/demos/features/shiny.volumes.html | live demo usage} + * @see {@link https://niivue.com/demos/features/gradient.order.html | live demo usage} + */ + async setVolumeRenderIllumination(i = 0) { + this.renderGradientValues = Number.isNaN(i), this.renderShader = this.renderVolumeShader, this.renderGradientValues ? this.renderShader = this.renderGradientValuesShader : (this.opts.gradientAmount = i, i > 0 || this.opts.gradientOpacity > 0 ? this.renderShader = this.renderGradientShader : i < 0 && (this.renderShader = this.renderSliceShader)), await this.refreshLayers(this.volumes[0], 0), this.initRenderShader(this.renderShader, i), this.renderShader.use(this.gl), this.setClipPlaneColor(this.opts.clipPlaneColor), Number.isNaN(i) ? this.gradientTextureAmount = 1 : this.gradientTextureAmount = i, !(this.volumes.length < 1) && this.drawScene(); + } + /** + * set volume rendering opacity influence of the gradient magnitude + * @param gradientOpacity - amount of gradient magnitude influence on opacity (0..1), default 0 (no-influence) + * @param renderSilhouette - make core transparent to enhance rims (0..1), default 0 (no-influence) + * @example + * niivue.setGradientOpacity(0.6); + * @see {@link https://niivue.com/demos/features/gradient.opacity.html | live demo usage} + */ + async setGradientOpacity(i = 0, e = 0) { + this.opts.gradientOpacity = i, this.opts.renderSilhouette = e, this.renderGradientValues ? this.renderShader = this.renderGradientValuesShader : this.gradientTextureAmount > 0 || i > 0 ? this.renderShader = this.renderGradientShader : this.gradientTextureAmount < 0 && (this.renderShader = this.renderSliceShader), this.initRenderShader(this.renderShader, this.gradientTextureAmount), this.renderShader.use(this.gl), this.gradientTextureAmount > 0 && this.refreshLayers(this.volumes[0], 0), this.drawScene(); + } + /** + * Generates a placeholder RGBA overlay of a green sphere for testing purposes only. + * @internal + * @remarks Marked for future removal — creates a test sphere, not intended for production use. + */ + overlayRGBA(i) { + const e = i.hdr, t = e.dims[1] * e.dims[2] * e.dims[3], s = new Uint8ClampedArray(t * 4), r = 0.2 * Math.min(Math.min(e.dims[1], e.dims[2]), e.dims[3]), a = 0.5 * e.dims[1], n = 0.5 * e.dims[2], o = 0.5 * e.dims[3]; + let l = 0; + for (let c = 0; c < e.dims[3]; c++) + for (let h = 0; h < e.dims[2]; h++) + for (let u = 0; u < e.dims[1]; u++) { + const d = Math.abs(u - a), f = Math.abs(h - n), g = Math.abs(c - o), m = Math.sqrt(d * d + f * f + g * g); + let p = 0; + m < r && (p = 255), s[l++] = 0, s[l++] = p, s[l++] = 0, s[l++] = p * 0.5; + } + return s; + } + /** + * Convert voxel coordinates to millimeters using a transformation matrix. + * @internal + */ + vox2mm(i, e) { + return j.vox2mm(i, e); + } + /** + * clone a volume and return a new volume + * @param index - the index of the volume to clone + * @returns new volume to work with, but that volume is not added to the canvas + * @example + * niivue = new Niivue() + * niivue.cloneVolume(0) + */ + cloneVolume(i) { + return this.volumes[i].clone(); + } + /** + * Loads an NVDocument from a URL and integrates it into the scene. + */ + async loadDocumentFromUrl(i) { + const e = await es.loadFromUrl(i); + await this.loadDocument(e); + } + /** + * Loads an NVDocument + * @returns Niivue instance + * @see {@link https://niivue.com/demos/features/document.load.html | live demo usage} + */ + async loadDocument(i) { + this.volumes = [], this.meshes = [], this.document = i, this.document.labels = this.document.labels ? this.document.labels : []; + const e = { ...Ge, ...i.opts }; + this.scene.pan2Dxyzmm = i.scene.pan2Dxyzmm ? i.scene.pan2Dxyzmm : [0, 0, 0, 1], this.document.opts = e, this.scene.clipPlaneDepthAziElevs && this.setClipPlane(this.scene.clipPlaneDepthAziElevs[this.uiData.activeClipPlaneIndex ?? 0]), R.debug("load document", i), this.mediaUrlMap.clear(), this.createEmptyDrawing(); + const t = i.encodedImageBlobs; + for (let r = 0; r < i.imageOptionsArray.length; r++) { + const a = i.imageOptionsArray[r], n = t[r]; + if (n) { + "colorMap" in a && (a.colormap = a.colorMap); + const o = await Se.loadFromBase64({ base64: n, ...a }); + if (o) { + if (o.colormapLabel) { + const l = Object.keys(o.colormapLabel.lut).length, c = new Uint8ClampedArray(l); + for (const h in o.colormapLabel.lut) + c[h] = o.colormapLabel.lut[h]; + o.colormapLabel.lut = c; + } + this.addVolume(o); + } + } + } + this.volumes.length > 0 && (this.back = this.volumes[0]); + for (const r of i.meshDataObjects ?? []) { + const a = { gl: this.gl, ...r }; + r.offsetPt0 && (a.rgba255[3] = 0, a.tris = new Uint32Array(r.offsetPt0)), R.debug(a); + const n = new kt( + a.pts, + a.tris, + a.name, + a.rgba255, + a.opacity, + a.visible, + this.gl, + a.connectome, + a.dpg, + a.dps, + a.dpv + ); + r.offsetPt0 && (n.fiberGroupColormap = r.fiberGroupColormap, n.fiberColor = r.fiberColor, n.fiberDither = r.fiberDither, n.fiberRadius = r.fiberRadius, n.colormap = r.colormap), n.meshShaderIndex = a.meshShaderIndex, n.layers = a.layers, n.updateMesh(this.gl), R.debug(n), this.addMesh(n); + } + if (i.data.connectomes) + for (const r of i.data.connectomes) { + const a = JSON.parse(r), n = this.loadConnectomeAsMesh(a); + n.updateMesh(this.gl), this.addMesh(n); + } + this.createEmptyDrawing(); + const s = i.encodedDrawingBlob; + if (s) { + const r = await j.b64toUint8(s); + if (r) { + const a = this.back.dims; + let n = a[1] * a[2] * a[3]; + if (r.length - 352 === n && (n += 352), r.length !== n) + throw new Error( + `drawBitmap size does not match the texture dimensions (${a[1]}×${a[2]}×${a[3]}) ${n} != ${a[1] * a[2] * a[3]}.` + ); + this.drawBitmap = r, this.refreshDrawing(); + } + } + return await this.setGradientOpacity(this.opts.gradientOpacity), await this.setVolumeRenderIllumination(this.opts.gradientAmount), this.updateGLVolume(), this.drawScene(), this.onDocumentLoaded(i), this; + } + /** + * generates JavaScript to load the current scene as a document + * @param canvasId - id of canvas NiiVue will be attached to + * @param esm - bundled version of NiiVue + * @example + * const javascript = this.generateLoadDocumentJavaScript("gl1"); + * const html = `