@niivue/niivue 0.40.0 → 0.41.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.js CHANGED
@@ -10,11 +10,10 @@ var __publicField = (obj, key, value) => {
10
10
  };
11
11
 
12
12
  // src/niivue/index.ts
13
- import { Subject } from "rxjs";
14
13
  import { mat4 as mat46, vec2, vec3 as vec310, vec4 as vec47 } from "gl-matrix";
15
14
 
16
15
  // package.json
17
- var version = "0.40.0";
16
+ var version = "0.41.1";
18
17
 
19
18
  // src/logger.ts
20
19
  var _Log = class _Log {
@@ -745,8 +744,109 @@ out vec4 color;` + kDrawFunc + `void main() {
745
744
  if ((isAlphaClipDark) && (background.a == 0.0)) color.a = 0.0; //FSLeyes clipping range
746
745
  vec4 ocolor = vec4(0.0);
747
746
  float overlayAlpha = overlayAlphaShader;
748
- if (overlays > 0.0)
747
+ if (overlays > 0.0) {
749
748
  ocolor = texture(overlay, texPos);
749
+ //dFdx for "boxing" issue 435 has aliasing on some implementations (coarse vs fine)
750
+ //however, this only identifies 50% of the edges due to aliasing effects
751
+ // http://www.aclockworkberry.com/shader-derivative-functions/
752
+ // https://bgolus.medium.com/distinctive-derivative-differences-cce38d36797b
753
+ //if ((ocolor.a >= 1.0) && ((dFdx(ocolor.a) != 0.0) || (dFdy(ocolor.a) != 0.0) ))
754
+ // ocolor.rbg = vec3(0.0, 0.0, 0.0);
755
+ bool isOutlineBelowNotAboveThreshold = true;
756
+ if (isOutlineBelowNotAboveThreshold) {
757
+ if ((overlayOutlineWidth > 0.0) && (ocolor.a < 1.0)) { //check voxel neighbors for edge
758
+ vec3 vx = (overlayOutlineWidth ) / vec3(textureSize(overlay, 0));
759
+ //6 voxel neighbors that share a face
760
+ vec3 vxR = vec3(texPos.x+vx.x, texPos.y, texPos.z);
761
+ vec3 vxL = vec3(texPos.x-vx.x, texPos.y, texPos.z);
762
+ vec3 vxA = vec3(texPos.x, texPos.y+vx.y, texPos.z);
763
+ vec3 vxP = vec3(texPos.x, texPos.y-vx.y, texPos.z);
764
+ vec3 vxS = vec3(texPos.x, texPos.y, texPos.z+vx.z);
765
+ vec3 vxI = vec3(texPos.x, texPos.y, texPos.z-vx.z);
766
+ float a = 0.0;
767
+ if (axCorSag != 2) {
768
+ a = max(a, texture(overlay, vxR).a);
769
+ a = max(a, texture(overlay, vxL).a);
770
+ }
771
+ if (axCorSag != 1) {
772
+ a = max(a, texture(overlay, vxA).a);
773
+ a = max(a, texture(overlay, vxP).a);
774
+ }
775
+ if (axCorSag != 0) {
776
+ a = max(a, texture(overlay, vxS).a);
777
+ a = max(a, texture(overlay, vxI).a);
778
+ }
779
+ bool isCheckCorners = true;
780
+ if (isCheckCorners) {
781
+ //12 voxel neighbors that share an edge
782
+ vec3 vxRA = vec3(texPos.x+vx.x, texPos.y+vx.y, texPos.z);
783
+ vec3 vxLA = vec3(texPos.x-vx.x, texPos.y+vx.y, texPos.z);
784
+ vec3 vxRP = vec3(texPos.x+vx.x, texPos.y-vx.y, texPos.z);
785
+ vec3 vxLP = vec3(texPos.x-vx.x, texPos.y-vx.y, texPos.z);
786
+ vec3 vxRS = vec3(texPos.x+vx.x, texPos.y, texPos.z+vx.z);
787
+ vec3 vxLS = vec3(texPos.x-vx.x, texPos.y, texPos.z+vx.z);
788
+ vec3 vxRI = vec3(texPos.x+vx.x, texPos.y, texPos.z-vx.z);
789
+ vec3 vxLI = vec3(texPos.x-vx.x, texPos.y, texPos.z-vx.z);
790
+ vec3 vxAS = vec3(texPos.x, texPos.y+vx.y, texPos.z+vx.z);
791
+ vec3 vxPS = vec3(texPos.x, texPos.y-vx.y, texPos.z+vx.z);
792
+ vec3 vxAI = vec3(texPos.x, texPos.y+vx.y, texPos.z-vx.z);
793
+ vec3 vxPI = vec3(texPos.x, texPos.y-vx.y, texPos.z-vx.z);
794
+
795
+ if (axCorSag == 0) { //axial corners
796
+ a = max(a, texture(overlay, vxRA).a);
797
+ a = max(a, texture(overlay, vxLA).a);
798
+ a = max(a, texture(overlay, vxRP).a);
799
+ a = max(a, texture(overlay, vxLP).a);
800
+ }
801
+ if (axCorSag == 1) { //coronal corners
802
+ a = max(a, texture(overlay, vxRS).a);
803
+ a = max(a, texture(overlay, vxLS).a);
804
+ a = max(a, texture(overlay, vxRI).a);
805
+ a = max(a, texture(overlay, vxLI).a);
806
+ }
807
+ if (axCorSag == 2) { //sagittal corners
808
+ a = max(a, texture(overlay, vxAS).a);
809
+ a = max(a, texture(overlay, vxPS).a);
810
+ a = max(a, texture(overlay, vxAI).a);
811
+ a = max(a, texture(overlay, vxPI).a);
812
+ }
813
+ }
814
+ if (a >= 1.0) {
815
+ ocolor = vec4(0.0, 0.0, 0.0, 1.0);
816
+ overlayAlpha = 1.0;
817
+ }
818
+ }
819
+
820
+ } else {
821
+ if ((overlayOutlineWidth > 0.0) && (ocolor.a >= 1.0)) { //check voxel neighbors for edge
822
+ vec3 vx = (overlayOutlineWidth ) / vec3(textureSize(overlay, 0));
823
+ vec3 vxR = vec3(texPos.x+vx.x, texPos.y, texPos.z);
824
+ vec3 vxL = vec3(texPos.x-vx.x, texPos.y, texPos.z);
825
+ vec3 vxA = vec3(texPos.x, texPos.y+vx.y, texPos.z);
826
+ vec3 vxP = vec3(texPos.x, texPos.y-vx.y, texPos.z);
827
+ vec3 vxS = vec3(texPos.x, texPos.y, texPos.z+vx.z);
828
+ vec3 vxI = vec3(texPos.x, texPos.y, texPos.z-vx.z);
829
+ float a = 1.0;
830
+ if (axCorSag != 2) {
831
+ a = min(a, texture(overlay, vxR).a);
832
+ a = min(a, texture(overlay, vxL).a);
833
+ }
834
+ if (axCorSag != 1) {
835
+ a = min(a, texture(overlay, vxA).a);
836
+ a = min(a, texture(overlay, vxP).a);
837
+ }
838
+ if (axCorSag != 0) {
839
+ a = min(a, texture(overlay, vxS).a);
840
+ a = min(a, texture(overlay, vxI).a);
841
+ }
842
+ if (a < 1.0) {
843
+ ocolor = vec4(0.0, 0.0, 0.0, 1.0);
844
+ overlayAlpha = 1.0;
845
+ }
846
+ }
847
+ } //outline above threshold
848
+ }
849
+
750
850
  `;
751
851
  var kFragSliceTail = ` ocolor.a *= overlayAlpha;
752
852
  vec4 dcolor = drawColor(texture(drawing, texPos).r, drawOpacity);
@@ -1004,7 +1104,7 @@ uniform float layer;
1004
1104
  uniform highp sampler2D colormap;
1005
1105
  uniform lowp sampler3D blend3D;
1006
1106
  uniform float opacity;
1007
- uniform vec3 xyzFrac;
1107
+ uniform vec4 xyzaFrac;
1008
1108
  uniform mat4 mtx;
1009
1109
  void main(void) {
1010
1110
  vec4 vx = vec4(TexCoord.x, TexCoord.y, coordZ, 1.0) * mtx;
@@ -1012,41 +1112,30 @@ void main(void) {
1012
1112
  FragColor = vec4(0.0, 0.0, 0.0, 0.0);
1013
1113
  if (idx == uint(0))
1014
1114
  return;
1015
- if (xyzFrac.x > 0.0) { //outline
1016
- vx = vec4(TexCoord.x+xyzFrac.x, TexCoord.y, coordZ, 1.0) * mtx;
1115
+ //idx = ((idx - uint(1)) % uint(100))+uint(1);
1116
+ float textureWidth = float(textureSize(colormap, 0).x);
1117
+ float fx = (float(idx)+0.5) / textureWidth;
1118
+ float nlayer = float(textureSize(colormap, 0).y);
1119
+ float y = ((2.0 * layer) + 1.5)/nlayer;
1120
+ FragColor = texture(colormap, vec2(fx, y)).rgba;
1121
+ float alpha = FragColor.a;
1122
+ FragColor.a *= opacity;
1123
+ if (xyzaFrac.a > 0.0) { //outline
1124
+ vx = vec4(TexCoord.x+xyzaFrac.x, TexCoord.y, coordZ, 1.0) * mtx;
1017
1125
  uint R = uint(texture(intensityVol, vx.xyz).r);
1018
- vx = vec4(TexCoord.x-xyzFrac.x, TexCoord.y, coordZ, 1.0) * mtx;
1126
+ vx = vec4(TexCoord.x-xyzaFrac.x, TexCoord.y, coordZ, 1.0) * mtx;
1019
1127
  uint L = uint(texture(intensityVol, vx.xyz).r);
1020
- vx = vec4(TexCoord.x, TexCoord.y+xyzFrac.y, coordZ, 1.0) * mtx;
1128
+ vx = vec4(TexCoord.x, TexCoord.y+xyzaFrac.y, coordZ, 1.0) * mtx;
1021
1129
  uint A = uint(texture(intensityVol, vx.xyz).r);
1022
- vx = vec4(TexCoord.x, TexCoord.y-xyzFrac.y, coordZ, 1.0) * mtx;
1130
+ vx = vec4(TexCoord.x, TexCoord.y-xyzaFrac.y, coordZ, 1.0) * mtx;
1023
1131
  uint P = uint(texture(intensityVol, vx.xyz).r);
1024
- vx = vec4(TexCoord.x, TexCoord.y, coordZ+xyzFrac.z, 1.0) * mtx;
1132
+ vx = vec4(TexCoord.x, TexCoord.y, coordZ+xyzaFrac.z, 1.0) * mtx;
1025
1133
  uint S = uint(texture(intensityVol, vx.xyz).r);
1026
- vx = vec4(TexCoord.x, TexCoord.y, coordZ-xyzFrac.z, 1.0) * mtx;
1134
+ vx = vec4(TexCoord.x, TexCoord.y, coordZ-xyzaFrac.z, 1.0) * mtx;
1027
1135
  uint I = uint(texture(intensityVol, vx.xyz).r);
1028
- if ((idx == R) && (idx == L) && (idx == A) && (idx == P) && (idx == S) && (idx == I))
1029
- return;
1136
+ if ((idx != R) || (idx != L) || (idx != A) || (idx != P) || (idx != S) || (idx != I))
1137
+ FragColor.a = alpha * xyzaFrac.a;
1030
1138
  }
1031
- idx = ((idx - uint(1)) % uint(100))+uint(1);
1032
- float textureWidth = float(textureSize(colormap, 0).x);
1033
- float fx = (float(idx)+0.5) / textureWidth;
1034
- float nlayer = float(textureSize(colormap, 0).y);
1035
- float y = ((2.0 * layer) + 1.5)/nlayer;
1036
- FragColor = texture(colormap, vec2(fx, y)).rgba;
1037
- //FragColor.a *= opacity;
1038
- FragColor.a = opacity;
1039
- return;
1040
-
1041
- if (layer < 2.0) return;
1042
- //vec2 texXY = TexCoord.xy*0.5 +vec2(0.5,0.5);
1043
- //vec4 prevColor = texture(blend3D, vec3(texXY, coordZ));
1044
- vec4 prevColor = texture(blend3D, vec3(TexCoord.xy, coordZ));
1045
- // https://en.wikipedia.org/wiki/Alpha_compositing
1046
- float aout = FragColor.a + (1.0 - FragColor.a) * prevColor.a;
1047
- if (aout <= 0.0) return;
1048
- FragColor.rgb = ((FragColor.rgb * FragColor.a) + (prevColor.rgb * prevColor.a * (1.0 - FragColor.a))) / aout;
1049
- FragColor.a = aout;
1050
1139
  }`;
1051
1140
  var fragOrientShader = `#line 691
1052
1141
  precision highp int;
@@ -1187,28 +1276,28 @@ var fragGrowCutShader = `#version 300 es
1187
1276
  uniform int finalPass;
1188
1277
  uniform float coordZ;
1189
1278
  uniform lowp sampler3D in3D;
1190
- uniform highp isampler3D inputTexture0; // background
1191
- uniform highp isampler3D inputTexture1; // label
1192
- uniform highp isampler3D inputTexture2; // strength
1279
+ uniform highp isampler3D backTex; // background
1280
+ uniform highp isampler3D labelTex; // label
1281
+ uniform highp isampler3D strengthTex; // strength
1193
1282
  void main(void) {
1194
1283
  vec3 interpolatedTextureCoordinate = vec3(TexCoord.xy, coordZ);
1195
- ivec3 size = textureSize(inputTexture0, 0);
1284
+ ivec3 size = textureSize(backTex, 0);
1196
1285
  ivec3 texelIndex = ivec3(floor(interpolatedTextureCoordinate * vec3(size)));
1197
- int background = texelFetch(inputTexture0, texelIndex, 0).r;
1198
- label = texelFetch(inputTexture1, texelIndex, 0).r;
1199
- strength = texelFetch(inputTexture2, texelIndex, 0).r;
1286
+ int background = texelFetch(backTex, texelIndex, 0).r;
1287
+ label = texelFetch(labelTex, texelIndex, 0).r;
1288
+ strength = texelFetch(strengthTex, texelIndex, 0).r;
1200
1289
  for (int k = -1; k <= 1; k++) {
1201
1290
  for (int j = -1; j <= 1; j++) {
1202
1291
  for (int i = -1; i <= 1; i++) {
1203
1292
  if (i != 0 && j != 0 && k != 0) {
1204
1293
  ivec3 neighborIndex = texelIndex + ivec3(i,j,k);
1205
- int neighborBackground = texelFetch(inputTexture0, neighborIndex, 0).r;
1206
- int neighborStrength = texelFetch(inputTexture2, neighborIndex, 0).r;
1294
+ int neighborBackground = texelFetch(backTex, neighborIndex, 0).r;
1295
+ int neighborStrength = texelFetch(strengthTex, neighborIndex, 0).r;
1207
1296
  int strengthCost = abs(neighborBackground - background);
1208
1297
  int takeoverStrength = neighborStrength - strengthCost;
1209
1298
  if (takeoverStrength > strength) {
1210
1299
  strength = takeoverStrength;
1211
- label = texelFetch(inputTexture1, neighborIndex, 0).r;
1300
+ label = texelFetch(labelTex, neighborIndex, 0).r;
1212
1301
  }
1213
1302
  }
1214
1303
  }
@@ -1222,7 +1311,7 @@ void main(void) {
1222
1311
  for (int j = -1; j <= 1; j++)
1223
1312
  for (int i = -1; i <= 1; i++) {
1224
1313
  ivec3 neighborIndex = texelIndex + ivec3(i,j,k);
1225
- int ilabel = texelFetch(inputTexture1, neighborIndex, 0).r;
1314
+ int ilabel = texelFetch(labelTex, neighborIndex, 0).r;
1226
1315
  if ((ilabel < 0) || (ilabel > 3))
1227
1316
  ok = 0;
1228
1317
  else
@@ -1279,9 +1368,11 @@ uniform mat4 mvpMtx;
1279
1368
  uniform mat4 normMtx;
1280
1369
  out vec4 vClr;
1281
1370
  out vec3 vN;
1371
+ out vec4 vP;
1282
1372
  void main(void) {
1283
1373
  vec3 lightPosition = vec3(0.0, 0.0, -10.0);
1284
- gl_Position = mvpMtx * vec4(pos, 1.0);
1374
+ vP = vec4(pos, 1.0);
1375
+ gl_Position = mvpMtx * vP;
1285
1376
  vN = normalize((normMtx * vec4(norm.xyz,1.0)).xyz);
1286
1377
  //vV = -vec3(modelMtx*vec4(pos,1.0));
1287
1378
  vClr = clr;
@@ -2825,8 +2916,8 @@ __publicField(_NiivueObject3D, "CULL_FACE", 2);
2825
2916
  __publicField(_NiivueObject3D, "CULL_FRONT", 4);
2826
2917
  __publicField(_NiivueObject3D, "CULL_BACK", 8);
2827
2918
  __publicField(_NiivueObject3D, "ENABLE_DEPTH_TEST", 16);
2828
- __publicField(_NiivueObject3D, "generateCrosshairs", function(gl, id, xyzMM, xyzMin, xyzMax, radius, sides = 20) {
2829
- const geometry = _NiivueObject3D.generateCrosshairsGeometry(gl, xyzMM, xyzMin, xyzMax, radius, sides);
2919
+ __publicField(_NiivueObject3D, "generateCrosshairs", function(gl, id, xyzMM, xyzMin, xyzMax, radius, sides = 20, gap = 0) {
2920
+ const geometry = _NiivueObject3D.generateCrosshairsGeometry(gl, xyzMM, xyzMin, xyzMax, radius, sides, gap);
2830
2921
  return new _NiivueObject3D(
2831
2922
  id,
2832
2923
  geometry.vertexBuffer,
@@ -2837,18 +2928,40 @@ __publicField(_NiivueObject3D, "generateCrosshairs", function(gl, id, xyzMM, xyz
2837
2928
  );
2838
2929
  });
2839
2930
  // not included in public docs
2840
- __publicField(_NiivueObject3D, "generateCrosshairsGeometry", function(gl, xyzMM, xyzMin, xyzMax, radius, sides = 20) {
2931
+ __publicField(_NiivueObject3D, "generateCrosshairsGeometry", function(gl, xyzMM, xyzMin, xyzMax, radius, sides = 20, gap = 0) {
2841
2932
  const vertices = [];
2842
2933
  const indices = [];
2843
- let start = vec3.fromValues(xyzMin[0], xyzMM[1], xyzMM[2]);
2844
- let dest = vec3.fromValues(xyzMax[0], xyzMM[1], xyzMM[2]);
2845
- _NiivueObject3D.makeCylinder(vertices, indices, start, dest, radius, sides);
2846
- start = vec3.fromValues(xyzMM[0], xyzMin[1], xyzMM[2]);
2847
- dest = vec3.fromValues(xyzMM[0], xyzMax[1], xyzMM[2]);
2848
- _NiivueObject3D.makeCylinder(vertices, indices, start, dest, radius, sides);
2849
- start = vec3.fromValues(xyzMM[0], xyzMM[1], xyzMin[2]);
2850
- dest = vec3.fromValues(xyzMM[0], xyzMM[1], xyzMax[2]);
2851
- _NiivueObject3D.makeCylinder(vertices, indices, start, dest, radius, sides);
2934
+ const gapX = radius * gap;
2935
+ if (gapX <= 0) {
2936
+ let start = vec3.fromValues(xyzMin[0], xyzMM[1], xyzMM[2]);
2937
+ let dest = vec3.fromValues(xyzMax[0], xyzMM[1], xyzMM[2]);
2938
+ _NiivueObject3D.makeCylinder(vertices, indices, start, dest, radius, sides);
2939
+ start = vec3.fromValues(xyzMM[0], xyzMin[1], xyzMM[2]);
2940
+ dest = vec3.fromValues(xyzMM[0], xyzMax[1], xyzMM[2]);
2941
+ _NiivueObject3D.makeCylinder(vertices, indices, start, dest, radius, sides);
2942
+ start = vec3.fromValues(xyzMM[0], xyzMM[1], xyzMin[2]);
2943
+ dest = vec3.fromValues(xyzMM[0], xyzMM[1], xyzMax[2]);
2944
+ _NiivueObject3D.makeCylinder(vertices, indices, start, dest, radius, sides);
2945
+ } else {
2946
+ let start = vec3.fromValues(xyzMin[0], xyzMM[1], xyzMM[2]);
2947
+ let dest = vec3.fromValues(xyzMM[0] - gapX, xyzMM[1], xyzMM[2]);
2948
+ _NiivueObject3D.makeCylinder(vertices, indices, start, dest, radius, sides, false);
2949
+ start = vec3.fromValues(xyzMM[0] + gapX, xyzMM[1], xyzMM[2]);
2950
+ dest = vec3.fromValues(xyzMax[0], xyzMM[1], xyzMM[2]);
2951
+ _NiivueObject3D.makeCylinder(vertices, indices, start, dest, radius, sides, false);
2952
+ start = vec3.fromValues(xyzMM[0], xyzMin[1], xyzMM[2]);
2953
+ dest = vec3.fromValues(xyzMM[0], xyzMM[1] - gapX, xyzMM[2]);
2954
+ _NiivueObject3D.makeCylinder(vertices, indices, start, dest, radius, sides, false);
2955
+ start = vec3.fromValues(xyzMM[0], xyzMM[1] + gapX, xyzMM[2]);
2956
+ dest = vec3.fromValues(xyzMM[0], xyzMax[1], xyzMM[2]);
2957
+ _NiivueObject3D.makeCylinder(vertices, indices, start, dest, radius, sides, false);
2958
+ start = vec3.fromValues(xyzMM[0], xyzMM[1], xyzMin[2]);
2959
+ dest = vec3.fromValues(xyzMM[0], xyzMM[1], xyzMM[2] - gapX);
2960
+ _NiivueObject3D.makeCylinder(vertices, indices, start, dest, radius, sides, false);
2961
+ start = vec3.fromValues(xyzMM[0], xyzMM[1], xyzMM[2] + gapX);
2962
+ dest = vec3.fromValues(xyzMM[0], xyzMM[1], xyzMax[2]);
2963
+ _NiivueObject3D.makeCylinder(vertices, indices, start, dest, radius, sides, false);
2964
+ }
2852
2965
  const vertexBuffer = gl.createBuffer();
2853
2966
  if (vertexBuffer === null) {
2854
2967
  throw new Error("could not instantiate vertex buffer");
@@ -11044,7 +11157,7 @@ var ColorTables = class {
11044
11157
  const cmap = this.colormapFromKey(key);
11045
11158
  return this.makeLut(cmap.R, cmap.G, cmap.B, cmap.A, cmap.I, isInvert);
11046
11159
  }
11047
- makeLabelLut(cm, alphaFill = 64) {
11160
+ makeLabelLut(cm, alphaFill = 255) {
11048
11161
  if (cm.R === void 0 || cm.G === void 0 || cm.B === void 0) {
11049
11162
  throw new Error(`Invalid colormap table: ${cm}`);
11050
11163
  }
@@ -11383,11 +11496,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
11383
11496
  const offsetPt0 = [];
11384
11497
  offsetPt0.push(npt);
11385
11498
  const pts = [];
11386
- const dps = [];
11387
- dps.push({
11388
- id: "tract",
11389
- vals: []
11390
- });
11499
+ const bundleTags = [];
11391
11500
  for (let t = 0; t < n_tracts; t++) {
11392
11501
  line = readStr();
11393
11502
  const new_tracts = readNumericTag("ni_dimen=");
@@ -11407,13 +11516,18 @@ var NVMeshLoaders = class _NVMeshLoaders {
11407
11516
  }
11408
11517
  npt += new_pts;
11409
11518
  offsetPt0.push(npt);
11410
- dps[0].vals.push(bundleTag);
11519
+ bundleTags.push(bundleTag);
11411
11520
  }
11412
11521
  line = readStr();
11413
11522
  }
11523
+ const dps = [];
11524
+ dps.push({
11525
+ id: "tract",
11526
+ vals: Float32Array.from(bundleTags)
11527
+ });
11414
11528
  return {
11415
- pts,
11416
- offsetPt0,
11529
+ pts: new Float32Array(pts),
11530
+ offsetPt0: new Uint32Array(offsetPt0),
11417
11531
  dps
11418
11532
  };
11419
11533
  }
@@ -11502,8 +11616,8 @@ var NVMeshLoaders = class _NVMeshLoaders {
11502
11616
  }
11503
11617
  return mat2;
11504
11618
  }
11505
- let offsetPt0 = [];
11506
- let pts = [];
11619
+ let offsetPt0 = new Uint32Array(0);
11620
+ let pts = new Float32Array(0);
11507
11621
  const mat = readMatV4(buffer);
11508
11622
  if (!("trans_to_mni" in mat)) {
11509
11623
  throw new Error("TT format file must have 'trans_to_mni'");
@@ -11535,8 +11649,6 @@ var NVMeshLoaders = class _NVMeshLoaders {
11535
11649
  m[15]
11536
11650
  );
11537
11651
  mat42.transpose(trans_to_mni, trans_to_mni);
11538
- const zoomMat = mat42.create();
11539
- mat42.transpose(zoomMat, zoomMat);
11540
11652
  function parse_tt(track) {
11541
11653
  const dv = new DataView(track.buffer);
11542
11654
  const pos = [];
@@ -11574,12 +11686,10 @@ var NVMeshLoaders = class _NVMeshLoaders {
11574
11686
  for (let i2 = 0; i2 < npt; i2++) {
11575
11687
  pts[i2] = pts[i2] / 32;
11576
11688
  }
11577
- const vox2mmMat = mat42.create();
11578
- mat42.mul(vox2mmMat, trans_to_mni, zoomMat);
11579
11689
  let v = 0;
11580
11690
  for (let i2 = 0; i2 < npt / 3; i2++) {
11581
11691
  const pos2 = vec42.fromValues(pts[v], pts[v + 1], pts[v + 2], 1);
11582
- vec42.transformMat4(pos2, pos2, vox2mmMat);
11692
+ vec42.transformMat4(pos2, pos2, trans_to_mni);
11583
11693
  pts[v++] = pos2[0];
11584
11694
  pts[v++] = pos2[1];
11585
11695
  pts[v++] = pos2[2];
@@ -11603,7 +11713,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
11603
11713
  }
11604
11714
  let noff = 0;
11605
11715
  let npt = 0;
11606
- let pts = [];
11716
+ let pts = new Float32Array([]);
11607
11717
  const offsetPt0 = [];
11608
11718
  const dpg = [];
11609
11719
  const dps = [];
@@ -11678,31 +11788,21 @@ var NVMeshLoaders = class _NVMeshLoaders {
11678
11788
  if (pname.includes("groups")) {
11679
11789
  dpg.push({
11680
11790
  id: tag,
11681
- vals: Array.from(vals.slice())
11791
+ vals: Float32Array.from(vals.slice())
11682
11792
  });
11683
11793
  continue;
11684
11794
  }
11685
11795
  if (pname.includes("dpv")) {
11686
- let mn = vals[0];
11687
- let mx = vals[0];
11688
- for (let i2 = 1; i2 < vals.length; i2++) {
11689
- mn = Math.min(vals[i2], mn);
11690
- mx = Math.max(vals[i2], mx);
11691
- }
11692
11796
  dpv.push({
11693
11797
  id: tag,
11694
- vals: Array.from(vals.slice()),
11695
- global_min: mn,
11696
- global_max: mx,
11697
- cal_min: mn,
11698
- cal_max: mx
11798
+ vals: Float32Array.from(vals.slice())
11699
11799
  });
11700
11800
  continue;
11701
11801
  }
11702
11802
  if (pname.includes("dps")) {
11703
11803
  dps.push({
11704
11804
  id: tag,
11705
- vals: Array.from(vals.slice())
11805
+ vals: Float32Array.from(vals.slice())
11706
11806
  });
11707
11807
  continue;
11708
11808
  }
@@ -11714,7 +11814,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
11714
11814
  }
11715
11815
  if (fname.startsWith("positions.3.")) {
11716
11816
  npt = nval;
11717
- pts = Array.from(vals.slice());
11817
+ pts = new Float32Array(vals);
11718
11818
  }
11719
11819
  }
11720
11820
  if (noff === 0 || npt === 0) {
@@ -11725,8 +11825,8 @@ var NVMeshLoaders = class _NVMeshLoaders {
11725
11825
  }
11726
11826
  offsetPt0[noff] = npt / 3;
11727
11827
  return {
11728
- pts: Array.from(pts),
11729
- offsetPt0: Array.from(offsetPt0),
11828
+ pts,
11829
+ offsetPt0: new Uint32Array(offsetPt0),
11730
11830
  dpg,
11731
11831
  dps,
11732
11832
  dpv,
@@ -11886,23 +11986,16 @@ var NVMeshLoaders = class _NVMeshLoaders {
11886
11986
  if (vers > 2 || hdr_sz !== 1e3 || magic !== 1128354388) {
11887
11987
  throw new Error("Not a valid TRK file");
11888
11988
  }
11889
- const dps = [];
11890
- const dpv = [];
11891
11989
  const n_scalars = reader.getInt16(36, true);
11892
- if (n_scalars > 0) {
11893
- for (let i2 = 0; i2 < n_scalars; i2++) {
11894
- const arr = new Uint8Array(buffer.slice(38 + i2 * 20, 58 + i2 * 20));
11895
- const str = new TextDecoder().decode(arr).split("\0").shift();
11896
- dpv.push({
11897
- id: str.trim(),
11898
- // TODO can we guarantee this?
11899
- vals: [],
11900
- global_min: 0,
11901
- global_max: 0,
11902
- cal_min: 0,
11903
- cal_max: 0
11904
- });
11905
- }
11990
+ const dpv = [];
11991
+ for (let i2 = 0; i2 < n_scalars; i2++) {
11992
+ const arr = new Uint8Array(buffer.slice(38 + i2 * 20, 58 + i2 * 20));
11993
+ const str = new TextDecoder().decode(arr).split("\0").shift();
11994
+ dpv.push({
11995
+ id: str.trim(),
11996
+ // TODO can we guarantee this?
11997
+ vals: []
11998
+ });
11906
11999
  }
11907
12000
  const voxel_sizeX = reader.getFloat32(12, true);
11908
12001
  const voxel_sizeY = reader.getFloat32(16, true);
@@ -11926,16 +12019,15 @@ var NVMeshLoaders = class _NVMeshLoaders {
11926
12019
  1
11927
12020
  );
11928
12021
  const n_properties = reader.getInt16(238, true);
11929
- if (n_properties > 0) {
11930
- for (let i2 = 0; i2 < n_properties; i2++) {
11931
- const arr = new Uint8Array(buffer.slice(240 + i2 * 20, 260 + i2 * 20));
11932
- const str = new TextDecoder().decode(arr).split("\0").shift();
11933
- dps.push({
11934
- id: str.trim(),
11935
- // TODO can we guarantee this?
11936
- vals: []
11937
- });
11938
- }
12022
+ const dps = [];
12023
+ for (let i2 = 0; i2 < n_properties; i2++) {
12024
+ const arr = new Uint8Array(buffer.slice(240 + i2 * 20, 260 + i2 * 20));
12025
+ const str = new TextDecoder().decode(arr).split("\0").shift();
12026
+ dps.push({
12027
+ id: str.trim(),
12028
+ // TODO can we guarantee this?
12029
+ vals: []
12030
+ });
11939
12031
  }
11940
12032
  const mat = mat42.create();
11941
12033
  for (let i2 = 0; i2 < 16; i2++) {
@@ -11947,9 +12039,6 @@ var NVMeshLoaders = class _NVMeshLoaders {
11947
12039
  }
11948
12040
  const vox2mmMat = mat42.create();
11949
12041
  mat42.mul(vox2mmMat, zoomMat, mat);
11950
- vox2mmMat[3] = mat[3];
11951
- vox2mmMat[7] = mat[7];
11952
- vox2mmMat[11] = mat[11];
11953
12042
  let i32 = null;
11954
12043
  let f32 = null;
11955
12044
  i32 = new Int32Array(buffer.slice(hdr_sz));
@@ -11991,15 +12080,19 @@ var NVMeshLoaders = class _NVMeshLoaders {
11991
12080
  }
11992
12081
  }
11993
12082
  }
11994
- if (n_scalars > 0) {
11995
- for (let s = 0; s < n_scalars; s++) {
11996
- const mn = dpv[s].vals.reduce((acc, current) => Math.min(acc, current));
11997
- const mx = dpv[s].vals.reduce((acc, current) => Math.max(acc, current));
11998
- dpv[s].global_min = mn;
11999
- dpv[s].global_max = mx;
12000
- dpv[s].cal_min = mn;
12001
- dpv[s].cal_max = mx;
12002
- }
12083
+ const dps32 = [];
12084
+ for (let i2 = 0; i2 < dps.length; i2++) {
12085
+ dps32.push({
12086
+ id: dps[i2].id,
12087
+ vals: Float32Array.from(dps[i2].vals)
12088
+ });
12089
+ }
12090
+ const dpv32 = [];
12091
+ for (let s = 0; s < dpv.length; s++) {
12092
+ dpv32.push({
12093
+ id: dpv[i].id,
12094
+ vals: Float32Array.from(dpv[i].vals)
12095
+ });
12003
12096
  }
12004
12097
  offsetPt0[noffset++] = npt;
12005
12098
  pts = pts.slice(0, npt3);
@@ -12007,8 +12100,8 @@ var NVMeshLoaders = class _NVMeshLoaders {
12007
12100
  return {
12008
12101
  pts,
12009
12102
  offsetPt0,
12010
- dps,
12011
- dpv
12103
+ dps: dps32,
12104
+ dpv: dpv32
12012
12105
  };
12013
12106
  }
12014
12107
  // readTRK()
@@ -12166,7 +12259,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12166
12259
  } else {
12167
12260
  throw new Error("Unsupported ASCII VTK datatype " + items[0]);
12168
12261
  }
12169
- const indices = new Int32Array(tris);
12262
+ const indices = new Uint32Array(tris);
12170
12263
  return {
12171
12264
  positions,
12172
12265
  indices
@@ -12174,7 +12267,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12174
12267
  }
12175
12268
  // readTxtVTK()
12176
12269
  // read mesh overlay to influence vertex colors
12177
- static readLayer(name = "", buffer, nvmesh, opacity = 0.5, colormap = "warm", colormapNegative = "winter", useNegativeCmap = false, cal_min = null, cal_max = null, isOutlineBorder = false) {
12270
+ static readLayer(name = "", buffer, nvmesh, opacity = 0.5, colormap = "warm", colormapNegative = "winter", useNegativeCmap = false, cal_min = null, cal_max = null, outlineBorder = 0) {
12178
12271
  const layer = {
12179
12272
  colormapInvert: false,
12180
12273
  alphaThreshold: false,
@@ -12212,7 +12305,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12212
12305
  const mx2 = vals.reduce((acc, current) => Math.max(acc, current));
12213
12306
  nvmesh.dpv.push({
12214
12307
  id: tag,
12215
- vals: Array.from(vals.slice()),
12308
+ vals: Float32Array.from(vals.slice()),
12216
12309
  global_min: mn2,
12217
12310
  global_max: mx2,
12218
12311
  cal_min: mn2,
@@ -12271,7 +12364,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12271
12364
  }
12272
12365
  layer.nFrame4D = layer.values.length / n_vert;
12273
12366
  layer.frame4D = 0;
12274
- layer.isOutlineBorder = isOutlineBorder;
12367
+ layer.outlineBorder = outlineBorder;
12275
12368
  let mn = layer.values[0];
12276
12369
  let mx = layer.values[0];
12277
12370
  for (let i = 0; i < layer.values.length; i++) {
@@ -12624,7 +12717,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12624
12717
  }
12625
12718
  if (ntri < 1) {
12626
12719
  ntri = parseInt(items[0]);
12627
- indices = new Int32Array(ntri * 3);
12720
+ indices = new Uint32Array(ntri * 3);
12628
12721
  continue;
12629
12722
  }
12630
12723
  if (t >= ntri * 3) {
@@ -12680,7 +12773,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12680
12773
  positions[j + 2] = parseFloat(items[2]);
12681
12774
  j += 3;
12682
12775
  }
12683
- const indices = new Int32Array(ntri * 3);
12776
+ const indices = new Uint32Array(ntri * 3);
12684
12777
  j = 0;
12685
12778
  for (let i = 0; i < ntri; i++) {
12686
12779
  line = readStr();
@@ -12862,7 +12955,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12862
12955
  } else {
12863
12956
  throw new Error("Unsupported binary VTK datatype " + items[0]);
12864
12957
  }
12865
- const indices = new Int32Array(tris);
12958
+ const indices = new Uint32Array(tris);
12866
12959
  return {
12867
12960
  positions,
12868
12961
  indices
@@ -12883,7 +12976,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12883
12976
  const nvert = reader.getUint32(28, true);
12884
12977
  const vcoffset = reader.getUint32(48, true);
12885
12978
  let pos = hdrBytes;
12886
- const indices = new Int32Array(buffer, pos, nface * 3);
12979
+ const indices = new Uint32Array(buffer, pos, nface * 3);
12887
12980
  pos += nface * 3 * 4;
12888
12981
  const positions = new Float32Array(buffer, pos, nvert * 3);
12889
12982
  for (let i = 0; i < nvert * 3; i += 3) {
@@ -12941,7 +13034,11 @@ var NVMeshLoaders = class _NVMeshLoaders {
12941
13034
  isSCALAR = true;
12942
13035
  }
12943
13036
  if (isSCALAR) {
12944
- const FSizeWoScalars = 16 + nskip + (isFace ? 1 : 0) * nface * 12 + (isVert ? 1 : 0) * n_vert * 12 + (isRGBA ? 1 : 0) * n_vert * 4;
13037
+ let nv = n_vert;
13038
+ if (n_vert === 0) {
13039
+ nv = nvert;
13040
+ }
13041
+ const FSizeWoScalars = 16 + nskip + (isFace ? 1 : 0) * nface * 12 + (isVert ? 1 : 0) * nv * 12 + (isRGBA ? 1 : 0) * nv * 4;
12945
13042
  const scalarFloats = Math.floor((_buffer.byteLength - FSizeWoScalars) / bytesPerScalar);
12946
13043
  if (nvert !== n_vert && scalarFloats % n_vert === 0) {
12947
13044
  log.warn("Issue 729: mz3 mismatch scalar NVERT does not match mesh NVERT");
@@ -12962,7 +13059,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12962
13059
  let filepos = 16 + nskip;
12963
13060
  let indices = null;
12964
13061
  if (isFace) {
12965
- indices = new Int32Array(_buffer, filepos, nface * 3);
13062
+ indices = new Uint32Array(_buffer, filepos, nface * 3);
12966
13063
  filepos += nface * 3 * 4;
12967
13064
  }
12968
13065
  let positions = null;
@@ -13116,7 +13213,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13116
13213
  positions2[v + 2] = parseFloat(items[2]);
13117
13214
  v += 3;
13118
13215
  }
13119
- let indices2 = new Int32Array(nface * 3);
13216
+ let indices2 = new Uint32Array(nface * 3);
13120
13217
  let f = 0;
13121
13218
  for (let i = 0; i < nface; i++) {
13122
13219
  line = readStr();
@@ -13126,7 +13223,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13126
13223
  break;
13127
13224
  }
13128
13225
  if (f + nTri * 3 > indices2.length) {
13129
- const c = new Int32Array(indices2.length + indices2.length);
13226
+ const c = new Uint32Array(indices2.length + indices2.length);
13130
13227
  c.set(indices2);
13131
13228
  indices2 = c.slice();
13132
13229
  }
@@ -13176,7 +13273,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13176
13273
  pos += vertStride;
13177
13274
  }
13178
13275
  }
13179
- const indices = new Int32Array(nface * 3);
13276
+ const indices = new Uint32Array(nface * 3);
13180
13277
  let isTriangular = true;
13181
13278
  let j = 0;
13182
13279
  if (indexCountBytes === 1 && indexBytes === 4 && indexStrideBytes === 13) {
@@ -13267,7 +13364,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13267
13364
  header = lines[line].trim().split(/\s+/);
13268
13365
  line++;
13269
13366
  const num_f = parseInt(header[0]);
13270
- const indices = new Int32Array(num_f * 3);
13367
+ const indices = new Uint32Array(num_f * 3);
13271
13368
  for (let i = 0; i < num_f; i++) {
13272
13369
  const items = lines[line].trim().split(/\s+/);
13273
13370
  line++;
@@ -13352,7 +13449,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13352
13449
  }
13353
13450
  }
13354
13451
  const positions = new Float32Array(pts);
13355
- const indices = new Int32Array(t);
13452
+ const indices = new Uint32Array(t);
13356
13453
  return {
13357
13454
  positions,
13358
13455
  indices
@@ -13398,16 +13495,58 @@ var NVMeshLoaders = class _NVMeshLoaders {
13398
13495
  i++;
13399
13496
  }
13400
13497
  const positions = new Float32Array(pts);
13401
- const indices = new Int32Array(t);
13498
+ const indices = new Uint32Array(t);
13402
13499
  return {
13403
13500
  positions,
13404
13501
  indices
13405
13502
  };
13406
13503
  }
13407
13504
  // readOFF()
13505
+ static readOBJMNI(buffer) {
13506
+ const enc = new TextDecoder("utf-8");
13507
+ const txt = enc.decode(buffer);
13508
+ const items = txt.trim().split(/\s*,\s*|\s+/);
13509
+ if (items.length < 1 || items[0] !== "P") {
13510
+ log.warn("This is not a valid MNI OBJ mesh.");
13511
+ }
13512
+ let j = 6;
13513
+ const nVert = parseInt(items[j++]);
13514
+ const nVertX3 = nVert * 3;
13515
+ const positions = new Float32Array(nVertX3);
13516
+ for (let i = 0; i < nVertX3; i++) {
13517
+ positions[i] = parseFloat(items[j++]);
13518
+ }
13519
+ j += nVertX3;
13520
+ const nTri = parseInt(items[j++]);
13521
+ const colour_flag = parseInt(items[j++]);
13522
+ if (nTri < 1 || colour_flag < 0 || colour_flag > 2) {
13523
+ log.warn("This is not a valid MNI OBJ mesh.");
13524
+ }
13525
+ let num_c = 1;
13526
+ if (colour_flag === 1) {
13527
+ num_c = nTri;
13528
+ } else if (colour_flag === 1) {
13529
+ num_c = nVert;
13530
+ }
13531
+ j += num_c * 4;
13532
+ j += nTri;
13533
+ const nTriX3 = nTri * 3;
13534
+ const indices = new Uint32Array(nTriX3);
13535
+ for (let i = 0; i < nTriX3; i++) {
13536
+ indices[i] = parseInt(items[j++]);
13537
+ }
13538
+ return {
13539
+ positions,
13540
+ indices
13541
+ };
13542
+ }
13543
+ // readOBJMNI()
13408
13544
  static readOBJ(buffer) {
13409
13545
  const enc = new TextDecoder("utf-8");
13410
13546
  const txt = enc.decode(buffer);
13547
+ if (txt[0] === "P") {
13548
+ return this.readOBJMNI(buffer);
13549
+ }
13411
13550
  const lines = txt.split("\n");
13412
13551
  const n = lines.length;
13413
13552
  const pts = [];
@@ -13441,7 +13580,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13441
13580
  }
13442
13581
  }
13443
13582
  const positions = new Float32Array(pts);
13444
- const indices = new Int32Array(t);
13583
+ const indices = new Uint32Array(t);
13445
13584
  return {
13446
13585
  positions,
13447
13586
  indices
@@ -13476,7 +13615,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13476
13615
  offset += 4;
13477
13616
  }
13478
13617
  nf *= 3;
13479
- const indices = new Int32Array(nf);
13618
+ const indices = new Uint32Array(nf);
13480
13619
  for (let i = 0; i < nf; i++) {
13481
13620
  indices[i] = view.getUint32(offset, false);
13482
13621
  offset += 4;
@@ -13593,7 +13732,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13593
13732
  const nNearest = reader.getUint32(pos, true);
13594
13733
  pos += 4 + 4 * nNearest;
13595
13734
  }
13596
- const indices = new Int32Array(nTri * 3);
13735
+ const indices = new Uint32Array(nTri * 3);
13597
13736
  for (let i = 0; i < nTri * 3; i++) {
13598
13737
  indices[i] = reader.getInt32(pos, true);
13599
13738
  pos += 4;
@@ -13632,7 +13771,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13632
13771
  throw new Error("Unable to parse ASCII STL file.");
13633
13772
  }
13634
13773
  const positions = new Float32Array(pts);
13635
- const indices = new Int32Array(npts);
13774
+ const indices = new Uint32Array(npts);
13636
13775
  for (let i = 0; i < npts; i++) {
13637
13776
  indices[i] = i;
13638
13777
  }
@@ -13658,7 +13797,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13658
13797
  if (buffer.byteLength < 80 + 4 + ntri * 50) {
13659
13798
  throw new Error("STL file too small to store triangles = " + ntri);
13660
13799
  }
13661
- const indices = new Int32Array(ntri3);
13800
+ const indices = new Uint32Array(ntri3);
13662
13801
  const positions = new Float32Array(ntri3 * 3);
13663
13802
  let pos = 80 + 4 + 12;
13664
13803
  let v = 0;
@@ -13753,7 +13892,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13753
13892
  let indexCount = 0;
13754
13893
  let surfaceNumberOfVertices = 0;
13755
13894
  let brainStructure = "";
13756
- let vertexIndices = new Int32Array();
13895
+ let vertexIndices = new Uint32Array();
13757
13896
  const bytes = new Uint8Array(buffer);
13758
13897
  let pos = 552;
13759
13898
  let line;
@@ -13803,7 +13942,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13803
13942
  if (items.length < indexCount) {
13804
13943
  log.error("Error parsing VertexIndices");
13805
13944
  }
13806
- vertexIndices = new Int32Array(indexCount);
13945
+ vertexIndices = new Uint32Array(indexCount);
13807
13946
  for (let i = 0; i < indexCount; i++) {
13808
13947
  vertexIndices[i] = parseInt(items[i]);
13809
13948
  }
@@ -14099,33 +14238,20 @@ var NVMeshLoaders = class _NVMeshLoaders {
14099
14238
  static readX3D(buffer) {
14100
14239
  const len = buffer.byteLength;
14101
14240
  if (len < 20) {
14102
- throw new Error("File too small to be GII: bytes = " + len);
14241
+ throw new Error("File too small to be X3D: bytes = " + len);
14103
14242
  }
14104
14243
  const bytes = new Uint8Array(buffer);
14105
14244
  let pos = 0;
14106
- function readStrX() {
14107
- while (pos < len && bytes[pos] === 10) {
14245
+ function readStr() {
14246
+ while (pos < len && bytes[pos] !== 60) {
14108
14247
  pos++;
14109
14248
  }
14110
- const startPos = pos;
14111
- while (pos < len && bytes[pos] !== 10) {
14249
+ const startP = pos;
14250
+ while (pos < len && bytes[pos] !== 62) {
14112
14251
  pos++;
14113
14252
  }
14114
- pos++;
14115
- if (pos - startPos < 1) {
14116
- return "";
14117
- }
14118
- return new TextDecoder().decode(buffer.slice(startPos, pos - 1)).trim();
14119
- }
14120
- function readStr() {
14121
- let line2 = readStrX();
14122
- if (!line2.startsWith("<") || line2.endsWith(">")) {
14123
- return line2;
14124
- }
14125
- while (pos < len && !line2.endsWith(">")) {
14126
- line2 += readStrX();
14127
- }
14128
- return line2;
14253
+ const endP = pos;
14254
+ return new TextDecoder().decode(buffer.slice(startP, endP + 1)).trim();
14129
14255
  }
14130
14256
  let line = readStr();
14131
14257
  function readStringTag(TagName) {
@@ -14146,22 +14272,30 @@ var NVMeshLoaders = class _NVMeshLoaders {
14146
14272
  const delimiter = line[fpos + TagName.length + 1];
14147
14273
  const spos = line.indexOf(delimiter, fpos) + 1;
14148
14274
  const epos = line.indexOf(delimiter, spos);
14149
- const str = line.slice(spos, epos).trim();
14150
- const items = str.trim().split(/\s+/);
14275
+ let str = line.slice(spos, epos).trim();
14276
+ str = str.replace(/,\s*$/, "");
14277
+ const items = str.trim().split(/\s*,\s*|\s+/);
14151
14278
  if (items.length < 2) {
14152
14279
  return parseFloat(str);
14153
14280
  }
14154
- const ret = [];
14281
+ let ret = new Array(items.length);
14282
+ let j = 0;
14155
14283
  for (let i = 0; i < items.length; i++) {
14156
- ret.push(parseFloat(items[i]));
14284
+ const v = parseFloat(items[i]);
14285
+ if (!isFinite(v)) {
14286
+ continue;
14287
+ }
14288
+ ret[j] = v;
14289
+ j++;
14157
14290
  }
14291
+ ret = ret.slice(0, j);
14158
14292
  return ret;
14159
14293
  }
14160
14294
  if (!line.includes("xml version")) {
14161
14295
  log.warn("Not a X3D image");
14162
14296
  }
14163
14297
  let positions = [];
14164
- const indices = [];
14298
+ let indices = [];
14165
14299
  let rgba255 = [];
14166
14300
  let color = [];
14167
14301
  let translation = [0, 0, 0, 0];
@@ -14239,12 +14373,20 @@ var NVMeshLoaders = class _NVMeshLoaders {
14239
14373
  height = -2;
14240
14374
  coordIndex = readNumericTag("coordIndex");
14241
14375
  }
14376
+ if (line.startsWith("<IndexedTriangleSet")) {
14377
+ height = -7;
14378
+ coordIndex = readNumericTag("index");
14379
+ }
14242
14380
  if (line.startsWith("<IndexedTriangleStripSet")) {
14243
14381
  height = -3;
14244
14382
  coordIndex = readNumericTag("index");
14245
14383
  }
14246
14384
  if (line.startsWith("<Coordinate")) {
14247
14385
  point = readNumericTag("point");
14386
+ const rem = point.length % 3;
14387
+ if (rem !== 0) {
14388
+ point = point.slice(0, -rem);
14389
+ }
14248
14390
  }
14249
14391
  if (line.startsWith("<Color")) {
14250
14392
  color = readNumericTag("color");
@@ -14262,7 +14404,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
14262
14404
  log.warn("Unsupported x3d shape: ElevationGrid");
14263
14405
  }
14264
14406
  }
14265
- if (height < -3) {
14407
+ if (height < -3 && height !== -7) {
14266
14408
  } else if (height < -1) {
14267
14409
  if (coordIndex.length < 1 || point.length < 3 || point.length === void 0) {
14268
14410
  log.warn("Indexed mesh must specify indices and points");
@@ -14270,7 +14412,9 @@ var NVMeshLoaders = class _NVMeshLoaders {
14270
14412
  }
14271
14413
  const idx0 = Math.floor(positions.length / 3);
14272
14414
  let j = 2;
14273
- if (height === -2) {
14415
+ if (height === -7) {
14416
+ indices = [...indices, ...coordIndex];
14417
+ } else if (height === -2) {
14274
14418
  let triStart = 0;
14275
14419
  while (j < coordIndex.length) {
14276
14420
  if (coordIndex[j] >= 0) {
@@ -14329,9 +14473,9 @@ var NVMeshLoaders = class _NVMeshLoaders {
14329
14473
  }
14330
14474
  }
14331
14475
  return {
14332
- positions,
14333
- indices: Int32Array.from(indices),
14334
- rgba255
14476
+ positions: Float32Array.from(positions),
14477
+ indices: Uint32Array.from(indices),
14478
+ rgba255: Uint8Array.from(rgba255)
14335
14479
  };
14336
14480
  }
14337
14481
  // readX3D()
@@ -14399,7 +14543,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
14399
14543
  }
14400
14544
  len = tag.contentEndPos;
14401
14545
  let positions = new Float32Array();
14402
- let indices = new Int32Array();
14546
+ let indices = new Uint32Array();
14403
14547
  let scalars = new Float32Array();
14404
14548
  let anatomicalStructurePrimary = "";
14405
14549
  let isIdx = false;
@@ -14526,7 +14670,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
14526
14670
  if (dataType !== 8) {
14527
14671
  log.warn("expect indices as INT32");
14528
14672
  }
14529
- indices = new Int32Array(datBin.buffer);
14673
+ indices = new Uint32Array(datBin.buffer);
14530
14674
  if (isColMajor) {
14531
14675
  const tmp = indices.slice();
14532
14676
  const np = tmp.length / 3;
@@ -14659,7 +14803,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
14659
14803
 
14660
14804
  // src/nvmesh.ts
14661
14805
  var NVMeshFromUrlOptions = class {
14662
- constructor(url = "", gl = null, name = "", opacity = 1, rgba255 = [255, 255, 255, 255], visible = true, layers = [], colorbarVisible = true) {
14806
+ constructor(url = "", gl = null, name = "", opacity = 1, rgba255 = new Uint8Array([255, 255, 255, 255]), visible = true, layers = [], colorbarVisible = true) {
14663
14807
  __publicField(this, "url");
14664
14808
  __publicField(this, "gl");
14665
14809
  __publicField(this, "name");
@@ -14694,7 +14838,7 @@ var NVMesh = class _NVMesh {
14694
14838
  * @param colorbarVisible - does this mesh display a colorbar
14695
14839
  * @param anatomicalStructurePrimary - region for mesh. Default is an empty string
14696
14840
  */
14697
- constructor(pts, tris, name = "", rgba255 = [255, 255, 255, 255], opacity = 1, visible = true, gl, connectome = null, dpg = null, dps = null, dpv = null, colorbarVisible = true, anatomicalStructurePrimary = "") {
14841
+ constructor(pts, tris, name = "", rgba255 = new Uint8Array([255, 255, 255, 255]), opacity = 1, visible = true, gl, connectome = null, dpg = null, dps = null, dpv = null, colorbarVisible = true, anatomicalStructurePrimary = "") {
14698
14842
  __publicField(this, "id");
14699
14843
  __publicField(this, "name");
14700
14844
  __publicField(this, "anatomicalStructurePrimary");
@@ -14742,6 +14886,7 @@ var NVMesh = class _NVMesh {
14742
14886
  __publicField(this, "vertexCount", 1);
14743
14887
  __publicField(this, "nodeScale", 4);
14744
14888
  __publicField(this, "edgeScale", 1);
14889
+ __publicField(this, "legendLineThickness", 0);
14745
14890
  __publicField(this, "nodeColormap", "warm");
14746
14891
  __publicField(this, "edgeColormap", "warm");
14747
14892
  __publicField(this, "nodeColormapNegative");
@@ -14804,6 +14949,15 @@ var NVMesh = class _NVMesh {
14804
14949
  this.dpg = dpg;
14805
14950
  this.dps = dps;
14806
14951
  this.dpv = dpv;
14952
+ if (dpg) {
14953
+ this.initValuesArray(dpg);
14954
+ }
14955
+ if (dps) {
14956
+ this.initValuesArray(dps);
14957
+ }
14958
+ if (dpv) {
14959
+ this.initValuesArray(dpv);
14960
+ }
14807
14961
  this.offsetPt0 = tris;
14808
14962
  this.updateFibers(gl);
14809
14963
  gl.bindVertexArray(this.vaoFiber);
@@ -14828,6 +14982,17 @@ var NVMesh = class _NVMesh {
14828
14982
  this.tris = tris;
14829
14983
  this.updateMesh(gl);
14830
14984
  }
14985
+ initValuesArray(va) {
14986
+ for (let i = 0; i < va.length; i++) {
14987
+ const mn = va[i].vals.reduce((acc, current) => Math.min(acc, current));
14988
+ const mx = va[i].vals.reduce((acc, current) => Math.max(acc, current));
14989
+ va[i].global_min = mn;
14990
+ va[i].global_max = mx;
14991
+ va[i].cal_min = mn;
14992
+ va[i].cal_max = mx;
14993
+ }
14994
+ return va;
14995
+ }
14831
14996
  // given streamlines (which webGL renders as a single pixel), extrude to cylinders
14832
14997
  linesToCylinders(gl, posClrF32, indices) {
14833
14998
  function v4ToV3(v4) {
@@ -14923,7 +15088,7 @@ var NVMesh = class _NVMesh {
14923
15088
  }
14924
15089
  }
14925
15090
  const nidx = (n_line_vtx - n_streamlines) * cyl_sides * 2 * 3;
14926
- const idxs = new Int32Array(nidx);
15091
+ const idxs = new Uint32Array(nidx);
14927
15092
  let idx = 0;
14928
15093
  vtx = 0;
14929
15094
  for (let i = 1; i < n_count; i++) {
@@ -14955,9 +15120,9 @@ var NVMesh = class _NVMesh {
14955
15120
  vtx += cyl_sides;
14956
15121
  }
14957
15122
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
14958
- gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int32Array(idxs), gl.STATIC_DRAW);
15123
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, Uint32Array.from(idxs), gl.STATIC_DRAW);
14959
15124
  gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
14960
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(f32), gl.STATIC_DRAW);
15125
+ gl.bufferData(gl.ARRAY_BUFFER, Float32Array.from(f32), gl.STATIC_DRAW);
14961
15126
  this.indexCount = nidx;
14962
15127
  }
14963
15128
  // linesToCylinders
@@ -14972,7 +15137,7 @@ var NVMesh = class _NVMesh {
14972
15137
  const n_count = offsetPt0.length - 1;
14973
15138
  const npt = pts.length / 3;
14974
15139
  if (!this.fiberLengths) {
14975
- this.fiberLengths = [];
15140
+ this.fiberLengths = new Uint32Array(n_count);
14976
15141
  for (let i = 0; i < n_count; i++) {
14977
15142
  const vStart3 = offsetPt0[i] * 3;
14978
15143
  const vEnd3 = (offsetPt0[i + 1] - 1) * 3;
@@ -14981,7 +15146,7 @@ var NVMesh = class _NVMesh {
14981
15146
  const v = vec34.fromValues(pts[j + 0] - pts[j + 3], pts[j + 1] - pts[j + 4], pts[j + 2] - pts[j + 5]);
14982
15147
  len += vec34.len(v);
14983
15148
  }
14984
- this.fiberLengths.push(len);
15149
+ this.fiberLengths[i] = len;
14985
15150
  }
14986
15151
  }
14987
15152
  const posClrF32 = new Float32Array(npt * 4);
@@ -15194,119 +15359,12 @@ var NVMesh = class _NVMesh {
15194
15359
  } else {
15195
15360
  this.indexCount = indices.length;
15196
15361
  gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
15197
- gl.bufferData(gl.ARRAY_BUFFER, new Uint32Array(posClrU32), gl.STATIC_DRAW);
15362
+ gl.bufferData(gl.ARRAY_BUFFER, Uint32Array.from(posClrU32), gl.STATIC_DRAW);
15198
15363
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
15199
- gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(indices), gl.STATIC_DRAW);
15364
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, Uint32Array.from(indices), gl.STATIC_DRAW);
15200
15365
  }
15201
15366
  }
15202
15367
  // updateFibers()
15203
- // internal function filters connectome to identify which color, size and visibility of nodes and edges
15204
- updateConnectome(gl) {
15205
- const tris = [];
15206
- const nNode = this.nodes.X.length;
15207
- let hasEdges = false;
15208
- if (nNode > 1 && this.edges) {
15209
- let nEdges = this.edges.length;
15210
- if (nEdges = nNode * nNode) {
15211
- hasEdges = true;
15212
- } else {
15213
- log.warn("Expected %d edges not %d", nNode * nNode, nEdges);
15214
- }
15215
- }
15216
- const pts = [];
15217
- const rgba255 = [];
15218
- let lut = cmapper.colormap(this.nodeColormap, this.colormapInvert);
15219
- let lutNeg = cmapper.colormap(this.nodeColormapNegative, this.colormapInvert);
15220
- let hasNeg = this.nodeColormapNegative !== void 0;
15221
- let min = this.nodeMinColor;
15222
- let max = this.nodeMaxColor;
15223
- for (let i = 0; i < nNode; i++) {
15224
- const radius = this.nodes.Size[i] * this.nodeScale;
15225
- if (radius <= 0) {
15226
- continue;
15227
- }
15228
- let color = this.nodes.Color[i];
15229
- let isNeg = false;
15230
- if (hasNeg && color < 0) {
15231
- isNeg = true;
15232
- color = -color;
15233
- }
15234
- if (min < max) {
15235
- if (color < min) {
15236
- continue;
15237
- }
15238
- color = (color - min) / (max - min);
15239
- } else {
15240
- color = 1;
15241
- }
15242
- color = Math.round(Math.max(Math.min(255, color * 255))) * 4;
15243
- let rgba = [lut[color], lut[color + 1], lut[color + 2], 255];
15244
- if (isNeg) {
15245
- rgba = [lutNeg[color], lutNeg[color + 1], lutNeg[color + 2], 255];
15246
- }
15247
- const pt = [
15248
- this.nodes.X[i],
15249
- this.nodes.Y[i],
15250
- this.nodes.Z[i]
15251
- ];
15252
- NiivueObject3D.makeColoredSphere(pts, tris, rgba255, radius, pt, rgba);
15253
- }
15254
- if (hasEdges) {
15255
- lut = cmapper.colormap(this.edgeColormap, this.colormapInvert);
15256
- lutNeg = cmapper.colormap(this.edgeColormapNegative, this.colormapInvert);
15257
- hasNeg = this.edgeColormapNegative !== void 0;
15258
- min = this.edgeMin;
15259
- max = this.edgeMax;
15260
- for (let i = 0; i < nNode - 1; i++) {
15261
- for (let j = i + 1; j < nNode; j++) {
15262
- let color = this.edges[i * nNode + j];
15263
- let isNeg = false;
15264
- if (hasNeg && color < 0) {
15265
- isNeg = true;
15266
- color = -color;
15267
- }
15268
- const radius = color * this.edgeScale;
15269
- if (radius <= 0) {
15270
- continue;
15271
- }
15272
- if (min < max) {
15273
- if (color < min) {
15274
- continue;
15275
- }
15276
- color = (color - min) / (max - min);
15277
- } else {
15278
- color = 1;
15279
- }
15280
- color = Math.round(Math.max(Math.min(255, color * 255))) * 4;
15281
- let rgba = [lut[color], lut[color + 1], lut[color + 2], 255];
15282
- if (isNeg) {
15283
- rgba = [lutNeg[color], lutNeg[color + 1], lutNeg[color + 2], 255];
15284
- }
15285
- const pti = [
15286
- this.nodes.X[i],
15287
- this.nodes.Y[i],
15288
- this.nodes.Z[i]
15289
- ];
15290
- const ptj = [
15291
- this.nodes.X[j],
15292
- this.nodes.Y[j],
15293
- this.nodes.Z[j]
15294
- ];
15295
- NiivueObject3D.makeColoredCylinder(pts, tris, rgba255, pti, ptj, radius, rgba);
15296
- }
15297
- }
15298
- }
15299
- const obj = NVMeshUtilities.getExtents(pts);
15300
- this.furthestVertexFromOrigin = obj.mxDx;
15301
- this.extentsMin = obj.extentsMin;
15302
- this.extentsMax = obj.extentsMax;
15303
- const posNormClr = this.generatePosNormClr(pts, tris, rgba255);
15304
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
15305
- gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int32Array(tris), gl.STATIC_DRAW);
15306
- gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
15307
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(posNormClr), gl.STATIC_DRAW);
15308
- this.indexCount = tris.length;
15309
- }
15310
15368
  // given X,Y,Z coordinates in world space, return index of nearest vertex as well as
15311
15369
  // the distance of this closest vertex to the coordinates
15312
15370
  indexNearestXYZmm(Xmm, Ymm, Zmm) {
@@ -15334,7 +15392,6 @@ var NVMesh = class _NVMesh {
15334
15392
  return;
15335
15393
  }
15336
15394
  if (this.hasConnectome) {
15337
- this.updateConnectome(gl);
15338
15395
  return;
15339
15396
  }
15340
15397
  if (!this.pts || !this.tris || !this.rgba255) {
@@ -15359,6 +15416,9 @@ var NVMesh = class _NVMesh {
15359
15416
  if (opacity <= 0 || layer.cal_min > layer.cal_max) {
15360
15417
  continue;
15361
15418
  }
15419
+ if (layer.outlineBorder === void 0) {
15420
+ layer.outlineBorder = 0;
15421
+ }
15362
15422
  if (layer.isAdditiveBlend === void 0) {
15363
15423
  layer.isAdditiveBlend = false;
15364
15424
  }
@@ -15380,23 +15440,23 @@ var NVMesh = class _NVMesh {
15380
15440
  rgba8[k + 2] = lut2[idx + 2];
15381
15441
  k += 4;
15382
15442
  }
15383
- let opaque = new Array(nvtx).fill(true);
15384
- if (layer.isOutlineBorder) {
15443
+ let opaque = new Array(nvtx).fill(false);
15444
+ if (layer.outlineBorder > 0) {
15385
15445
  opaque = NVMeshUtilities.getClusterBoundary(rgba8, this.tris);
15386
15446
  }
15387
15447
  k = 0;
15388
15448
  for (let j = 0; j < layer.values.length; j++) {
15389
- if (!opaque[j]) {
15390
- k += 4;
15391
- continue;
15449
+ let opa = opacity;
15450
+ if (opaque[j]) {
15451
+ opa = layer.outlineBorder;
15392
15452
  }
15393
15453
  let vtx = j * 28 + 24;
15394
15454
  if (this.f32PerVertex !== 7) {
15395
15455
  vtx = j * 20 + 16;
15396
15456
  }
15397
- u8[vtx + 0] = lerp(u8[vtx + 0], rgba8[k + 0], opacity);
15398
- u8[vtx + 1] = lerp(u8[vtx + 1], rgba8[k + 1], opacity);
15399
- u8[vtx + 2] = lerp(u8[vtx + 2], rgba8[k + 2], opacity);
15457
+ u8[vtx + 0] = lerp(u8[vtx + 0], rgba8[k + 0], opa);
15458
+ u8[vtx + 1] = lerp(u8[vtx + 1], rgba8[k + 1], opa);
15459
+ u8[vtx + 2] = lerp(u8[vtx + 2], rgba8[k + 2], opa);
15400
15460
  k += 4;
15401
15461
  }
15402
15462
  continue;
@@ -15404,7 +15464,7 @@ var NVMesh = class _NVMesh {
15404
15464
  if (layer.values instanceof Uint8Array) {
15405
15465
  const rgba8 = new Uint8Array(layer.values.buffer);
15406
15466
  let opaque = new Array(nvtx).fill(true);
15407
- if (layer.isOutlineBorder) {
15467
+ if (layer.outlineBorder > 0) {
15408
15468
  opaque = NVMeshUtilities.getClusterBoundary(rgba8, this.tris);
15409
15469
  }
15410
15470
  let k = 0;
@@ -15413,14 +15473,13 @@ var NVMesh = class _NVMesh {
15413
15473
  if (this.f32PerVertex !== 7) {
15414
15474
  vtx = j * 20 + 16;
15415
15475
  }
15416
- if (!opaque[j]) {
15417
- u8[vtx + 3] = 0;
15418
- k += 4;
15419
- continue;
15476
+ let opa = opacity;
15477
+ if (opaque[j]) {
15478
+ opa = layer.outlineBorder;
15420
15479
  }
15421
- u8[vtx + 0] = lerp(u8[vtx + 0], rgba8[k + 0], opacity);
15422
- u8[vtx + 1] = lerp(u8[vtx + 1], rgba8[k + 1], opacity);
15423
- u8[vtx + 2] = lerp(u8[vtx + 2], rgba8[k + 2], opacity);
15480
+ u8[vtx + 0] = lerp(u8[vtx + 0], rgba8[k + 0], opa);
15481
+ u8[vtx + 1] = lerp(u8[vtx + 1], rgba8[k + 1], opa);
15482
+ u8[vtx + 2] = lerp(u8[vtx + 2], rgba8[k + 2], opa);
15424
15483
  k += 4;
15425
15484
  }
15426
15485
  continue;
@@ -15447,7 +15506,7 @@ var NVMesh = class _NVMesh {
15447
15506
  if (!layer.isTransparentBelowCalMin) {
15448
15507
  mnCal = Number.NEGATIVE_INFINITY;
15449
15508
  }
15450
- if (!layer.isOutlineBorder) {
15509
+ if (layer.outlineBorder <= 0) {
15451
15510
  for (let j = 0; j < nvtx; j++) {
15452
15511
  const v = layer.values[j + frameOffset];
15453
15512
  if (v < mnCal) {
@@ -15470,9 +15529,10 @@ var NVMesh = class _NVMesh {
15470
15529
  additiveRGBA[j4 + 2] = additiveBlend(additiveRGBA[j4 + 2], lut[v255 + 2]);
15471
15530
  additiveRGBA[j4 + 3] = additiveBlend(additiveRGBA[j4 + 3], 255);
15472
15531
  } else {
15473
- u8[vtx + 0] = lerp(u8[vtx + 0], lut[v255 + 0], opacity);
15474
- u8[vtx + 1] = lerp(u8[vtx + 1], lut[v255 + 1], opacity);
15475
- u8[vtx + 2] = lerp(u8[vtx + 2], lut[v255 + 2], opacity);
15532
+ const opa = opacity;
15533
+ u8[vtx + 0] = lerp(u8[vtx + 0], lut[v255 + 0], opa);
15534
+ u8[vtx + 1] = lerp(u8[vtx + 1], lut[v255 + 1], opa);
15535
+ u8[vtx + 2] = lerp(u8[vtx + 2], lut[v255 + 2], opa);
15476
15536
  }
15477
15537
  }
15478
15538
  } else {
@@ -15487,23 +15547,26 @@ var NVMesh = class _NVMesh {
15487
15547
  }
15488
15548
  const opaque = NVMeshUtilities.getClusterBoundaryU8(v255s, this.tris);
15489
15549
  for (let j = 0; j < nvtx; j++) {
15490
- let v255 = 255;
15491
- if (!opaque[j]) {
15550
+ if (v255s[j] < 1) {
15492
15551
  continue;
15493
15552
  }
15494
- v255 = Math.min(255, v255) * 4;
15553
+ let opa = opacity;
15554
+ if (opaque[j]) {
15555
+ opa = layer.outlineBorder;
15556
+ }
15557
+ const v255 = v255s[j] * 4;
15495
15558
  let vtx = j * 28 + 24;
15496
15559
  if (this.f32PerVertex !== 7) {
15497
15560
  vtx = j * 20 + 16;
15498
15561
  }
15499
- u8[vtx + 0] = lerp(u8[vtx + 0], lut[v255 + 0], opacity);
15500
- u8[vtx + 1] = lerp(u8[vtx + 1], lut[v255 + 1], opacity);
15501
- u8[vtx + 2] = lerp(u8[vtx + 2], lut[v255 + 2], opacity);
15562
+ u8[vtx + 0] = lerp(u8[vtx + 0], lut[v255 + 0], opa);
15563
+ u8[vtx + 1] = lerp(u8[vtx + 1], lut[v255 + 1], opa);
15564
+ u8[vtx + 2] = lerp(u8[vtx + 2], lut[v255 + 2], opa);
15502
15565
  }
15503
15566
  }
15504
15567
  if (layer.useNegativeCmap) {
15505
15568
  const lut2 = cmapper.colormap(layer.colormapNegative, layer.colormapInvert);
15506
- if (!layer.isOutlineBorder) {
15569
+ if (layer.outlineBorder <= 0) {
15507
15570
  let mn2 = layer.cal_min;
15508
15571
  let mx = layer.cal_max;
15509
15572
  if (isFinite(layer.cal_minNeg) && isFinite(layer.cal_minNeg)) {
@@ -15560,17 +15623,18 @@ var NVMesh = class _NVMesh {
15560
15623
  const opaque = NVMeshUtilities.getClusterBoundaryU8(v255s, this.tris);
15561
15624
  for (let j = 0; j < nvtx; j++) {
15562
15625
  let v255 = 255;
15563
- if (!opaque[j]) {
15564
- continue;
15626
+ let opa = opacity;
15627
+ if (opaque[j]) {
15628
+ opa = layer.outlineBorder;
15565
15629
  }
15566
15630
  v255 = Math.min(255, v255) * 4;
15567
15631
  let vtx = j * 28 + 24;
15568
15632
  if (this.f32PerVertex !== 7) {
15569
15633
  vtx = j * 20 + 16;
15570
15634
  }
15571
- u8[vtx + 0] = lerp(u8[vtx + 0], lut2[v255 + 0], opacity);
15572
- u8[vtx + 1] = lerp(u8[vtx + 1], lut2[v255 + 1], opacity);
15573
- u8[vtx + 2] = lerp(u8[vtx + 2], lut2[v255 + 2], opacity);
15635
+ u8[vtx + 0] = lerp(u8[vtx + 0], lut2[v255 + 0], opa);
15636
+ u8[vtx + 1] = lerp(u8[vtx + 1], lut2[v255 + 1], opa);
15637
+ u8[vtx + 2] = lerp(u8[vtx + 2], lut2[v255 + 2], opa);
15574
15638
  }
15575
15639
  }
15576
15640
  }
@@ -15600,9 +15664,9 @@ var NVMesh = class _NVMesh {
15600
15664
  }
15601
15665
  }
15602
15666
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
15603
- gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int32Array(this.tris), gl.STATIC_DRAW);
15667
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, Uint32Array.from(this.tris), gl.STATIC_DRAW);
15604
15668
  gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
15605
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(posNormClr), gl.STATIC_DRAW);
15669
+ gl.bufferData(gl.ARRAY_BUFFER, Float32Array.from(posNormClr), gl.STATIC_DRAW);
15606
15670
  this.indexCount = this.tris.length;
15607
15671
  this.vertexCount = this.pts.length;
15608
15672
  }
@@ -15689,75 +15753,10 @@ var NVMesh = class _NVMesh {
15689
15753
  }
15690
15754
  return f32;
15691
15755
  }
15692
- static loadConnectomeFromFreeSurfer(json, gl, name = "", opacity = 1, visible = true) {
15693
- let isValid = true;
15694
- if (json.data_type === void 0) {
15695
- isValid = false;
15696
- } else if (json.data_type !== "fs_pointset") {
15697
- isValid = false;
15698
- }
15699
- if (json.points === void 0) {
15700
- isValid = false;
15701
- }
15702
- if (!isValid) {
15703
- throw Error("not a valid FreeSurfer json pointset");
15704
- }
15705
- const jcon = {
15706
- nodes: {
15707
- names: [],
15708
- prefilled: [],
15709
- X: [],
15710
- Y: [],
15711
- Z: [],
15712
- Color: [],
15713
- Size: []
15714
- },
15715
- edges: [],
15716
- // @ts-expect-error not sure where this should come from
15717
- name: this.data_type
15718
- };
15719
- if (!json.points) {
15720
- throw new Error("points are not set!");
15721
- }
15722
- for (let i = 0; i < json.points.length; i++) {
15723
- let name2 = "";
15724
- if (json.points[i].comments) {
15725
- if (json.points[i].comments[0].text) {
15726
- name2 = json.points[i].comments[0].text;
15727
- }
15728
- }
15729
- jcon.nodes.names.push(name2);
15730
- let prefilled = "";
15731
- if (json.points[i].comments) {
15732
- if (json.points[i].comments[0].prefilled) {
15733
- prefilled = json.points[i].comments[0].prefilled;
15734
- }
15735
- }
15736
- jcon.nodes.prefilled.push(prefilled);
15737
- jcon.nodes.X.push(json.points[i].coordinates.x);
15738
- jcon.nodes.Y.push(json.points[i].coordinates.y);
15739
- jcon.nodes.Z.push(json.points[i].coordinates.z);
15740
- jcon.nodes.Color.push(1);
15741
- jcon.nodes.Size.push(1);
15742
- }
15743
- return new _NVMesh([], [], name, [], opacity, visible, gl, jcon);
15744
- }
15745
- // loadConnectomeFromFreeSurfer
15746
- // read connectome saved as JSON
15747
- static loadConnectomeFromJSON(json, gl, name = "", opacity = 1, visible = true) {
15748
- if ("name" in json) {
15749
- name = this.name;
15750
- }
15751
- if (!("nodes" in json)) {
15752
- throw Error("not a valid jcon connectome file");
15753
- }
15754
- return new _NVMesh([], [], name, [], opacity, visible, gl, json);
15755
- }
15756
- // loadConnectomeFromJSON()
15757
15756
  // wrapper to read meshes, tractograms and connectomes regardless of format
15758
- static readMesh(buffer, name, gl, opacity = 1, rgba255 = [255, 255, 255, 255], visible = true) {
15759
- let tris = [];
15760
- let pts = [];
15757
+ static readMesh(buffer, name, gl, opacity = 1, rgba255 = new Uint8Array([255, 255, 255, 255]), visible = true) {
15758
+ let tris = new Uint32Array([]);
15759
+ let pts = new Float32Array([]);
15761
15760
  let anatomicalStructurePrimary = "";
15762
15761
  let obj;
15763
15762
  const re = /(?:\.([^.]+))?$/;
@@ -15768,12 +15767,12 @@ var NVMesh = class _NVMesh {
15768
15767
  ext = ext.toUpperCase();
15769
15768
  }
15770
15769
  if (ext === "JCON") {
15771
- return _NVMesh.loadConnectomeFromJSON(JSON.parse(new TextDecoder().decode(buffer)), gl, name, opacity);
15770
+ log.error("you should never see this message: load using nvconnectome not nvmesh");
15772
15771
  }
15773
15772
  if (ext === "JSON") {
15774
- return _NVMesh.loadConnectomeFromFreeSurfer(JSON.parse(new TextDecoder().decode(buffer)), gl, name, opacity);
15773
+ log.error("you should never see this message: load using nvconnectome not nvmesh");
15775
15774
  }
15776
- rgba255[3] = Math.max(0, rgba255[3]);
15775
+ rgba255[3] = Math.max(1, rgba255[3]);
15777
15776
  if (ext === "TCK" || ext === "TRK" || ext === "TT" || ext === "TRX" || ext === "TRACT") {
15778
15777
  if (ext === "TCK") {
15779
15778
  obj = NVMeshLoaders.readTCK(buffer);
@@ -15787,12 +15786,12 @@ var NVMesh = class _NVMesh {
15787
15786
  obj = NVMeshLoaders.readTRK(buffer);
15788
15787
  }
15789
15788
  if (typeof obj === "undefined") {
15790
- const pts2 = [0, 0, 0, 0, 0, 0];
15791
- const offsetPt0 = [0];
15789
+ const pts2 = new Float32Array([0, 0, 0, 0, 0, 0]);
15790
+ const offsetPt0 = new Uint32Array([0]);
15792
15791
  obj = { pts: pts2, offsetPt0 };
15793
15792
  log.error("Creating empty tracts");
15794
15793
  }
15795
- rgba255[3] = -1;
15794
+ rgba255[3] = 0;
15796
15795
  return new _NVMesh(
15797
15796
  obj.pts,
15798
15797
  obj.offsetPt0,
@@ -15840,10 +15839,10 @@ var NVMesh = class _NVMesh {
15840
15839
  } else if (ext === "FIB" || ext === "VTK") {
15841
15840
  obj = NVMeshLoaders.readVTK(buffer);
15842
15841
  if ("offsetPt0" in obj) {
15843
- rgba255[3] = -1;
15842
+ rgba255[3] = 0;
15844
15843
  return new _NVMesh(
15845
- Array.from(obj.pts),
15846
- Array.from(obj.offsetPt0),
15844
+ obj.pts,
15845
+ obj.offsetPt0,
15847
15846
  name,
15848
15847
  rgba255,
15849
15848
  // colormap,
@@ -15874,20 +15873,21 @@ var NVMesh = class _NVMesh {
15874
15873
  if (!obj.indices) {
15875
15874
  throw new Error("indices not loaded");
15876
15875
  }
15877
- pts = Array.from(obj.positions);
15878
- tris = Array.from(obj.indices);
15876
+ pts = obj.positions;
15877
+ tris = obj.indices;
15879
15878
  if ("rgba255" in obj && obj.rgba255.length > 0) {
15880
15879
  rgba255 = obj.rgba255;
15881
15880
  }
15882
15881
  if ("colors" in obj && obj.colors && obj.colors.length === pts.length) {
15883
- rgba255 = [];
15884
15882
  const n = pts.length / 3;
15883
+ rgba255 = new Uint8Array(n * 4);
15885
15884
  let c = 0;
15885
+ let k = 0;
15886
15886
  for (let i = 0; i < n; i++) {
15887
- rgba255.push(obj.colors[c] * 255);
15888
- rgba255.push(obj.colors[c + 1] * 255);
15889
- rgba255.push(obj.colors[c + 2] * 255);
15890
- rgba255.push(255);
15887
+ rgba255[k++] = obj.colors[c] * 255;
15888
+ rgba255[k++] = obj.colors[c + 1] * 255;
15889
+ rgba255[k++] = obj.colors[c + 2] * 255;
15890
+ rgba255[k++] = 255;
15891
15891
  c += 3;
15892
15892
  }
15893
15893
  }
@@ -15896,6 +15896,7 @@ var NVMesh = class _NVMesh {
15896
15896
  if (ntri < 1 || npt < 3) {
15897
15897
  throw new Error("Mesh should have at least one triangle and three vertices");
15898
15898
  }
15899
+ rgba255[3] = Math.max(1, rgba255[3]);
15899
15900
  const nvm = new _NVMesh(
15900
15901
  pts,
15901
15902
  tris,
@@ -16039,7 +16040,7 @@ var NVMesh = class _NVMesh {
16039
16040
  throw Error(response.statusText);
16040
16041
  }
16041
16042
  const buffer = await response.arrayBuffer();
16042
- const nvmesh = await this.readMesh(buffer, name, gl, opacity, rgba255, visible);
16043
+ const nvmesh = await this.readMesh(buffer, name, gl, opacity, new Uint8Array(rgba255), visible);
16043
16044
  if (!layers || layers.length < 1) {
16044
16045
  return nvmesh;
16045
16046
  }
@@ -16082,7 +16083,7 @@ var NVMesh = class _NVMesh {
16082
16083
  throw new Error("rendering context must be set");
16083
16084
  }
16084
16085
  const buffer = await _NVMesh.readFileAsync(file);
16085
- const nvmesh = _NVMesh.readMesh(buffer, name, gl, opacity, rgba255, visible);
16086
+ const nvmesh = _NVMesh.readMesh(buffer, name, gl, opacity, new Uint8Array(rgba255), visible);
16086
16087
  if (!layers || layers.length < 1) {
16087
16088
  return nvmesh;
16088
16089
  }
@@ -16120,7 +16121,7 @@ var NVMesh = class _NVMesh {
16120
16121
  return bytes.buffer;
16121
16122
  }
16122
16123
  const buffer = base64ToArrayBuffer(base64);
16123
- const nvmesh = await _NVMesh.readMesh(buffer, name, gl, opacity, rgba255, visible);
16124
+ const nvmesh = await _NVMesh.readMesh(buffer, name, gl, opacity, new Uint8Array(rgba255), visible);
16124
16125
  if (!layers || layers.length < 1) {
16125
16126
  return nvmesh;
16126
16127
  }
@@ -16210,7 +16211,7 @@ var NVMesh = class _NVMesh {
16210
16211
  return NVMeshLoaders.readTSF(buffer);
16211
16212
  }
16212
16213
  static readTT(buffer) {
16213
- return NVMeshLoaders.readTRX(buffer);
16214
+ return NVMeshLoaders.readTT(buffer);
16214
16215
  }
16215
16216
  static readTRX(buffer) {
16216
16217
  return NVMeshLoaders.readTRX(buffer);
@@ -17950,7 +17951,7 @@ var NVIMAGE_TYPE = Object.freeze({
17950
17951
  return imageType;
17951
17952
  }
17952
17953
  });
17953
- var NVImageFromUrlOptions = (url, urlImageData = "", name = "", colormap = "gray", opacity = 1, cal_min = NaN, cal_max = NaN, trustCalMinMax = true, percentileFrac = 0.02, ignoreZeroVoxels = false, visible = true, useQFormNotSForm = false, colormapNegative = "", frame4D = 0, imageType = NVIMAGE_TYPE.UNKNOWN, cal_minNeg = NaN, cal_maxNeg = NaN, colorbarVisible = true, alphaThreshold = false, colormapLabel = []) => {
17954
+ var NVImageFromUrlOptions = (url, urlImageData = "", name = "", colormap = "gray", opacity = 1, cal_min = NaN, cal_max = NaN, trustCalMinMax = true, percentileFrac = 0.02, ignoreZeroVoxels = false, useQFormNotSForm = false, colormapNegative = "", frame4D = 0, imageType = NVIMAGE_TYPE.UNKNOWN, cal_minNeg = NaN, cal_maxNeg = NaN, colorbarVisible = true, alphaThreshold = false, colormapLabel = []) => {
17954
17955
  return {
17955
17956
  url,
17956
17957
  urlImageData,
@@ -17963,7 +17964,6 @@ var NVImageFromUrlOptions = (url, urlImageData = "", name = "", colormap = "gray
17963
17964
  trustCalMinMax,
17964
17965
  percentileFrac,
17965
17966
  ignoreZeroVoxels,
17966
- visible,
17967
17967
  useQFormNotSForm,
17968
17968
  colormapNegative,
17969
17969
  imageType,
@@ -18165,7 +18165,6 @@ var NVImage = class _NVImage {
18165
18165
  * @param trustCalMinMax - whether or not to trust cal_min and cal_max from the nifti header (trusting results in faster loading)
18166
18166
  * @param percentileFrac - the percentile to use for setting the robust range of the display values (smart intensity setting for images with large ranges)
18167
18167
  * @param ignoreZeroVoxels - whether or not to ignore zero voxels in setting the robust range of display values
18168
- * @param visible - whether or not this image is to be visible
18169
18168
  * @param useQFormNotSForm - give precedence to QForm (Quaternion) or SForm (Matrix)
18170
18169
  * @param colormapNegative - a color map to use for symmetrical negative intensities
18171
18170
  * @param frame4D - volume displayed, 0 indexed, must be less than nFrame4D
@@ -18181,7 +18180,7 @@ var NVImage = class _NVImage {
18181
18180
  * @param colorbarVisible - TODO
18182
18181
  * @param colormapLabel - TODO
18183
18182
  */
18184
- constructor(dataBuffer = null, name = "", colormap = "gray", opacity = 1, pairedImgData = null, cal_min = NaN, cal_max = NaN, trustCalMinMax = true, percentileFrac = 0.02, ignoreZeroVoxels = false, visible = true, useQFormNotSForm = false, colormapNegative = "", frame4D = 0, imageType = NVIMAGE_TYPE.UNKNOWN, cal_minNeg = NaN, cal_maxNeg = NaN, colorbarVisible = true, colormapLabel = null) {
18183
+ constructor(dataBuffer = null, name = "", colormap = "gray", opacity = 1, pairedImgData = null, cal_min = NaN, cal_max = NaN, trustCalMinMax = true, percentileFrac = 0.02, ignoreZeroVoxels = false, useQFormNotSForm = false, colormapNegative = "", frame4D = 0, imageType = NVIMAGE_TYPE.UNKNOWN, cal_minNeg = NaN, cal_maxNeg = NaN, colorbarVisible = true, colormapLabel = null) {
18185
18184
  __publicField(this, "name");
18186
18185
  __publicField(this, "id");
18187
18186
  __publicField(this, "url");
@@ -18202,7 +18201,6 @@ var NVImage = class _NVImage {
18202
18201
  __publicField(this, "cal_minNeg");
18203
18202
  __publicField(this, "cal_maxNeg");
18204
18203
  __publicField(this, "colorbarVisible", true);
18205
- __publicField(this, "visible");
18206
18204
  __publicField(this, "modulationImage", null);
18207
18205
  __publicField(this, "modulateAlpha", 0);
18208
18206
  // if !=0, mod transparency with expon power |Alpha|
@@ -18305,7 +18303,6 @@ var NVImage = class _NVImage {
18305
18303
  this.cal_minNeg = cal_minNeg;
18306
18304
  this.cal_maxNeg = cal_maxNeg;
18307
18305
  this.colorbarVisible = colorbarVisible;
18308
- this.visible = visible;
18309
18306
  this.useQFormNotSForm = useQFormNotSForm;
18310
18307
  if (!dataBuffer) {
18311
18308
  return;
@@ -20568,7 +20565,6 @@ var NVImage = class _NVImage {
20568
20565
  trustCalMinMax = true,
20569
20566
  percentileFrac = 0.02,
20570
20567
  ignoreZeroVoxels = false,
20571
- visible = true,
20572
20568
  useQFormNotSForm = false,
20573
20569
  colormapNegative = "",
20574
20570
  frame4D = 0,
@@ -20710,7 +20706,6 @@ var NVImage = class _NVImage {
20710
20706
  trustCalMinMax,
20711
20707
  percentileFrac,
20712
20708
  ignoreZeroVoxels,
20713
- visible,
20714
20709
  useQFormNotSForm,
20715
20710
  colormapNegative,
20716
20711
  frame4D,
@@ -20755,7 +20750,6 @@ var NVImage = class _NVImage {
20755
20750
  trustCalMinMax = true,
20756
20751
  percentileFrac = 0.02,
20757
20752
  ignoreZeroVoxels = false,
20758
- visible = true,
20759
20753
  useQFormNotSForm = false,
20760
20754
  colormapNegative = "",
20761
20755
  frame4D = 0,
@@ -20845,7 +20839,6 @@ var NVImage = class _NVImage {
20845
20839
  trustCalMinMax,
20846
20840
  percentileFrac,
20847
20841
  ignoreZeroVoxels,
20848
- visible,
20849
20842
  useQFormNotSForm,
20850
20843
  colormapNegative,
20851
20844
  frame4D,
@@ -20877,8 +20870,7 @@ var NVImage = class _NVImage {
20877
20870
  cal_max = NaN,
20878
20871
  trustCalMinMax = true,
20879
20872
  percentileFrac = 0.02,
20880
- ignoreZeroVoxels = false,
20881
- visible = true
20873
+ ignoreZeroVoxels = false
20882
20874
  }) {
20883
20875
  function base64ToArrayBuffer(base642) {
20884
20876
  const binary_string = window.atob(base642);
@@ -20903,8 +20895,7 @@ var NVImage = class _NVImage {
20903
20895
  cal_max,
20904
20896
  trustCalMinMax,
20905
20897
  percentileFrac,
20906
- ignoreZeroVoxels,
20907
- visible
20898
+ ignoreZeroVoxels
20908
20899
  );
20909
20900
  } catch (err) {
20910
20901
  log.debug(err);
@@ -20923,7 +20914,7 @@ var NVImage = class _NVImage {
20923
20914
  */
20924
20915
  clone() {
20925
20916
  const clonedImage = new _NVImage();
20926
- clonedImage.id = this.id;
20917
+ clonedImage.id = uuidv42();
20927
20918
  clonedImage.hdr = Object.assign({}, this.hdr);
20928
20919
  clonedImage.img = this.img.slice();
20929
20920
  clonedImage.calculateRAS();
@@ -21172,8 +21163,6 @@ var NVImage = class _NVImage {
21172
21163
  // percentileFrac
21173
21164
  this.ignoreZeroVoxels,
21174
21165
  // ignoreZeroVoxels
21175
- this.visible,
21176
- // visible
21177
21166
  this.useQFormNotSForm,
21178
21167
  // useQFormNotSForm
21179
21168
  this.colormapNegative,
@@ -21356,6 +21345,7 @@ var DEFAULT_OPTIONS = {
21356
21345
  textHeight: 0.06,
21357
21346
  colorbarHeight: 0.05,
21358
21347
  crosshairWidth: 1,
21348
+ crosshairGap: 0,
21359
21349
  rulerWidth: 4,
21360
21350
  show3Dcrosshair: false,
21361
21351
  backColor: [0, 0, 0, 1],
@@ -21373,7 +21363,7 @@ var DEFAULT_OPTIONS = {
21373
21363
  keyDebounceTime: 50,
21374
21364
  isNearestInterpolation: false,
21375
21365
  isResizeCanvas: true,
21376
- isAtlasOutline: false,
21366
+ atlasOutline: 0,
21377
21367
  isRuler: false,
21378
21368
  isColorbar: false,
21379
21369
  isOrientCube: false,
@@ -21649,7 +21639,6 @@ var NVDocument = class _NVDocument {
21649
21639
  trustCalMinMax: true,
21650
21640
  percentileFrac: 0.02,
21651
21641
  ignoreZeroVoxels: false,
21652
- visible: true,
21653
21642
  useQFormNotSForm: false,
21654
21643
  colormapNegative: "",
21655
21644
  colormapLabel: [],
@@ -21695,7 +21684,6 @@ var NVDocument = class _NVDocument {
21695
21684
  trustCalMinMax: true,
21696
21685
  percentileFrac: 0.02,
21697
21686
  ignoreZeroVoxels: false,
21698
- visible: true,
21699
21687
  useQFormNotSForm: false,
21700
21688
  colormapNegative: "",
21701
21689
  colormapLabel: [],
@@ -21737,7 +21725,7 @@ var NVDocument = class _NVDocument {
21737
21725
  pts: mesh.pts,
21738
21726
  tris: mesh.tris,
21739
21727
  name: mesh.name,
21740
- rgba255: mesh.rgba255,
21728
+ rgba255: Uint8Array.from(mesh.rgba255),
21741
21729
  opacity: mesh.opacity,
21742
21730
  connectome: mesh.connectome,
21743
21731
  dpg: mesh.dpg,
@@ -21748,7 +21736,7 @@ var NVDocument = class _NVDocument {
21748
21736
  values: layer.values,
21749
21737
  nFrame4D: layer.nFrame4D,
21750
21738
  frame4D: 0,
21751
- isOutlineBorder: layer.isOutlineBorder,
21739
+ outlineBorder: layer.outlineBorder,
21752
21740
  global_min: layer.global_min,
21753
21741
  global_max: layer.global_max,
21754
21742
  cal_min: layer.cal_min,
@@ -21774,6 +21762,7 @@ var NVDocument = class _NVDocument {
21774
21762
  nodeMaxColor: mesh.nodeMaxColor,
21775
21763
  nodeMinColor: mesh.nodeMinColor,
21776
21764
  nodeScale: mesh.nodeScale,
21765
+ legendLineThickness: mesh.legendLineThickness,
21777
21766
  offsetPt0: mesh.offsetPt0,
21778
21767
  nodes: mesh.nodes
21779
21768
  };
@@ -21917,14 +21906,14 @@ var defaultOptions = {
21917
21906
  edgeColormapNegative: "winter",
21918
21907
  edgeMin: 2,
21919
21908
  edgeMax: 6,
21920
- edgeScale: 1
21909
+ edgeScale: 1,
21910
+ legendLineThickness: 0
21921
21911
  };
21922
21912
  var NVConnectome = class _NVConnectome extends NVMesh {
21923
21913
  constructor(gl, connectome) {
21924
- super([], [], connectome.name, [], 1, true, gl, connectome);
21914
+ super(new Float32Array([]), new Uint32Array([]), connectome.name, new Uint8Array([]), 1, true, gl, connectome);
21925
21915
  __publicField(this, "gl");
21926
21916
  __publicField(this, "nodesChanged");
21927
- __publicField(this, "legendLineThickness");
21928
21917
  this.gl = gl;
21929
21918
  this.type = "connectome" /* CONNECTOME */;
21930
21919
  if (this.nodes) {
@@ -22200,19 +22189,22 @@ var NVConnectome = class _NVConnectome extends NVMesh {
22200
22189
  const ptj = vec38.fromValues(nodes[edge.second].x, nodes[edge.second].y, nodes[edge.second].z);
22201
22190
  NiivueObject3D.makeColoredCylinder(pts, tris, rgba255, pti, ptj, radius, rgba);
22202
22191
  }
22203
- const obj = NVMeshUtilities.getExtents(pts);
22192
+ const pts32 = new Float32Array(pts);
22193
+ const tris32 = new Uint32Array(tris);
22194
+ const obj = NVMeshUtilities.getExtents(pts32);
22204
22195
  this.furthestVertexFromOrigin = obj.mxDx;
22205
22196
  this.extentsMin = obj.extentsMin;
22206
22197
  this.extentsMax = obj.extentsMax;
22207
- const posNormClr = this.generatePosNormClr(pts, tris, rgba255);
22198
+ const posNormClr = this.generatePosNormClr(pts32, tris32, new Uint8Array(rgba255));
22208
22199
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
22209
- gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int32Array(tris), gl.STATIC_DRAW);
22200
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, Uint32Array.from(tris32), gl.STATIC_DRAW);
22210
22201
  gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
22211
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(posNormClr), gl.STATIC_DRAW);
22202
+ gl.bufferData(gl.ARRAY_BUFFER, Float32Array.from(posNormClr), gl.STATIC_DRAW);
22212
22203
  this.indexCount = tris.length;
22213
22204
  }
22214
22205
  updateMesh(gl) {
22215
22206
  this.updateConnectome(gl);
22207
+ this.updateLabels();
22216
22208
  }
22217
22209
  json() {
22218
22210
  const json = {};
@@ -23013,6 +23005,22 @@ var MESH_EXTENSIONS = [
23013
23005
  var LEFT_MOUSE_BUTTON = 0;
23014
23006
  var CENTER_MOUSE_BUTTON = 1;
23015
23007
  var RIGHT_MOUSE_BUTTON = 2;
23008
+ var TEXTURE0_BACK_VOL = 33984;
23009
+ var TEXTURE1_COLORMAPS = 33985;
23010
+ var TEXTURE2_OVERLAY_VOL = 33986;
23011
+ var TEXTURE3_FONT = 33987;
23012
+ var TEXTURE4_THUMBNAIL = 33988;
23013
+ var TEXTURE5_MATCAP = 33989;
23014
+ var TEXTURE6_GRADIENT = 33990;
23015
+ var TEXTURE7_DRAW = 33991;
23016
+ var TEXTURE8_GRADIENT_TEMP = 33992;
23017
+ var TEXTURE9_ORIENT = 33993;
23018
+ var TEXTURE10_BLEND = 33994;
23019
+ var TEXTURE11_GC_BACK = 33995;
23020
+ var TEXTURE12_GC_STRENGTH0 = 33996;
23021
+ var TEXTURE13_GC_STRENGTH1 = 33997;
23022
+ var TEXTURE14_GC_LABEL0 = 33998;
23023
+ var TEXTURE15_GC_LABEL1 = 33999;
23016
23024
  var defaultSaveImageOptions = {
23017
23025
  filename: "",
23018
23026
  isSaveDrawing: false,
@@ -23115,6 +23123,8 @@ var Niivue = class _Niivue {
23115
23123
  __publicField(this, "position");
23116
23124
  __publicField(this, "extentsMin");
23117
23125
  __publicField(this, "extentsMax");
23126
+ // ResizeObserver
23127
+ __publicField(this, "resizeObserver", null);
23118
23128
  __publicField(this, "syncOpts", {});
23119
23129
  __publicField(this, "readyForSync", false);
23120
23130
  // UI Data
@@ -23140,13 +23150,7 @@ var Niivue = class _Niivue {
23140
23150
  dragEnd: [0, 0],
23141
23151
  dragClipPlaneStartDepthAziElev: [0, 0, 0],
23142
23152
  lastTwoTouchDistance: 0,
23143
- multiTouchGesture: false,
23144
- loading$: new Subject()
23145
- // whether or not the scene is loading
23146
- });
23147
- // mapping of keys (event strings) to rxjs subjects
23148
- __publicField(this, "eventsToSubjects", {
23149
- loading: this.uiData.loading$
23153
+ multiTouchGesture: false
23150
23154
  });
23151
23155
  __publicField(this, "back", null);
23152
23156
  // base layer; defines image space to work in. Defined as this.volumes[0] in Niivue.loadVolumes
@@ -23435,8 +23439,6 @@ var Niivue = class _Niivue {
23435
23439
  __publicField(this, "initialized", false);
23436
23440
  __publicField(this, "currentDrawUndoBitmap");
23437
23441
  __publicField(this, "loadingText");
23438
- // rxjs subscriptions. Keeping a reference array like this allows us to unsubscribe later
23439
- __publicField(this, "subscriptions", []);
23440
23442
  for (const name in options) {
23441
23443
  if (typeof options[name] === "function") {
23442
23444
  this[name] = options[name];
@@ -23529,57 +23531,6 @@ var Niivue = class _Niivue {
23529
23531
  log.debug("attached to element with id: ", id);
23530
23532
  return this;
23531
23533
  }
23532
- // on handles matching event strings (event) with know rxjs subjects within NiiVue.
23533
- // if the event string exists (e.g. 'location') then the corresponding rxjs subject reference
23534
- // is extracted from this.eventsToSubjects and the callback passed as the second argument to NiiVue.on
23535
- // is added to the subsciptions to the next method. These callbacks are called whenever subject.next is called within
23536
- // various NiiVue methods.
23537
- /**
23538
- * register a callback function to run when known Niivue events happen
23539
- * @param event - the name of the event to watch for. Event names are shown in the type column
23540
- * @param callback - the function to call when the event happens
23541
- * @example
23542
- * niivue = new Niivue()
23543
- *
23544
- * // 'location' update event is fired when the crosshair changes position via user input
23545
- * function doSomethingWithLocationData(data)\{
23546
- * // data has the shape \{mm: [N, N, N], vox: [N, N, N], frac: [N, N, N], values: this.volumes.map(v =\> \{return val\})\}
23547
- * //...
23548
- * \}
23549
- */
23550
- on(event, callback) {
23551
- const knownEvents = Object.keys(this.eventsToSubjects);
23552
- if (!knownEvents.includes(event)) {
23553
- return;
23554
- }
23555
- const subject = this.eventsToSubjects[event];
23556
- const subscription = subject.subscribe({
23557
- next: (data) => callback(data)
23558
- });
23559
- this.subscriptions.push({ [event]: subscription });
23560
- }
23561
- /**
23562
- * off unsubscribes events and subjects (the opposite of on)
23563
- * @param event - the name of the event to watch for. Event names are shown in the type column
23564
- * @example
23565
- * niivue = new Niivue()
23566
- * niivue.off('location')
23567
- */
23568
- off(event) {
23569
- const knownEvents = Object.keys(this.eventsToSubjects);
23570
- if (!knownEvents.includes(event)) {
23571
- return;
23572
- }
23573
- const nsubs = this.subscriptions.length;
23574
- for (let i = 0; i < nsubs; i++) {
23575
- const key = Object.keys(this.subscriptions[i])[0];
23576
- if (key === event) {
23577
- this.subscriptions[i][event].unsubscribe();
23578
- this.subscriptions.splice(i, 1);
23579
- return;
23580
- }
23581
- }
23582
- }
23583
23534
  /**
23584
23535
  * attach the Niivue instance to a canvas element directly
23585
23536
  * @param canvas - the canvas element reference
@@ -23606,6 +23557,8 @@ var Niivue = class _Niivue {
23606
23557
  this.canvas.width = this.canvas.offsetWidth;
23607
23558
  this.canvas.height = this.canvas.offsetHeight;
23608
23559
  window.addEventListener("resize", this.resizeListener.bind(this));
23560
+ this.resizeObserver = new ResizeObserver(this.resizeListener.bind(this));
23561
+ this.resizeObserver.observe(this.canvas.parentElement);
23609
23562
  }
23610
23563
  this.registerInteractions();
23611
23564
  await this.init();
@@ -24263,6 +24216,12 @@ var Niivue = class _Niivue {
24263
24216
  this.moveCrosshairInVox(0, -1, 0);
24264
24217
  } else if (e.code === "KeyK" && this.opts.sliceType !== 4 /* RENDER */) {
24265
24218
  this.moveCrosshairInVox(0, 1, 0);
24219
+ } else if (e.code === "KeyM" && this.opts.sliceType !== 4 /* RENDER */) {
24220
+ this.opts.dragMode++;
24221
+ if (this.opts.dragMode >= 4 /* slicer3D */) {
24222
+ this.opts.dragMode = 0 /* none */;
24223
+ }
24224
+ log.info("drag mode changed to ", DRAG_MODE[this.opts.dragMode]);
24266
24225
  } else if (e.code === "ArrowLeft") {
24267
24226
  this.setFrame4D(this.volumes[0].id, this.volumes[0].frame4D - 1);
24268
24227
  } else if (e.code === "ArrowRight") {
@@ -24499,7 +24458,6 @@ var Niivue = class _Niivue {
24499
24458
  gl: this.gl,
24500
24459
  name: file.name
24501
24460
  }).then((mesh) => {
24502
- this.uiData.loading$.next(false);
24503
24461
  this.addMesh(mesh);
24504
24462
  }).catch((e2) => {
24505
24463
  throw e2;
@@ -24576,6 +24534,9 @@ var Niivue = class _Niivue {
24576
24534
  * @see {@link https://niivue.github.io/niivue/features/layout.html|live demo usage}
24577
24535
  */
24578
24536
  setMultiplanarLayout(layout) {
24537
+ if (typeof layout === "string") {
24538
+ layout = parseInt(layout);
24539
+ }
24579
24540
  this.opts.multiplanarLayout = layout;
24580
24541
  this.drawScene();
24581
24542
  }
@@ -24806,7 +24767,7 @@ var Niivue = class _Niivue {
24806
24767
  const perm = drawingBitmap.permRAS;
24807
24768
  const vx = dims[1] * dims[2] * dims[3];
24808
24769
  this.drawBitmap = new Uint8Array(vx);
24809
- this.drawTexture = this.r8Tex(this.drawTexture, this.gl.TEXTURE7, this.back.dims, true);
24770
+ this.drawTexture = this.r8Tex(this.drawTexture, TEXTURE7_DRAW, this.back.dims, true);
24810
24771
  const layout = [0, 0, 0];
24811
24772
  for (let i = 0; i < 3; i++) {
24812
24773
  for (let j2 = 0; j2 < 3; j2++) {
@@ -25211,6 +25172,24 @@ var Niivue = class _Niivue {
25211
25172
  this.updateGLVolume();
25212
25173
  this.onMeshPropertyChanged(idx, key, val);
25213
25174
  }
25175
+ /**
25176
+ * returns the index of the mesh vertex that is closest to the provided coordinates
25177
+ * @param id - identity of mesh to change
25178
+ * @param Xmm - location in left/right dimension
25179
+ * @param Ymm - location in posterior/anterior dimension
25180
+ * @param Zmm - location in foot/head dimension
25181
+ * @returns the an array where ret[0] is the mesh index and ret[1] is distance from vertex to coordinates
25182
+ * @example niivue.indexNearestXYZmm(niivue.meshes[0].id, -22, 42, 13)
25183
+ * @see {@link https://niivue.github.io/niivue/features/clipplanes.html | live demo usage}
25184
+ */
25185
+ indexNearestXYZmm(mesh, Xmm, Ymm, Zmm) {
25186
+ const idx = this.getMeshIndexByID(mesh);
25187
+ if (idx < 0) {
25188
+ log.warn("indexNearestXYZmm() id not loaded", mesh);
25189
+ return [NaN, NaN];
25190
+ }
25191
+ return this.meshes[idx].indexNearestXYZmm(Xmm, Ymm, Zmm);
25192
+ }
25214
25193
  /**
25215
25194
  * reverse triangle winding of mesh (swap front and back faces)
25216
25195
  * @param id - identity of mesh to change
@@ -25996,14 +25975,8 @@ var Niivue = class _Niivue {
25996
25975
  * @see {@link https://niivue.github.io/niivue/features/mask.html|live demo usage}
25997
25976
  */
25998
25977
  async loadVolumes(volumeList) {
25999
- this.on("loading", (isLoading) => {
26000
- if (isLoading) {
26001
- this.loadingText = "loading...";
26002
- this.drawScene();
26003
- } else {
26004
- this.loadingText = this.opts.loadingText;
26005
- }
26006
- });
25978
+ this.loadingText = "loading...";
25979
+ this.drawScene();
26007
25980
  if (this.thumbnailVisible) {
26008
25981
  this.deferredVolumes = volumeList;
26009
25982
  return this;
@@ -26011,9 +25984,7 @@ var Niivue = class _Niivue {
26011
25984
  this.volumes = [];
26012
25985
  this.gl.clearColor(0, 0, 0, 1);
26013
25986
  this.gl.clear(this.gl.COLOR_BUFFER_BIT);
26014
- this.uiData.loading$.next(false);
26015
25987
  for (let i = 0; i < volumeList.length; i++) {
26016
- this.uiData.loading$.next(true);
26017
25988
  if (volumeList[i].colorMap !== void 0) {
26018
25989
  volumeList[i].colormap = volumeList[i].colorMap;
26019
25990
  }
@@ -26034,7 +26005,6 @@ var Niivue = class _Niivue {
26034
26005
  colorbarVisible: volumeList[i].colorbarVisible
26035
26006
  };
26036
26007
  await this.addVolumeFromUrl(imageOptions);
26037
- this.uiData.loading$.next(false);
26038
26008
  }
26039
26009
  return this;
26040
26010
  }
@@ -26043,6 +26013,16 @@ var Niivue = class _Niivue {
26043
26013
  * @see {@link https://niivue.github.io/niivue/features/multiuser.meshes.html|live demo usage}
26044
26014
  */
26045
26015
  async addMeshFromUrl(meshOptions) {
26016
+ const ext = this.getFileExt(meshOptions.url);
26017
+ if (ext === "JCON" || ext === "JSON") {
26018
+ const response = await fetch(meshOptions.url, {});
26019
+ const json = await response.json();
26020
+ const mesh2 = this.loadConnectomeAsMesh(json);
26021
+ this.mediaUrlMap.set(mesh2, meshOptions.url);
26022
+ this.onMeshAddedFromUrl(meshOptions, mesh2);
26023
+ this.addMesh(mesh2);
26024
+ return mesh2;
26025
+ }
26046
26026
  const mesh = await NVMesh.loadFromUrl({ ...meshOptions, gl: this.gl });
26047
26027
  this.mediaUrlMap.set(mesh, meshOptions.url);
26048
26028
  this.onMeshAddedFromUrl(meshOptions, mesh);
@@ -26059,14 +26039,8 @@ var Niivue = class _Niivue {
26059
26039
  * @see {@link https://niivue.github.io/niivue/features/meshes.html|live demo usage}
26060
26040
  */
26061
26041
  async loadMeshes(meshList) {
26062
- this.on("loading", (isLoading) => {
26063
- if (isLoading) {
26064
- this.loadingText = "loading...";
26065
- this.drawScene();
26066
- } else {
26067
- this.loadingText = this.opts.loadingText;
26068
- }
26069
- });
26042
+ this.loadingText = "loading...";
26043
+ this.drawScene();
26070
26044
  if (this.thumbnailVisible) {
26071
26045
  this.deferredMeshes = meshList;
26072
26046
  return this;
@@ -26076,11 +26050,8 @@ var Niivue = class _Niivue {
26076
26050
  this.meshes = [];
26077
26051
  this.gl.clearColor(0, 0, 0, 1);
26078
26052
  this.gl.clear(this.gl.COLOR_BUFFER_BIT);
26079
- this.uiData.loading$.next(false);
26080
26053
  for (let i = 0; i < meshList.length; i++) {
26081
- this.uiData.loading$.next(true);
26082
26054
  await this.addMeshFromUrl(meshList[i]);
26083
- this.uiData.loading$.next(false);
26084
26055
  }
26085
26056
  this.drawScene();
26086
26057
  return this;
@@ -26133,6 +26104,22 @@ var Niivue = class _Niivue {
26133
26104
  );
26134
26105
  this.drawScene();
26135
26106
  }
26107
+ loadConnectomeAsMesh(json) {
26108
+ let connectome = json;
26109
+ if ("data_type" in json && json.data_type === "fs_pointset") {
26110
+ connectome = NVConnectome.convertFreeSurferConnectome(json);
26111
+ log.warn("converted FreeSurfer connectome", connectome);
26112
+ } else if ("nodes" in json) {
26113
+ const nodes = json.nodes;
26114
+ if ("names" in nodes && "X" in nodes && "Y" in nodes && "Z" in nodes && "Color" in nodes && "Size" in nodes) {
26115
+ connectome = NVConnectome.convertLegacyConnectome(json);
26116
+ log.warn("converted legacy connectome", connectome);
26117
+ }
26118
+ } else {
26119
+ throw new Error("not a known connectome format");
26120
+ }
26121
+ return new NVConnectome(this.gl, connectome);
26122
+ }
26136
26123
  /**
26137
26124
  * load a connectome specified by json
26138
26125
  * @param connectome - model
@@ -26140,27 +26127,13 @@ var Niivue = class _Niivue {
26140
26127
  * @see {@link https://niivue.github.io/niivue/features/connectome.html|live demo usage}
26141
26128
  */
26142
26129
  loadConnectome(json) {
26143
- this.on("loading", (isLoading) => {
26144
- if (isLoading) {
26145
- this.loadingText = "loading...";
26146
- this.drawScene();
26147
- } else {
26148
- this.loadingText = this.opts.loadingText;
26149
- }
26150
- });
26130
+ this.loadingText = "loading...";
26131
+ this.drawScene();
26151
26132
  this.meshes = [];
26152
26133
  this.gl.clearColor(0, 0, 0, 1);
26153
26134
  this.gl.clear(this.gl.COLOR_BUFFER_BIT);
26154
- this.uiData.loading$.next(true);
26155
- let connectome = json;
26156
- const nodes = json.nodes;
26157
- if ("names" in nodes && "X" in nodes && "Y" in nodes && "Z" in nodes && "Color" in nodes && "Size" in nodes) {
26158
- connectome = NVConnectome.convertLegacyConnectome(json);
26159
- log.warn("converted legacy connectome", connectome);
26160
- }
26161
- const mesh = new NVConnectome(this.gl, connectome);
26135
+ const mesh = this.loadConnectomeAsMesh(json);
26162
26136
  this.addMesh(mesh);
26163
- this.uiData.loading$.next(false);
26164
26137
  this.drawScene();
26165
26138
  return this;
26166
26139
  }
@@ -26181,7 +26154,7 @@ var Niivue = class _Niivue {
26181
26154
  this.drawBitmap = new Uint8Array(vx);
26182
26155
  this.drawClearAllUndoBitmaps();
26183
26156
  this.drawAddUndoBitmap();
26184
- this.drawTexture = this.r8Tex(this.drawTexture, this.gl.TEXTURE7, this.back.dims, true);
26157
+ this.drawTexture = this.r8Tex(this.drawTexture, TEXTURE7_DRAW, this.back.dims, true);
26185
26158
  this.refreshDrawing(false);
26186
26159
  }
26187
26160
  // not included in public docs
@@ -26242,29 +26215,29 @@ var Niivue = class _Niivue {
26242
26215
  gl.viewport(0, 0, this.back.dims[1], this.back.dims[2]);
26243
26216
  gl.disable(gl.BLEND);
26244
26217
  let img16 = img2ras16(this.back);
26245
- const background = this.r16Tex(null, gl.TEXTURE3, this.back.dims, img16);
26218
+ const background = this.r16Tex(null, TEXTURE11_GC_BACK, this.back.dims, img16);
26246
26219
  for (let i = 1; i < nv; i++) {
26247
26220
  img16[i] = this.drawBitmap[i];
26248
26221
  }
26249
- const label0 = this.r16Tex(null, gl.TEXTURE6, this.back.dims, img16);
26250
- const label1 = this.r16Tex(null, gl.TEXTURE7, this.back.dims, img16);
26222
+ const label0 = this.r16Tex(null, TEXTURE14_GC_LABEL0, this.back.dims, img16);
26223
+ const label1 = this.r16Tex(null, TEXTURE15_GC_LABEL1, this.back.dims, img16);
26251
26224
  const kMAX_STRENGTH = 1e4;
26252
26225
  for (let i = 1; i < nv; i++) {
26253
26226
  if (img16[i] > 0) {
26254
26227
  img16[i] = kMAX_STRENGTH;
26255
26228
  }
26256
26229
  }
26257
- const strength0 = this.r16Tex(null, gl.TEXTURE4, this.back.dims, img16);
26258
- const strength1 = this.r16Tex(null, gl.TEXTURE5, this.back.dims, img16);
26230
+ const strength0 = this.r16Tex(null, TEXTURE12_GC_STRENGTH0, this.back.dims, img16);
26231
+ const strength1 = this.r16Tex(null, TEXTURE13_GC_STRENGTH1, this.back.dims, img16);
26259
26232
  gl.bindVertexArray(this.genericVAO);
26260
26233
  const shader = this.growCutShader;
26261
26234
  shader.use(gl);
26262
26235
  const iterations = 128;
26263
26236
  gl.uniform1i(shader.uniforms.finalPass, 0);
26264
- gl.uniform1i(shader.uniforms.inputTexture0, 3);
26237
+ gl.uniform1i(shader.uniforms.backTex, 11);
26265
26238
  for (let j = 0; j < iterations; j++) {
26266
- gl.uniform1i(shader.uniforms.inputTexture1, 6);
26267
- gl.uniform1i(shader.uniforms.inputTexture2, 4);
26239
+ gl.uniform1i(shader.uniforms.labelTex, 14);
26240
+ gl.uniform1i(shader.uniforms.strengthTex, 12);
26268
26241
  for (let i = 0; i < this.back.dims[3]; i++) {
26269
26242
  const coordZ = 1 / this.back.dims[3] * (i + 0.5);
26270
26243
  gl.uniform1f(shader.uniforms.coordZ, coordZ);
@@ -26280,8 +26253,8 @@ var Niivue = class _Niivue {
26280
26253
  if (j === iterations - 1) {
26281
26254
  gl.uniform1i(shader.uniforms.finalPass, 1);
26282
26255
  }
26283
- gl.uniform1i(shader.uniforms.inputTexture1, 7);
26284
- gl.uniform1i(shader.uniforms.inputTexture2, 5);
26256
+ gl.uniform1i(shader.uniforms.labelTex, 15);
26257
+ gl.uniform1i(shader.uniforms.strengthTex, 13);
26285
26258
  for (let i = 0; i < this.back.dims[3]; i++) {
26286
26259
  const coordZ = 1 / this.back.dims[3] * (i + 0.5);
26287
26260
  gl.uniform1f(shader.uniforms.coordZ, coordZ);
@@ -26327,14 +26300,6 @@ var Niivue = class _Niivue {
26327
26300
  for (let i = 1; i < nv; i++) {
26328
26301
  this.drawBitmap[i] = img16[i];
26329
26302
  }
26330
- gl.activeTexture(gl.TEXTURE2);
26331
- gl.bindTexture(gl.TEXTURE_3D, this.overlayTexture);
26332
- gl.activeTexture(gl.TEXTURE3);
26333
- gl.bindTexture(gl.TEXTURE_2D, this.fontTexture);
26334
- gl.activeTexture(gl.TEXTURE4);
26335
- gl.bindTexture(gl.TEXTURE_2D, this.bmpTexture);
26336
- gl.activeTexture(gl.TEXTURE7);
26337
- gl.bindTexture(gl.TEXTURE_3D, this.drawTexture);
26338
26303
  gl.deleteTexture(background);
26339
26304
  gl.deleteTexture(strength0);
26340
26305
  gl.deleteTexture(strength1);
@@ -26760,7 +26725,7 @@ var Niivue = class _Niivue {
26760
26725
  */
26761
26726
  closeDrawing() {
26762
26727
  this.drawClearAllUndoBitmaps();
26763
- this.rgbaTex(this.drawTexture, this.gl.TEXTURE7, [2, 2, 2, 2], true);
26728
+ this.rgbaTex(this.drawTexture, TEXTURE7_DRAW, [2, 2, 2, 2], true);
26764
26729
  this.drawBitmap = null;
26765
26730
  this.drawScene();
26766
26731
  }
@@ -26786,7 +26751,7 @@ var Niivue = class _Niivue {
26786
26751
  } else if (vx !== this.drawBitmap.length) {
26787
26752
  log.warn("Drawing bitmap must match the background image");
26788
26753
  }
26789
- this.gl.activeTexture(this.gl.TEXTURE7);
26754
+ this.gl.activeTexture(TEXTURE7_DRAW);
26790
26755
  this.gl.bindTexture(this.gl.TEXTURE_3D, this.drawTexture);
26791
26756
  this.gl.texSubImage3D(
26792
26757
  this.gl.TEXTURE_3D,
@@ -26897,11 +26862,11 @@ var Niivue = class _Niivue {
26897
26862
  this.bmpTexture = this.gl.createTexture();
26898
26863
  pngTexture = this.bmpTexture;
26899
26864
  this.bmpTextureWH = img.width / img.height;
26900
- this.gl.activeTexture(this.gl.TEXTURE4);
26865
+ this.gl.activeTexture(TEXTURE4_THUMBNAIL);
26901
26866
  this.bmpShader.use(this.gl);
26902
26867
  this.gl.uniform1i(this.bmpShader.uniforms.bmpTexture, 4);
26903
26868
  } else if (textureNum === 5) {
26904
- this.gl.activeTexture(this.gl.TEXTURE5);
26869
+ this.gl.activeTexture(TEXTURE5_MATCAP);
26905
26870
  if (this.matCapTexture !== null) {
26906
26871
  this.gl.deleteTexture(this.matCapTexture);
26907
26872
  }
@@ -26909,7 +26874,7 @@ var Niivue = class _Niivue {
26909
26874
  pngTexture = this.matCapTexture;
26910
26875
  } else {
26911
26876
  this.fontShader.use(this.gl);
26912
- this.gl.activeTexture(this.gl.TEXTURE3);
26877
+ this.gl.activeTexture(TEXTURE3_FONT);
26913
26878
  this.gl.uniform1i(this.fontShader.uniforms.fontTexture, 3);
26914
26879
  if (this.fontTexture !== null) {
26915
26880
  this.gl.deleteTexture(this.fontTexture);
@@ -27144,9 +27109,9 @@ var Niivue = class _Niivue {
27144
27109
  this.gl.cullFace(this.gl.FRONT);
27145
27110
  this.gl.enable(this.gl.BLEND);
27146
27111
  this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
27147
- this.volumeTexture = this.rgbaTex(this.volumeTexture, this.gl.TEXTURE0, [2, 2, 2, 2], true);
27148
- this.overlayTexture = this.rgbaTex(this.overlayTexture, this.gl.TEXTURE2, [2, 2, 2, 2], true);
27149
- this.drawTexture = this.r8Tex(this.drawTexture, this.gl.TEXTURE7, [2, 2, 2, 2], true);
27112
+ this.volumeTexture = this.rgbaTex(this.volumeTexture, TEXTURE0_BACK_VOL, [2, 2, 2, 2], true);
27113
+ this.overlayTexture = this.rgbaTex(this.overlayTexture, TEXTURE2_OVERLAY_VOL, [2, 2, 2, 2], true);
27114
+ this.drawTexture = this.r8Tex(this.drawTexture, TEXTURE7_DRAW, [2, 2, 2, 2], true);
27150
27115
  const rectStrip = [
27151
27116
  1,
27152
27117
  1,
@@ -27283,10 +27248,10 @@ var Niivue = class _Niivue {
27283
27248
  gl.disable(gl.CULL_FACE);
27284
27249
  gl.viewport(0, 0, hdr.dims[1], hdr.dims[2]);
27285
27250
  gl.disable(gl.BLEND);
27286
- const tempTex3D = this.rgbaTex(null, this.gl.TEXTURE5, hdr.dims);
27251
+ const tempTex3D = this.rgbaTex(null, TEXTURE8_GRADIENT_TEMP, hdr.dims);
27287
27252
  const blurShader = this.blurShader;
27288
27253
  blurShader.use(gl);
27289
- gl.activeTexture(gl.TEXTURE0);
27254
+ gl.activeTexture(TEXTURE0_BACK_VOL);
27290
27255
  gl.bindTexture(gl.TEXTURE_3D, this.volumeTexture);
27291
27256
  const blurRadius = 0.7;
27292
27257
  gl.uniform1i(blurShader.uniforms.intensityVol, 0);
@@ -27303,20 +27268,20 @@ var Niivue = class _Niivue {
27303
27268
  }
27304
27269
  const sobelShader = this.sobelShader;
27305
27270
  sobelShader.use(gl);
27306
- gl.activeTexture(gl.TEXTURE1);
27271
+ gl.activeTexture(TEXTURE8_GRADIENT_TEMP);
27307
27272
  gl.bindTexture(gl.TEXTURE_3D, tempTex3D);
27308
- gl.uniform1i(sobelShader.uniforms.intensityVol, 1);
27273
+ gl.uniform1i(sobelShader.uniforms.intensityVol, 8);
27309
27274
  const sobelRadius = 0.7;
27310
27275
  gl.uniform1f(sobelShader.uniforms.dX, sobelRadius / hdr.dims[1]);
27311
27276
  gl.uniform1f(sobelShader.uniforms.dY, sobelRadius / hdr.dims[2]);
27312
27277
  gl.uniform1f(sobelShader.uniforms.dZ, sobelRadius / hdr.dims[3]);
27313
27278
  gl.uniform1f(sobelShader.uniforms.coordZ, 0.5);
27314
27279
  gl.bindVertexArray(vao2);
27315
- gl.activeTexture(gl.TEXTURE0);
27280
+ gl.activeTexture(TEXTURE0_BACK_VOL);
27316
27281
  if (this.gradientTexture !== null) {
27317
27282
  gl.deleteTexture(this.gradientTexture);
27318
27283
  }
27319
- this.gradientTexture = this.rgbaTex(this.gradientTexture, this.gl.TEXTURE6, hdr.dims);
27284
+ this.gradientTexture = this.rgbaTex(this.gradientTexture, TEXTURE6_GRADIENT, hdr.dims);
27320
27285
  for (let i = 0; i < hdr.dims[3] - 1; i++) {
27321
27286
  const coordZ = 1 / hdr.dims[3] * (i + 0.5);
27322
27287
  gl.uniform1f(sobelShader.uniforms.coordZ, coordZ);
@@ -27498,7 +27463,7 @@ var Niivue = class _Niivue {
27498
27463
  this.back.matRAS = overlayItem.matRAS;
27499
27464
  this.back.dims = overlayItem.dimsRAS;
27500
27465
  this.back.pixDims = overlayItem.pixDimsRAS;
27501
- outTexture = this.rgbaTex(this.volumeTexture, this.gl.TEXTURE0, overlayItem.dimsRAS);
27466
+ outTexture = this.rgbaTex(this.volumeTexture, TEXTURE0_BACK_VOL, overlayItem.dimsRAS);
27502
27467
  const { volScale: volScale2, vox: vox2 } = this.sliceScale(true);
27503
27468
  this.volScale = volScale2;
27504
27469
  this.vox = vox2;
@@ -27547,7 +27512,7 @@ var Niivue = class _Niivue {
27547
27512
  );
27548
27513
  mat46.invert(mtx, mtx);
27549
27514
  if (layer === 1) {
27550
- outTexture = this.rgbaTex(this.overlayTexture, this.gl.TEXTURE2, this.back.dims);
27515
+ outTexture = this.rgbaTex(this.overlayTexture, TEXTURE2_OVERLAY_VOL, this.back.dims);
27551
27516
  this.overlayTexture = outTexture;
27552
27517
  this.overlayTextureID = outTexture;
27553
27518
  } else {
@@ -27560,7 +27525,7 @@ var Niivue = class _Niivue {
27560
27525
  this.gl.viewport(0, 0, this.back.dims[1], this.back.dims[2]);
27561
27526
  this.gl.disable(this.gl.BLEND);
27562
27527
  const tempTex3D = this.gl.createTexture();
27563
- this.gl.activeTexture(this.gl.TEXTURE6);
27528
+ this.gl.activeTexture(TEXTURE9_ORIENT);
27564
27529
  this.gl.bindTexture(this.gl.TEXTURE_3D, tempTex3D);
27565
27530
  this.gl.texParameteri(this.gl.TEXTURE_3D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
27566
27531
  this.gl.texParameteri(this.gl.TEXTURE_3D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
@@ -27709,22 +27674,22 @@ var Niivue = class _Niivue {
27709
27674
  const isUseCopyTexSubImage3D = false;
27710
27675
  if (isUseCopyTexSubImage3D) {
27711
27676
  if (layer > 1) {
27712
- blendTexture = this.rgbaTex(blendTexture, this.gl.TEXTURE5, this.back.dims, true);
27677
+ blendTexture = this.rgbaTex(blendTexture, TEXTURE10_BLEND, this.back.dims, true);
27713
27678
  this.gl.bindTexture(this.gl.TEXTURE_3D, blendTexture);
27714
27679
  for (let i = 0; i < this.back.dims[3]; i++) {
27715
27680
  this.gl.framebufferTextureLayer(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.overlayTexture, 0, i);
27716
- this.gl.activeTexture(this.gl.TEXTURE5);
27681
+ this.gl.activeTexture(TEXTURE10_BLEND);
27717
27682
  this.gl.copyTexSubImage3D(this.gl.TEXTURE_3D, 0, 0, 0, i, 0, 0, this.back.dims[1], this.back.dims[2]);
27718
27683
  }
27719
27684
  } else {
27720
- blendTexture = this.rgbaTex(blendTexture, this.gl.TEXTURE5, [2, 2, 2, 2], true);
27685
+ blendTexture = this.rgbaTex(blendTexture, TEXTURE10_BLEND, [2, 2, 2, 2], true);
27721
27686
  }
27722
27687
  } else {
27723
27688
  if (layer > 1) {
27724
27689
  if (!this.back.dims) {
27725
27690
  throw new Error("back.dims undefined");
27726
27691
  }
27727
- blendTexture = this.rgbaTex(blendTexture, this.gl.TEXTURE5, this.back.dims);
27692
+ blendTexture = this.rgbaTex(blendTexture, TEXTURE10_BLEND, this.back.dims);
27728
27693
  this.gl.bindTexture(this.gl.TEXTURE_3D, blendTexture);
27729
27694
  const passShader = this.passThroughShader;
27730
27695
  passShader.use(this.gl);
@@ -27736,11 +27701,11 @@ var Niivue = class _Niivue {
27736
27701
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
27737
27702
  }
27738
27703
  } else {
27739
- blendTexture = this.rgbaTex(blendTexture, this.gl.TEXTURE5, [2, 2, 2, 2]);
27704
+ blendTexture = this.rgbaTex(blendTexture, TEXTURE10_BLEND, [2, 2, 2, 2]);
27740
27705
  }
27741
27706
  }
27742
27707
  orientShader.use(this.gl);
27743
- this.gl.activeTexture(this.gl.TEXTURE1);
27708
+ this.gl.activeTexture(TEXTURE1_COLORMAPS);
27744
27709
  let colormapLabelTexture = null;
27745
27710
  if (overlayItem.colormapLabel !== null && overlayItem.colormapLabel.lut.length > 7) {
27746
27711
  const nLabel = overlayItem.colormapLabel.max - overlayItem.colormapLabel.min + 1;
@@ -27783,8 +27748,8 @@ var Niivue = class _Niivue {
27783
27748
  this.gl.uniform1f(orientShader.uniforms.cal_minNeg ?? null, mnNeg);
27784
27749
  this.gl.uniform1f(orientShader.uniforms.cal_maxNeg ?? null, mxNeg);
27785
27750
  this.gl.bindTexture(this.gl.TEXTURE_3D, tempTex3D);
27786
- this.gl.uniform1i(orientShader.uniforms.intensityVol ?? null, 6);
27787
- this.gl.uniform1i(orientShader.uniforms.blend3D ?? null, 5);
27751
+ this.gl.uniform1i(orientShader.uniforms.intensityVol ?? null, 9);
27752
+ this.gl.uniform1i(orientShader.uniforms.blend3D ?? null, 10);
27788
27753
  this.gl.uniform1i(orientShader.uniforms.colormap ?? null, 1);
27789
27754
  this.gl.uniform1f(orientShader.uniforms.scl_inter ?? null, hdr.scl_inter);
27790
27755
  this.gl.uniform1f(orientShader.uniforms.scl_slope ?? null, hdr.scl_slope);
@@ -27880,13 +27845,16 @@ var Niivue = class _Niivue {
27880
27845
  if (!this.back.dims) {
27881
27846
  throw new Error("back.dims undefined");
27882
27847
  }
27848
+ let outline = 0;
27883
27849
  if (hdr.intent_code === 1002) {
27884
- let x = 1 / this.back.dims[1];
27885
- if (!this.opts.isAtlasOutline) {
27886
- x = -x;
27887
- }
27888
- this.gl.uniform3fv(orientShader.uniforms.xyzFrac, [x, 1 / this.back.dims[2], 1 / this.back.dims[3]]);
27850
+ outline = this.opts.atlasOutline;
27889
27851
  }
27852
+ this.gl.uniform4fv(orientShader.uniforms.xyzaFrac, [
27853
+ 1 / this.back.dims[1],
27854
+ 1 / this.back.dims[2],
27855
+ 1 / this.back.dims[3],
27856
+ outline
27857
+ ]);
27890
27858
  log.debug("back dims: ", this.back.dims);
27891
27859
  for (let i = 0; i < this.back.dims[3]; i++) {
27892
27860
  const coordZ = 1 / this.back.dims[3] * (i + 0.5);
@@ -27944,11 +27912,11 @@ var Niivue = class _Niivue {
27944
27912
  this.gl.uniform1f(shader.uniforms.drawOpacity, this.drawOpacity);
27945
27913
  if (colormapLabelTexture !== null) {
27946
27914
  this.gl.deleteTexture(colormapLabelTexture);
27947
- this.gl.activeTexture(this.gl.TEXTURE1);
27915
+ this.gl.activeTexture(TEXTURE1_COLORMAPS);
27948
27916
  this.gl.bindTexture(this.gl.TEXTURE_2D, this.colormapTexture);
27949
27917
  }
27950
27918
  this.gl.uniform1i(shader.uniforms.drawing, 7);
27951
- this.gl.activeTexture(this.gl.TEXTURE7);
27919
+ this.gl.activeTexture(TEXTURE7_DRAW);
27952
27920
  this.gl.bindTexture(this.gl.TEXTURE_3D, this.drawTexture);
27953
27921
  this.updateInterpolation(layer);
27954
27922
  }
@@ -28021,7 +27989,7 @@ var Niivue = class _Niivue {
28021
27989
  /**
28022
27990
  * modulate intensity of one image based on intensity of another
28023
27991
  * @param idTarget - the ID of the NVImage to be biased
28024
- * @param idModulation - the ID of the NVImage that controls bias (null to disable modulation)
27992
+ * @param idModulation - the ID of the NVImage that controls bias (empty string to disable modulation)
28025
27993
  * @param modulateAlpha - does the modulation influence alpha transparency (values greater than 1).
28026
27994
  * @example niivue.setModulationImage(niivue.volumes[0].id, niivue.volumes[1].id);
28027
27995
  * @see {@link https://niivue.github.io/niivue/features/modulate.html|live demo scalar usage}
@@ -28030,7 +27998,9 @@ var Niivue = class _Niivue {
28030
27998
  setModulationImage(idTarget, idModulation, modulateAlpha = 0) {
28031
27999
  const idxTarget = this.getVolumeIndexByID(idTarget);
28032
28000
  let idxModulation = null;
28033
- idxModulation = this.getVolumeIndexByID(idModulation);
28001
+ if (idModulation.length > 0) {
28002
+ idxModulation = this.getVolumeIndexByID(idModulation);
28003
+ }
28034
28004
  this.volumes[idxTarget].modulationImage = idxModulation;
28035
28005
  this.volumes[idxTarget].modulateAlpha = modulateAlpha;
28036
28006
  this.updateGLVolume();
@@ -28100,7 +28070,7 @@ var Niivue = class _Niivue {
28100
28070
  */
28101
28071
  getFrame4D(id) {
28102
28072
  const idx = this.getVolumeIndexByID(id);
28103
- return this.volumes[idx].nFrame4D;
28073
+ return this.volumes[idx].frame4D;
28104
28074
  }
28105
28075
  // not included in public docs
28106
28076
  colormapFromKey(name) {
@@ -28121,7 +28091,7 @@ var Niivue = class _Niivue {
28121
28091
  return null;
28122
28092
  }
28123
28093
  texture = this.gl.createTexture();
28124
- this.gl.activeTexture(this.gl.TEXTURE1);
28094
+ this.gl.activeTexture(TEXTURE1_COLORMAPS);
28125
28095
  this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
28126
28096
  this.gl.texStorage2D(this.gl.TEXTURE_2D, 1, this.gl.RGBA8, nCol, nRow);
28127
28097
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
@@ -28761,7 +28731,7 @@ var Niivue = class _Niivue {
28761
28731
  throw new Error("colorbarShader undefined");
28762
28732
  }
28763
28733
  this.colorbarShader.use(this.gl);
28764
- this.gl.activeTexture(this.gl.TEXTURE1);
28734
+ this.gl.activeTexture(TEXTURE1_COLORMAPS);
28765
28735
  this.gl.bindTexture(this.gl.TEXTURE_2D, this.colormapTexture);
28766
28736
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
28767
28737
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
@@ -29004,16 +28974,16 @@ var Niivue = class _Niivue {
29004
28974
  interp = this.gl.NEAREST;
29005
28975
  }
29006
28976
  if (layer === 0) {
29007
- this.gl.activeTexture(this.gl.TEXTURE0);
28977
+ this.gl.activeTexture(TEXTURE0_BACK_VOL);
29008
28978
  } else {
29009
- this.gl.activeTexture(this.gl.TEXTURE2);
28979
+ this.gl.activeTexture(TEXTURE2_OVERLAY_VOL);
29010
28980
  }
29011
28981
  this.gl.texParameteri(this.gl.TEXTURE_3D, this.gl.TEXTURE_MIN_FILTER, interp);
29012
28982
  this.gl.texParameteri(this.gl.TEXTURE_3D, this.gl.TEXTURE_MAG_FILTER, interp);
29013
28983
  }
29014
28984
  // not included in public docs
29015
28985
  setAtlasOutline(isOutline) {
29016
- this.opts.isAtlasOutline = isOutline;
28986
+ this.opts.atlasOutline = isOutline;
29017
28987
  this.updateGLVolume();
29018
28988
  this.drawScene();
29019
28989
  }
@@ -29576,6 +29546,12 @@ var Niivue = class _Niivue {
29576
29546
  if (graph.opacity <= 0 || graph.LTWH[2] <= 5 || graph.LTWH[3] <= 5) {
29577
29547
  return;
29578
29548
  }
29549
+ if (graph.LTWH[0] + graph.LTWH[2] > this.gl.canvas.width) {
29550
+ return;
29551
+ }
29552
+ if (graph.LTWH[1] + graph.LTWH[3] > this.gl.canvas.height) {
29553
+ return;
29554
+ }
29579
29555
  graph.backColor = [0.15, 0.15, 0.15, graph.opacity];
29580
29556
  graph.lineColor = [1, 1, 1, 1];
29581
29557
  if (this.opts.backColor[0] + this.opts.backColor[1] + this.opts.backColor[2] > 1.5) {
@@ -29839,7 +29815,7 @@ var Niivue = class _Niivue {
29839
29815
  shader.use(this.gl);
29840
29816
  gl.uniform1i(shader.uniforms.backgroundMasksOverlays, this.backgroundMasksOverlays);
29841
29817
  if (this.gradientTextureAmount > 0) {
29842
- gl.activeTexture(gl.TEXTURE6);
29818
+ gl.activeTexture(TEXTURE6_GRADIENT);
29843
29819
  gl.bindTexture(gl.TEXTURE_3D, this.gradientTexture);
29844
29820
  const modelMatrix = this.calculateModelMatrix(azimuth, elevation);
29845
29821
  const iModelMatrix = mat46.create();
@@ -30247,7 +30223,7 @@ var Niivue = class _Niivue {
30247
30223
  continue;
30248
30224
  }
30249
30225
  if (shader.isMatcap) {
30250
- gl.activeTexture(gl.TEXTURE6);
30226
+ gl.activeTexture(TEXTURE5_MATCAP);
30251
30227
  gl.bindTexture(gl.TEXTURE_2D, this.matCapTexture);
30252
30228
  }
30253
30229
  gl.bindVertexArray(this.meshes[i].vao);
@@ -30307,7 +30283,7 @@ var Niivue = class _Niivue {
30307
30283
  radius = range[0] * 0.02;
30308
30284
  }
30309
30285
  radius *= this.opts.crosshairWidth;
30310
- this.crosshairs3D = NiivueObject3D.generateCrosshairs(this.gl, 1, mm, mn, mx, radius);
30286
+ this.crosshairs3D = NiivueObject3D.generateCrosshairs(this.gl, 1, mm, mn, mx, radius, 20, this.opts.crosshairGap);
30311
30287
  this.crosshairs3D.mm = mm;
30312
30288
  }
30313
30289
  if (!this.surfaceShader) {