@niivue/niivue 0.41.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.41.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);
@@ -1176,28 +1276,28 @@ var fragGrowCutShader = `#version 300 es
1176
1276
  uniform int finalPass;
1177
1277
  uniform float coordZ;
1178
1278
  uniform lowp sampler3D in3D;
1179
- uniform highp isampler3D inputTexture0; // background
1180
- uniform highp isampler3D inputTexture1; // label
1181
- uniform highp isampler3D inputTexture2; // strength
1279
+ uniform highp isampler3D backTex; // background
1280
+ uniform highp isampler3D labelTex; // label
1281
+ uniform highp isampler3D strengthTex; // strength
1182
1282
  void main(void) {
1183
1283
  vec3 interpolatedTextureCoordinate = vec3(TexCoord.xy, coordZ);
1184
- ivec3 size = textureSize(inputTexture0, 0);
1284
+ ivec3 size = textureSize(backTex, 0);
1185
1285
  ivec3 texelIndex = ivec3(floor(interpolatedTextureCoordinate * vec3(size)));
1186
- int background = texelFetch(inputTexture0, texelIndex, 0).r;
1187
- label = texelFetch(inputTexture1, texelIndex, 0).r;
1188
- 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;
1189
1289
  for (int k = -1; k <= 1; k++) {
1190
1290
  for (int j = -1; j <= 1; j++) {
1191
1291
  for (int i = -1; i <= 1; i++) {
1192
1292
  if (i != 0 && j != 0 && k != 0) {
1193
1293
  ivec3 neighborIndex = texelIndex + ivec3(i,j,k);
1194
- int neighborBackground = texelFetch(inputTexture0, neighborIndex, 0).r;
1195
- int neighborStrength = texelFetch(inputTexture2, neighborIndex, 0).r;
1294
+ int neighborBackground = texelFetch(backTex, neighborIndex, 0).r;
1295
+ int neighborStrength = texelFetch(strengthTex, neighborIndex, 0).r;
1196
1296
  int strengthCost = abs(neighborBackground - background);
1197
1297
  int takeoverStrength = neighborStrength - strengthCost;
1198
1298
  if (takeoverStrength > strength) {
1199
1299
  strength = takeoverStrength;
1200
- label = texelFetch(inputTexture1, neighborIndex, 0).r;
1300
+ label = texelFetch(labelTex, neighborIndex, 0).r;
1201
1301
  }
1202
1302
  }
1203
1303
  }
@@ -1211,7 +1311,7 @@ void main(void) {
1211
1311
  for (int j = -1; j <= 1; j++)
1212
1312
  for (int i = -1; i <= 1; i++) {
1213
1313
  ivec3 neighborIndex = texelIndex + ivec3(i,j,k);
1214
- int ilabel = texelFetch(inputTexture1, neighborIndex, 0).r;
1314
+ int ilabel = texelFetch(labelTex, neighborIndex, 0).r;
1215
1315
  if ((ilabel < 0) || (ilabel > 3))
1216
1316
  ok = 0;
1217
1317
  else
@@ -2816,8 +2916,8 @@ __publicField(_NiivueObject3D, "CULL_FACE", 2);
2816
2916
  __publicField(_NiivueObject3D, "CULL_FRONT", 4);
2817
2917
  __publicField(_NiivueObject3D, "CULL_BACK", 8);
2818
2918
  __publicField(_NiivueObject3D, "ENABLE_DEPTH_TEST", 16);
2819
- __publicField(_NiivueObject3D, "generateCrosshairs", function(gl, id, xyzMM, xyzMin, xyzMax, radius, sides = 20) {
2820
- 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);
2821
2921
  return new _NiivueObject3D(
2822
2922
  id,
2823
2923
  geometry.vertexBuffer,
@@ -2828,18 +2928,40 @@ __publicField(_NiivueObject3D, "generateCrosshairs", function(gl, id, xyzMM, xyz
2828
2928
  );
2829
2929
  });
2830
2930
  // not included in public docs
2831
- __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) {
2832
2932
  const vertices = [];
2833
2933
  const indices = [];
2834
- let start = vec3.fromValues(xyzMin[0], xyzMM[1], xyzMM[2]);
2835
- let dest = vec3.fromValues(xyzMax[0], xyzMM[1], xyzMM[2]);
2836
- _NiivueObject3D.makeCylinder(vertices, indices, start, dest, radius, sides);
2837
- start = vec3.fromValues(xyzMM[0], xyzMin[1], xyzMM[2]);
2838
- dest = vec3.fromValues(xyzMM[0], xyzMax[1], xyzMM[2]);
2839
- _NiivueObject3D.makeCylinder(vertices, indices, start, dest, radius, sides);
2840
- start = vec3.fromValues(xyzMM[0], xyzMM[1], xyzMin[2]);
2841
- dest = vec3.fromValues(xyzMM[0], xyzMM[1], xyzMax[2]);
2842
- _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
+ }
2843
2965
  const vertexBuffer = gl.createBuffer();
2844
2966
  if (vertexBuffer === null) {
2845
2967
  throw new Error("could not instantiate vertex buffer");
@@ -11374,11 +11496,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
11374
11496
  const offsetPt0 = [];
11375
11497
  offsetPt0.push(npt);
11376
11498
  const pts = [];
11377
- const dps = [];
11378
- dps.push({
11379
- id: "tract",
11380
- vals: []
11381
- });
11499
+ const bundleTags = [];
11382
11500
  for (let t = 0; t < n_tracts; t++) {
11383
11501
  line = readStr();
11384
11502
  const new_tracts = readNumericTag("ni_dimen=");
@@ -11398,13 +11516,18 @@ var NVMeshLoaders = class _NVMeshLoaders {
11398
11516
  }
11399
11517
  npt += new_pts;
11400
11518
  offsetPt0.push(npt);
11401
- dps[0].vals.push(bundleTag);
11519
+ bundleTags.push(bundleTag);
11402
11520
  }
11403
11521
  line = readStr();
11404
11522
  }
11523
+ const dps = [];
11524
+ dps.push({
11525
+ id: "tract",
11526
+ vals: Float32Array.from(bundleTags)
11527
+ });
11405
11528
  return {
11406
- pts,
11407
- offsetPt0,
11529
+ pts: new Float32Array(pts),
11530
+ offsetPt0: new Uint32Array(offsetPt0),
11408
11531
  dps
11409
11532
  };
11410
11533
  }
@@ -11493,8 +11616,8 @@ var NVMeshLoaders = class _NVMeshLoaders {
11493
11616
  }
11494
11617
  return mat2;
11495
11618
  }
11496
- let offsetPt0 = [];
11497
- let pts = [];
11619
+ let offsetPt0 = new Uint32Array(0);
11620
+ let pts = new Float32Array(0);
11498
11621
  const mat = readMatV4(buffer);
11499
11622
  if (!("trans_to_mni" in mat)) {
11500
11623
  throw new Error("TT format file must have 'trans_to_mni'");
@@ -11526,8 +11649,6 @@ var NVMeshLoaders = class _NVMeshLoaders {
11526
11649
  m[15]
11527
11650
  );
11528
11651
  mat42.transpose(trans_to_mni, trans_to_mni);
11529
- const zoomMat = mat42.create();
11530
- mat42.transpose(zoomMat, zoomMat);
11531
11652
  function parse_tt(track) {
11532
11653
  const dv = new DataView(track.buffer);
11533
11654
  const pos = [];
@@ -11565,12 +11686,10 @@ var NVMeshLoaders = class _NVMeshLoaders {
11565
11686
  for (let i2 = 0; i2 < npt; i2++) {
11566
11687
  pts[i2] = pts[i2] / 32;
11567
11688
  }
11568
- const vox2mmMat = mat42.create();
11569
- mat42.mul(vox2mmMat, trans_to_mni, zoomMat);
11570
11689
  let v = 0;
11571
11690
  for (let i2 = 0; i2 < npt / 3; i2++) {
11572
11691
  const pos2 = vec42.fromValues(pts[v], pts[v + 1], pts[v + 2], 1);
11573
- vec42.transformMat4(pos2, pos2, vox2mmMat);
11692
+ vec42.transformMat4(pos2, pos2, trans_to_mni);
11574
11693
  pts[v++] = pos2[0];
11575
11694
  pts[v++] = pos2[1];
11576
11695
  pts[v++] = pos2[2];
@@ -11594,7 +11713,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
11594
11713
  }
11595
11714
  let noff = 0;
11596
11715
  let npt = 0;
11597
- let pts = [];
11716
+ let pts = new Float32Array([]);
11598
11717
  const offsetPt0 = [];
11599
11718
  const dpg = [];
11600
11719
  const dps = [];
@@ -11669,31 +11788,21 @@ var NVMeshLoaders = class _NVMeshLoaders {
11669
11788
  if (pname.includes("groups")) {
11670
11789
  dpg.push({
11671
11790
  id: tag,
11672
- vals: Array.from(vals.slice())
11791
+ vals: Float32Array.from(vals.slice())
11673
11792
  });
11674
11793
  continue;
11675
11794
  }
11676
11795
  if (pname.includes("dpv")) {
11677
- let mn = vals[0];
11678
- let mx = vals[0];
11679
- for (let i2 = 1; i2 < vals.length; i2++) {
11680
- mn = Math.min(vals[i2], mn);
11681
- mx = Math.max(vals[i2], mx);
11682
- }
11683
11796
  dpv.push({
11684
11797
  id: tag,
11685
- vals: Array.from(vals.slice()),
11686
- global_min: mn,
11687
- global_max: mx,
11688
- cal_min: mn,
11689
- cal_max: mx
11798
+ vals: Float32Array.from(vals.slice())
11690
11799
  });
11691
11800
  continue;
11692
11801
  }
11693
11802
  if (pname.includes("dps")) {
11694
11803
  dps.push({
11695
11804
  id: tag,
11696
- vals: Array.from(vals.slice())
11805
+ vals: Float32Array.from(vals.slice())
11697
11806
  });
11698
11807
  continue;
11699
11808
  }
@@ -11705,7 +11814,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
11705
11814
  }
11706
11815
  if (fname.startsWith("positions.3.")) {
11707
11816
  npt = nval;
11708
- pts = Array.from(vals.slice());
11817
+ pts = new Float32Array(vals);
11709
11818
  }
11710
11819
  }
11711
11820
  if (noff === 0 || npt === 0) {
@@ -11716,8 +11825,8 @@ var NVMeshLoaders = class _NVMeshLoaders {
11716
11825
  }
11717
11826
  offsetPt0[noff] = npt / 3;
11718
11827
  return {
11719
- pts: Array.from(pts),
11720
- offsetPt0: Array.from(offsetPt0),
11828
+ pts,
11829
+ offsetPt0: new Uint32Array(offsetPt0),
11721
11830
  dpg,
11722
11831
  dps,
11723
11832
  dpv,
@@ -11877,23 +11986,16 @@ var NVMeshLoaders = class _NVMeshLoaders {
11877
11986
  if (vers > 2 || hdr_sz !== 1e3 || magic !== 1128354388) {
11878
11987
  throw new Error("Not a valid TRK file");
11879
11988
  }
11880
- const dps = [];
11881
- const dpv = [];
11882
11989
  const n_scalars = reader.getInt16(36, true);
11883
- if (n_scalars > 0) {
11884
- for (let i2 = 0; i2 < n_scalars; i2++) {
11885
- const arr = new Uint8Array(buffer.slice(38 + i2 * 20, 58 + i2 * 20));
11886
- const str = new TextDecoder().decode(arr).split("\0").shift();
11887
- dpv.push({
11888
- id: str.trim(),
11889
- // TODO can we guarantee this?
11890
- vals: [],
11891
- global_min: 0,
11892
- global_max: 0,
11893
- cal_min: 0,
11894
- cal_max: 0
11895
- });
11896
- }
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
+ });
11897
11999
  }
11898
12000
  const voxel_sizeX = reader.getFloat32(12, true);
11899
12001
  const voxel_sizeY = reader.getFloat32(16, true);
@@ -11917,16 +12019,15 @@ var NVMeshLoaders = class _NVMeshLoaders {
11917
12019
  1
11918
12020
  );
11919
12021
  const n_properties = reader.getInt16(238, true);
11920
- if (n_properties > 0) {
11921
- for (let i2 = 0; i2 < n_properties; i2++) {
11922
- const arr = new Uint8Array(buffer.slice(240 + i2 * 20, 260 + i2 * 20));
11923
- const str = new TextDecoder().decode(arr).split("\0").shift();
11924
- dps.push({
11925
- id: str.trim(),
11926
- // TODO can we guarantee this?
11927
- vals: []
11928
- });
11929
- }
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
+ });
11930
12031
  }
11931
12032
  const mat = mat42.create();
11932
12033
  for (let i2 = 0; i2 < 16; i2++) {
@@ -11938,9 +12039,6 @@ var NVMeshLoaders = class _NVMeshLoaders {
11938
12039
  }
11939
12040
  const vox2mmMat = mat42.create();
11940
12041
  mat42.mul(vox2mmMat, zoomMat, mat);
11941
- vox2mmMat[3] = mat[3];
11942
- vox2mmMat[7] = mat[7];
11943
- vox2mmMat[11] = mat[11];
11944
12042
  let i32 = null;
11945
12043
  let f32 = null;
11946
12044
  i32 = new Int32Array(buffer.slice(hdr_sz));
@@ -11982,15 +12080,19 @@ var NVMeshLoaders = class _NVMeshLoaders {
11982
12080
  }
11983
12081
  }
11984
12082
  }
11985
- if (n_scalars > 0) {
11986
- for (let s = 0; s < n_scalars; s++) {
11987
- const mn = dpv[s].vals.reduce((acc, current) => Math.min(acc, current));
11988
- const mx = dpv[s].vals.reduce((acc, current) => Math.max(acc, current));
11989
- dpv[s].global_min = mn;
11990
- dpv[s].global_max = mx;
11991
- dpv[s].cal_min = mn;
11992
- dpv[s].cal_max = mx;
11993
- }
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
+ });
11994
12096
  }
11995
12097
  offsetPt0[noffset++] = npt;
11996
12098
  pts = pts.slice(0, npt3);
@@ -11998,8 +12100,8 @@ var NVMeshLoaders = class _NVMeshLoaders {
11998
12100
  return {
11999
12101
  pts,
12000
12102
  offsetPt0,
12001
- dps,
12002
- dpv
12103
+ dps: dps32,
12104
+ dpv: dpv32
12003
12105
  };
12004
12106
  }
12005
12107
  // readTRK()
@@ -12157,7 +12259,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12157
12259
  } else {
12158
12260
  throw new Error("Unsupported ASCII VTK datatype " + items[0]);
12159
12261
  }
12160
- const indices = new Int32Array(tris);
12262
+ const indices = new Uint32Array(tris);
12161
12263
  return {
12162
12264
  positions,
12163
12265
  indices
@@ -12203,7 +12305,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12203
12305
  const mx2 = vals.reduce((acc, current) => Math.max(acc, current));
12204
12306
  nvmesh.dpv.push({
12205
12307
  id: tag,
12206
- vals: Array.from(vals.slice()),
12308
+ vals: Float32Array.from(vals.slice()),
12207
12309
  global_min: mn2,
12208
12310
  global_max: mx2,
12209
12311
  cal_min: mn2,
@@ -12615,7 +12717,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12615
12717
  }
12616
12718
  if (ntri < 1) {
12617
12719
  ntri = parseInt(items[0]);
12618
- indices = new Int32Array(ntri * 3);
12720
+ indices = new Uint32Array(ntri * 3);
12619
12721
  continue;
12620
12722
  }
12621
12723
  if (t >= ntri * 3) {
@@ -12671,7 +12773,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12671
12773
  positions[j + 2] = parseFloat(items[2]);
12672
12774
  j += 3;
12673
12775
  }
12674
- const indices = new Int32Array(ntri * 3);
12776
+ const indices = new Uint32Array(ntri * 3);
12675
12777
  j = 0;
12676
12778
  for (let i = 0; i < ntri; i++) {
12677
12779
  line = readStr();
@@ -12853,7 +12955,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12853
12955
  } else {
12854
12956
  throw new Error("Unsupported binary VTK datatype " + items[0]);
12855
12957
  }
12856
- const indices = new Int32Array(tris);
12958
+ const indices = new Uint32Array(tris);
12857
12959
  return {
12858
12960
  positions,
12859
12961
  indices
@@ -12874,7 +12976,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12874
12976
  const nvert = reader.getUint32(28, true);
12875
12977
  const vcoffset = reader.getUint32(48, true);
12876
12978
  let pos = hdrBytes;
12877
- const indices = new Int32Array(buffer, pos, nface * 3);
12979
+ const indices = new Uint32Array(buffer, pos, nface * 3);
12878
12980
  pos += nface * 3 * 4;
12879
12981
  const positions = new Float32Array(buffer, pos, nvert * 3);
12880
12982
  for (let i = 0; i < nvert * 3; i += 3) {
@@ -12957,7 +13059,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12957
13059
  let filepos = 16 + nskip;
12958
13060
  let indices = null;
12959
13061
  if (isFace) {
12960
- indices = new Int32Array(_buffer, filepos, nface * 3);
13062
+ indices = new Uint32Array(_buffer, filepos, nface * 3);
12961
13063
  filepos += nface * 3 * 4;
12962
13064
  }
12963
13065
  let positions = null;
@@ -13111,7 +13213,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13111
13213
  positions2[v + 2] = parseFloat(items[2]);
13112
13214
  v += 3;
13113
13215
  }
13114
- let indices2 = new Int32Array(nface * 3);
13216
+ let indices2 = new Uint32Array(nface * 3);
13115
13217
  let f = 0;
13116
13218
  for (let i = 0; i < nface; i++) {
13117
13219
  line = readStr();
@@ -13121,7 +13223,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13121
13223
  break;
13122
13224
  }
13123
13225
  if (f + nTri * 3 > indices2.length) {
13124
- const c = new Int32Array(indices2.length + indices2.length);
13226
+ const c = new Uint32Array(indices2.length + indices2.length);
13125
13227
  c.set(indices2);
13126
13228
  indices2 = c.slice();
13127
13229
  }
@@ -13171,7 +13273,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13171
13273
  pos += vertStride;
13172
13274
  }
13173
13275
  }
13174
- const indices = new Int32Array(nface * 3);
13276
+ const indices = new Uint32Array(nface * 3);
13175
13277
  let isTriangular = true;
13176
13278
  let j = 0;
13177
13279
  if (indexCountBytes === 1 && indexBytes === 4 && indexStrideBytes === 13) {
@@ -13262,7 +13364,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13262
13364
  header = lines[line].trim().split(/\s+/);
13263
13365
  line++;
13264
13366
  const num_f = parseInt(header[0]);
13265
- const indices = new Int32Array(num_f * 3);
13367
+ const indices = new Uint32Array(num_f * 3);
13266
13368
  for (let i = 0; i < num_f; i++) {
13267
13369
  const items = lines[line].trim().split(/\s+/);
13268
13370
  line++;
@@ -13347,7 +13449,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13347
13449
  }
13348
13450
  }
13349
13451
  const positions = new Float32Array(pts);
13350
- const indices = new Int32Array(t);
13452
+ const indices = new Uint32Array(t);
13351
13453
  return {
13352
13454
  positions,
13353
13455
  indices
@@ -13393,7 +13495,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13393
13495
  i++;
13394
13496
  }
13395
13497
  const positions = new Float32Array(pts);
13396
- const indices = new Int32Array(t);
13498
+ const indices = new Uint32Array(t);
13397
13499
  return {
13398
13500
  positions,
13399
13501
  indices
@@ -13429,7 +13531,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13429
13531
  j += num_c * 4;
13430
13532
  j += nTri;
13431
13533
  const nTriX3 = nTri * 3;
13432
- const indices = new Int32Array(nTriX3);
13534
+ const indices = new Uint32Array(nTriX3);
13433
13535
  for (let i = 0; i < nTriX3; i++) {
13434
13536
  indices[i] = parseInt(items[j++]);
13435
13537
  }
@@ -13478,7 +13580,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13478
13580
  }
13479
13581
  }
13480
13582
  const positions = new Float32Array(pts);
13481
- const indices = new Int32Array(t);
13583
+ const indices = new Uint32Array(t);
13482
13584
  return {
13483
13585
  positions,
13484
13586
  indices
@@ -13513,7 +13615,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13513
13615
  offset += 4;
13514
13616
  }
13515
13617
  nf *= 3;
13516
- const indices = new Int32Array(nf);
13618
+ const indices = new Uint32Array(nf);
13517
13619
  for (let i = 0; i < nf; i++) {
13518
13620
  indices[i] = view.getUint32(offset, false);
13519
13621
  offset += 4;
@@ -13630,7 +13732,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13630
13732
  const nNearest = reader.getUint32(pos, true);
13631
13733
  pos += 4 + 4 * nNearest;
13632
13734
  }
13633
- const indices = new Int32Array(nTri * 3);
13735
+ const indices = new Uint32Array(nTri * 3);
13634
13736
  for (let i = 0; i < nTri * 3; i++) {
13635
13737
  indices[i] = reader.getInt32(pos, true);
13636
13738
  pos += 4;
@@ -13669,7 +13771,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13669
13771
  throw new Error("Unable to parse ASCII STL file.");
13670
13772
  }
13671
13773
  const positions = new Float32Array(pts);
13672
- const indices = new Int32Array(npts);
13774
+ const indices = new Uint32Array(npts);
13673
13775
  for (let i = 0; i < npts; i++) {
13674
13776
  indices[i] = i;
13675
13777
  }
@@ -13695,7 +13797,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13695
13797
  if (buffer.byteLength < 80 + 4 + ntri * 50) {
13696
13798
  throw new Error("STL file too small to store triangles = " + ntri);
13697
13799
  }
13698
- const indices = new Int32Array(ntri3);
13800
+ const indices = new Uint32Array(ntri3);
13699
13801
  const positions = new Float32Array(ntri3 * 3);
13700
13802
  let pos = 80 + 4 + 12;
13701
13803
  let v = 0;
@@ -13790,7 +13892,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13790
13892
  let indexCount = 0;
13791
13893
  let surfaceNumberOfVertices = 0;
13792
13894
  let brainStructure = "";
13793
- let vertexIndices = new Int32Array();
13895
+ let vertexIndices = new Uint32Array();
13794
13896
  const bytes = new Uint8Array(buffer);
13795
13897
  let pos = 552;
13796
13898
  let line;
@@ -13840,7 +13942,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13840
13942
  if (items.length < indexCount) {
13841
13943
  log.error("Error parsing VertexIndices");
13842
13944
  }
13843
- vertexIndices = new Int32Array(indexCount);
13945
+ vertexIndices = new Uint32Array(indexCount);
13844
13946
  for (let i = 0; i < indexCount; i++) {
13845
13947
  vertexIndices[i] = parseInt(items[i]);
13846
13948
  }
@@ -14372,8 +14474,8 @@ var NVMeshLoaders = class _NVMeshLoaders {
14372
14474
  }
14373
14475
  return {
14374
14476
  positions: Float32Array.from(positions),
14375
- indices: Int32Array.from(indices),
14376
- rgba255
14477
+ indices: Uint32Array.from(indices),
14478
+ rgba255: Uint8Array.from(rgba255)
14377
14479
  };
14378
14480
  }
14379
14481
  // readX3D()
@@ -14441,7 +14543,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
14441
14543
  }
14442
14544
  len = tag.contentEndPos;
14443
14545
  let positions = new Float32Array();
14444
- let indices = new Int32Array();
14546
+ let indices = new Uint32Array();
14445
14547
  let scalars = new Float32Array();
14446
14548
  let anatomicalStructurePrimary = "";
14447
14549
  let isIdx = false;
@@ -14568,7 +14670,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
14568
14670
  if (dataType !== 8) {
14569
14671
  log.warn("expect indices as INT32");
14570
14672
  }
14571
- indices = new Int32Array(datBin.buffer);
14673
+ indices = new Uint32Array(datBin.buffer);
14572
14674
  if (isColMajor) {
14573
14675
  const tmp = indices.slice();
14574
14676
  const np = tmp.length / 3;
@@ -14701,7 +14803,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
14701
14803
 
14702
14804
  // src/nvmesh.ts
14703
14805
  var NVMeshFromUrlOptions = class {
14704
- 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) {
14705
14807
  __publicField(this, "url");
14706
14808
  __publicField(this, "gl");
14707
14809
  __publicField(this, "name");
@@ -14736,7 +14838,7 @@ var NVMesh = class _NVMesh {
14736
14838
  * @param colorbarVisible - does this mesh display a colorbar
14737
14839
  * @param anatomicalStructurePrimary - region for mesh. Default is an empty string
14738
14840
  */
14739
- 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 = "") {
14740
14842
  __publicField(this, "id");
14741
14843
  __publicField(this, "name");
14742
14844
  __publicField(this, "anatomicalStructurePrimary");
@@ -14784,6 +14886,7 @@ var NVMesh = class _NVMesh {
14784
14886
  __publicField(this, "vertexCount", 1);
14785
14887
  __publicField(this, "nodeScale", 4);
14786
14888
  __publicField(this, "edgeScale", 1);
14889
+ __publicField(this, "legendLineThickness", 0);
14787
14890
  __publicField(this, "nodeColormap", "warm");
14788
14891
  __publicField(this, "edgeColormap", "warm");
14789
14892
  __publicField(this, "nodeColormapNegative");
@@ -14846,6 +14949,15 @@ var NVMesh = class _NVMesh {
14846
14949
  this.dpg = dpg;
14847
14950
  this.dps = dps;
14848
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
+ }
14849
14961
  this.offsetPt0 = tris;
14850
14962
  this.updateFibers(gl);
14851
14963
  gl.bindVertexArray(this.vaoFiber);
@@ -14870,6 +14982,17 @@ var NVMesh = class _NVMesh {
14870
14982
  this.tris = tris;
14871
14983
  this.updateMesh(gl);
14872
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
+ }
14873
14996
  // given streamlines (which webGL renders as a single pixel), extrude to cylinders
14874
14997
  linesToCylinders(gl, posClrF32, indices) {
14875
14998
  function v4ToV3(v4) {
@@ -14965,7 +15088,7 @@ var NVMesh = class _NVMesh {
14965
15088
  }
14966
15089
  }
14967
15090
  const nidx = (n_line_vtx - n_streamlines) * cyl_sides * 2 * 3;
14968
- const idxs = new Int32Array(nidx);
15091
+ const idxs = new Uint32Array(nidx);
14969
15092
  let idx = 0;
14970
15093
  vtx = 0;
14971
15094
  for (let i = 1; i < n_count; i++) {
@@ -14997,9 +15120,9 @@ var NVMesh = class _NVMesh {
14997
15120
  vtx += cyl_sides;
14998
15121
  }
14999
15122
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
15000
- 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);
15001
15124
  gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
15002
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(f32), gl.STATIC_DRAW);
15125
+ gl.bufferData(gl.ARRAY_BUFFER, Float32Array.from(f32), gl.STATIC_DRAW);
15003
15126
  this.indexCount = nidx;
15004
15127
  }
15005
15128
  // linesToCylinders
@@ -15014,7 +15137,7 @@ var NVMesh = class _NVMesh {
15014
15137
  const n_count = offsetPt0.length - 1;
15015
15138
  const npt = pts.length / 3;
15016
15139
  if (!this.fiberLengths) {
15017
- this.fiberLengths = [];
15140
+ this.fiberLengths = new Uint32Array(n_count);
15018
15141
  for (let i = 0; i < n_count; i++) {
15019
15142
  const vStart3 = offsetPt0[i] * 3;
15020
15143
  const vEnd3 = (offsetPt0[i + 1] - 1) * 3;
@@ -15023,7 +15146,7 @@ var NVMesh = class _NVMesh {
15023
15146
  const v = vec34.fromValues(pts[j + 0] - pts[j + 3], pts[j + 1] - pts[j + 4], pts[j + 2] - pts[j + 5]);
15024
15147
  len += vec34.len(v);
15025
15148
  }
15026
- this.fiberLengths.push(len);
15149
+ this.fiberLengths[i] = len;
15027
15150
  }
15028
15151
  }
15029
15152
  const posClrF32 = new Float32Array(npt * 4);
@@ -15236,119 +15359,12 @@ var NVMesh = class _NVMesh {
15236
15359
  } else {
15237
15360
  this.indexCount = indices.length;
15238
15361
  gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
15239
- gl.bufferData(gl.ARRAY_BUFFER, new Uint32Array(posClrU32), gl.STATIC_DRAW);
15362
+ gl.bufferData(gl.ARRAY_BUFFER, Uint32Array.from(posClrU32), gl.STATIC_DRAW);
15240
15363
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
15241
- 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);
15242
15365
  }
15243
15366
  }
15244
15367
  // updateFibers()
15245
- // internal function filters connectome to identify which color, size and visibility of nodes and edges
15246
- updateConnectome(gl) {
15247
- const tris = [];
15248
- const nNode = this.nodes.X.length;
15249
- let hasEdges = false;
15250
- if (nNode > 1 && this.edges) {
15251
- let nEdges = this.edges.length;
15252
- if (nEdges = nNode * nNode) {
15253
- hasEdges = true;
15254
- } else {
15255
- log.warn("Expected %d edges not %d", nNode * nNode, nEdges);
15256
- }
15257
- }
15258
- const pts = [];
15259
- const rgba255 = [];
15260
- let lut = cmapper.colormap(this.nodeColormap, this.colormapInvert);
15261
- let lutNeg = cmapper.colormap(this.nodeColormapNegative, this.colormapInvert);
15262
- let hasNeg = this.nodeColormapNegative !== void 0;
15263
- let min = this.nodeMinColor;
15264
- let max = this.nodeMaxColor;
15265
- for (let i = 0; i < nNode; i++) {
15266
- const radius = this.nodes.Size[i] * this.nodeScale;
15267
- if (radius <= 0) {
15268
- continue;
15269
- }
15270
- let color = this.nodes.Color[i];
15271
- let isNeg = false;
15272
- if (hasNeg && color < 0) {
15273
- isNeg = true;
15274
- color = -color;
15275
- }
15276
- if (min < max) {
15277
- if (color < min) {
15278
- continue;
15279
- }
15280
- color = (color - min) / (max - min);
15281
- } else {
15282
- color = 1;
15283
- }
15284
- color = Math.round(Math.max(Math.min(255, color * 255))) * 4;
15285
- let rgba = [lut[color], lut[color + 1], lut[color + 2], 255];
15286
- if (isNeg) {
15287
- rgba = [lutNeg[color], lutNeg[color + 1], lutNeg[color + 2], 255];
15288
- }
15289
- const pt = [
15290
- this.nodes.X[i],
15291
- this.nodes.Y[i],
15292
- this.nodes.Z[i]
15293
- ];
15294
- NiivueObject3D.makeColoredSphere(pts, tris, rgba255, radius, pt, rgba);
15295
- }
15296
- if (hasEdges) {
15297
- lut = cmapper.colormap(this.edgeColormap, this.colormapInvert);
15298
- lutNeg = cmapper.colormap(this.edgeColormapNegative, this.colormapInvert);
15299
- hasNeg = this.edgeColormapNegative !== void 0;
15300
- min = this.edgeMin;
15301
- max = this.edgeMax;
15302
- for (let i = 0; i < nNode - 1; i++) {
15303
- for (let j = i + 1; j < nNode; j++) {
15304
- let color = this.edges[i * nNode + j];
15305
- let isNeg = false;
15306
- if (hasNeg && color < 0) {
15307
- isNeg = true;
15308
- color = -color;
15309
- }
15310
- const radius = color * this.edgeScale;
15311
- if (radius <= 0) {
15312
- continue;
15313
- }
15314
- if (min < max) {
15315
- if (color < min) {
15316
- continue;
15317
- }
15318
- color = (color - min) / (max - min);
15319
- } else {
15320
- color = 1;
15321
- }
15322
- color = Math.round(Math.max(Math.min(255, color * 255))) * 4;
15323
- let rgba = [lut[color], lut[color + 1], lut[color + 2], 255];
15324
- if (isNeg) {
15325
- rgba = [lutNeg[color], lutNeg[color + 1], lutNeg[color + 2], 255];
15326
- }
15327
- const pti = [
15328
- this.nodes.X[i],
15329
- this.nodes.Y[i],
15330
- this.nodes.Z[i]
15331
- ];
15332
- const ptj = [
15333
- this.nodes.X[j],
15334
- this.nodes.Y[j],
15335
- this.nodes.Z[j]
15336
- ];
15337
- NiivueObject3D.makeColoredCylinder(pts, tris, rgba255, pti, ptj, radius, rgba);
15338
- }
15339
- }
15340
- }
15341
- const obj = NVMeshUtilities.getExtents(pts);
15342
- this.furthestVertexFromOrigin = obj.mxDx;
15343
- this.extentsMin = obj.extentsMin;
15344
- this.extentsMax = obj.extentsMax;
15345
- const posNormClr = this.generatePosNormClr(pts, tris, rgba255);
15346
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
15347
- gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int32Array(tris), gl.STATIC_DRAW);
15348
- gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
15349
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(posNormClr), gl.STATIC_DRAW);
15350
- this.indexCount = tris.length;
15351
- }
15352
15368
  // given X,Y,Z coordinates in world space, return index of nearest vertex as well as
15353
15369
  // the distance of this closest vertex to the coordinates
15354
15370
  indexNearestXYZmm(Xmm, Ymm, Zmm) {
@@ -15376,7 +15392,6 @@ var NVMesh = class _NVMesh {
15376
15392
  return;
15377
15393
  }
15378
15394
  if (this.hasConnectome) {
15379
- this.updateConnectome(gl);
15380
15395
  return;
15381
15396
  }
15382
15397
  if (!this.pts || !this.tris || !this.rgba255) {
@@ -15649,9 +15664,9 @@ var NVMesh = class _NVMesh {
15649
15664
  }
15650
15665
  }
15651
15666
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
15652
- 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);
15653
15668
  gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
15654
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(posNormClr), gl.STATIC_DRAW);
15669
+ gl.bufferData(gl.ARRAY_BUFFER, Float32Array.from(posNormClr), gl.STATIC_DRAW);
15655
15670
  this.indexCount = this.tris.length;
15656
15671
  this.vertexCount = this.pts.length;
15657
15672
  }
@@ -15738,75 +15753,10 @@ var NVMesh = class _NVMesh {
15738
15753
  }
15739
15754
  return f32;
15740
15755
  }
15741
- static loadConnectomeFromFreeSurfer(json, gl, name = "", opacity = 1, visible = true) {
15742
- let isValid = true;
15743
- if (json.data_type === void 0) {
15744
- isValid = false;
15745
- } else if (json.data_type !== "fs_pointset") {
15746
- isValid = false;
15747
- }
15748
- if (json.points === void 0) {
15749
- isValid = false;
15750
- }
15751
- if (!isValid) {
15752
- throw Error("not a valid FreeSurfer json pointset");
15753
- }
15754
- const jcon = {
15755
- nodes: {
15756
- names: [],
15757
- prefilled: [],
15758
- X: [],
15759
- Y: [],
15760
- Z: [],
15761
- Color: [],
15762
- Size: []
15763
- },
15764
- edges: [],
15765
- // @ts-expect-error not sure where this should come from
15766
- name: this.data_type
15767
- };
15768
- if (!json.points) {
15769
- throw new Error("points are not set!");
15770
- }
15771
- for (let i = 0; i < json.points.length; i++) {
15772
- let name2 = "";
15773
- if (json.points[i].comments) {
15774
- if (json.points[i].comments[0].text) {
15775
- name2 = json.points[i].comments[0].text;
15776
- }
15777
- }
15778
- jcon.nodes.names.push(name2);
15779
- let prefilled = "";
15780
- if (json.points[i].comments) {
15781
- if (json.points[i].comments[0].prefilled) {
15782
- prefilled = json.points[i].comments[0].prefilled;
15783
- }
15784
- }
15785
- jcon.nodes.prefilled.push(prefilled);
15786
- jcon.nodes.X.push(json.points[i].coordinates.x);
15787
- jcon.nodes.Y.push(json.points[i].coordinates.y);
15788
- jcon.nodes.Z.push(json.points[i].coordinates.z);
15789
- jcon.nodes.Color.push(1);
15790
- jcon.nodes.Size.push(1);
15791
- }
15792
- return new _NVMesh([], [], name, [], opacity, visible, gl, jcon);
15793
- }
15794
- // loadConnectomeFromFreeSurfer
15795
- // read connectome saved as JSON
15796
- static loadConnectomeFromJSON(json, gl, name = "", opacity = 1, visible = true) {
15797
- if ("name" in json) {
15798
- name = this.name;
15799
- }
15800
- if (!("nodes" in json)) {
15801
- throw Error("not a valid jcon connectome file");
15802
- }
15803
- return new _NVMesh([], [], name, [], opacity, visible, gl, json);
15804
- }
15805
- // loadConnectomeFromJSON()
15806
15756
  // wrapper to read meshes, tractograms and connectomes regardless of format
15807
- static readMesh(buffer, name, gl, opacity = 1, rgba255 = [255, 255, 255, 255], visible = true) {
15808
- let tris = [];
15809
- 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([]);
15810
15760
  let anatomicalStructurePrimary = "";
15811
15761
  let obj;
15812
15762
  const re = /(?:\.([^.]+))?$/;
@@ -15817,12 +15767,12 @@ var NVMesh = class _NVMesh {
15817
15767
  ext = ext.toUpperCase();
15818
15768
  }
15819
15769
  if (ext === "JCON") {
15820
- 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");
15821
15771
  }
15822
15772
  if (ext === "JSON") {
15823
- 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");
15824
15774
  }
15825
- rgba255[3] = Math.max(0, rgba255[3]);
15775
+ rgba255[3] = Math.max(1, rgba255[3]);
15826
15776
  if (ext === "TCK" || ext === "TRK" || ext === "TT" || ext === "TRX" || ext === "TRACT") {
15827
15777
  if (ext === "TCK") {
15828
15778
  obj = NVMeshLoaders.readTCK(buffer);
@@ -15836,12 +15786,12 @@ var NVMesh = class _NVMesh {
15836
15786
  obj = NVMeshLoaders.readTRK(buffer);
15837
15787
  }
15838
15788
  if (typeof obj === "undefined") {
15839
- const pts2 = [0, 0, 0, 0, 0, 0];
15840
- const offsetPt0 = [0];
15789
+ const pts2 = new Float32Array([0, 0, 0, 0, 0, 0]);
15790
+ const offsetPt0 = new Uint32Array([0]);
15841
15791
  obj = { pts: pts2, offsetPt0 };
15842
15792
  log.error("Creating empty tracts");
15843
15793
  }
15844
- rgba255[3] = -1;
15794
+ rgba255[3] = 0;
15845
15795
  return new _NVMesh(
15846
15796
  obj.pts,
15847
15797
  obj.offsetPt0,
@@ -15889,10 +15839,10 @@ var NVMesh = class _NVMesh {
15889
15839
  } else if (ext === "FIB" || ext === "VTK") {
15890
15840
  obj = NVMeshLoaders.readVTK(buffer);
15891
15841
  if ("offsetPt0" in obj) {
15892
- rgba255[3] = -1;
15842
+ rgba255[3] = 0;
15893
15843
  return new _NVMesh(
15894
- Array.from(obj.pts),
15895
- Array.from(obj.offsetPt0),
15844
+ obj.pts,
15845
+ obj.offsetPt0,
15896
15846
  name,
15897
15847
  rgba255,
15898
15848
  // colormap,
@@ -15923,20 +15873,21 @@ var NVMesh = class _NVMesh {
15923
15873
  if (!obj.indices) {
15924
15874
  throw new Error("indices not loaded");
15925
15875
  }
15926
- pts = Array.from(obj.positions);
15927
- tris = Array.from(obj.indices);
15876
+ pts = obj.positions;
15877
+ tris = obj.indices;
15928
15878
  if ("rgba255" in obj && obj.rgba255.length > 0) {
15929
15879
  rgba255 = obj.rgba255;
15930
15880
  }
15931
15881
  if ("colors" in obj && obj.colors && obj.colors.length === pts.length) {
15932
- rgba255 = [];
15933
15882
  const n = pts.length / 3;
15883
+ rgba255 = new Uint8Array(n * 4);
15934
15884
  let c = 0;
15885
+ let k = 0;
15935
15886
  for (let i = 0; i < n; i++) {
15936
- rgba255.push(obj.colors[c] * 255);
15937
- rgba255.push(obj.colors[c + 1] * 255);
15938
- rgba255.push(obj.colors[c + 2] * 255);
15939
- 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;
15940
15891
  c += 3;
15941
15892
  }
15942
15893
  }
@@ -15945,6 +15896,7 @@ var NVMesh = class _NVMesh {
15945
15896
  if (ntri < 1 || npt < 3) {
15946
15897
  throw new Error("Mesh should have at least one triangle and three vertices");
15947
15898
  }
15899
+ rgba255[3] = Math.max(1, rgba255[3]);
15948
15900
  const nvm = new _NVMesh(
15949
15901
  pts,
15950
15902
  tris,
@@ -16088,7 +16040,7 @@ var NVMesh = class _NVMesh {
16088
16040
  throw Error(response.statusText);
16089
16041
  }
16090
16042
  const buffer = await response.arrayBuffer();
16091
- 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);
16092
16044
  if (!layers || layers.length < 1) {
16093
16045
  return nvmesh;
16094
16046
  }
@@ -16131,7 +16083,7 @@ var NVMesh = class _NVMesh {
16131
16083
  throw new Error("rendering context must be set");
16132
16084
  }
16133
16085
  const buffer = await _NVMesh.readFileAsync(file);
16134
- const nvmesh = _NVMesh.readMesh(buffer, name, gl, opacity, rgba255, visible);
16086
+ const nvmesh = _NVMesh.readMesh(buffer, name, gl, opacity, new Uint8Array(rgba255), visible);
16135
16087
  if (!layers || layers.length < 1) {
16136
16088
  return nvmesh;
16137
16089
  }
@@ -16169,7 +16121,7 @@ var NVMesh = class _NVMesh {
16169
16121
  return bytes.buffer;
16170
16122
  }
16171
16123
  const buffer = base64ToArrayBuffer(base64);
16172
- 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);
16173
16125
  if (!layers || layers.length < 1) {
16174
16126
  return nvmesh;
16175
16127
  }
@@ -16259,7 +16211,7 @@ var NVMesh = class _NVMesh {
16259
16211
  return NVMeshLoaders.readTSF(buffer);
16260
16212
  }
16261
16213
  static readTT(buffer) {
16262
- return NVMeshLoaders.readTRX(buffer);
16214
+ return NVMeshLoaders.readTT(buffer);
16263
16215
  }
16264
16216
  static readTRX(buffer) {
16265
16217
  return NVMeshLoaders.readTRX(buffer);
@@ -20962,7 +20914,7 @@ var NVImage = class _NVImage {
20962
20914
  */
20963
20915
  clone() {
20964
20916
  const clonedImage = new _NVImage();
20965
- clonedImage.id = this.id;
20917
+ clonedImage.id = uuidv42();
20966
20918
  clonedImage.hdr = Object.assign({}, this.hdr);
20967
20919
  clonedImage.img = this.img.slice();
20968
20920
  clonedImage.calculateRAS();
@@ -21393,6 +21345,7 @@ var DEFAULT_OPTIONS = {
21393
21345
  textHeight: 0.06,
21394
21346
  colorbarHeight: 0.05,
21395
21347
  crosshairWidth: 1,
21348
+ crosshairGap: 0,
21396
21349
  rulerWidth: 4,
21397
21350
  show3Dcrosshair: false,
21398
21351
  backColor: [0, 0, 0, 1],
@@ -21772,7 +21725,7 @@ var NVDocument = class _NVDocument {
21772
21725
  pts: mesh.pts,
21773
21726
  tris: mesh.tris,
21774
21727
  name: mesh.name,
21775
- rgba255: mesh.rgba255,
21728
+ rgba255: Uint8Array.from(mesh.rgba255),
21776
21729
  opacity: mesh.opacity,
21777
21730
  connectome: mesh.connectome,
21778
21731
  dpg: mesh.dpg,
@@ -21809,6 +21762,7 @@ var NVDocument = class _NVDocument {
21809
21762
  nodeMaxColor: mesh.nodeMaxColor,
21810
21763
  nodeMinColor: mesh.nodeMinColor,
21811
21764
  nodeScale: mesh.nodeScale,
21765
+ legendLineThickness: mesh.legendLineThickness,
21812
21766
  offsetPt0: mesh.offsetPt0,
21813
21767
  nodes: mesh.nodes
21814
21768
  };
@@ -21952,14 +21906,14 @@ var defaultOptions = {
21952
21906
  edgeColormapNegative: "winter",
21953
21907
  edgeMin: 2,
21954
21908
  edgeMax: 6,
21955
- edgeScale: 1
21909
+ edgeScale: 1,
21910
+ legendLineThickness: 0
21956
21911
  };
21957
21912
  var NVConnectome = class _NVConnectome extends NVMesh {
21958
21913
  constructor(gl, connectome) {
21959
- super([], [], connectome.name, [], 1, true, gl, connectome);
21914
+ super(new Float32Array([]), new Uint32Array([]), connectome.name, new Uint8Array([]), 1, true, gl, connectome);
21960
21915
  __publicField(this, "gl");
21961
21916
  __publicField(this, "nodesChanged");
21962
- __publicField(this, "legendLineThickness");
21963
21917
  this.gl = gl;
21964
21918
  this.type = "connectome" /* CONNECTOME */;
21965
21919
  if (this.nodes) {
@@ -22235,19 +22189,22 @@ var NVConnectome = class _NVConnectome extends NVMesh {
22235
22189
  const ptj = vec38.fromValues(nodes[edge.second].x, nodes[edge.second].y, nodes[edge.second].z);
22236
22190
  NiivueObject3D.makeColoredCylinder(pts, tris, rgba255, pti, ptj, radius, rgba);
22237
22191
  }
22238
- const obj = NVMeshUtilities.getExtents(pts);
22192
+ const pts32 = new Float32Array(pts);
22193
+ const tris32 = new Uint32Array(tris);
22194
+ const obj = NVMeshUtilities.getExtents(pts32);
22239
22195
  this.furthestVertexFromOrigin = obj.mxDx;
22240
22196
  this.extentsMin = obj.extentsMin;
22241
22197
  this.extentsMax = obj.extentsMax;
22242
- const posNormClr = this.generatePosNormClr(pts, tris, rgba255);
22198
+ const posNormClr = this.generatePosNormClr(pts32, tris32, new Uint8Array(rgba255));
22243
22199
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
22244
- 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);
22245
22201
  gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
22246
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(posNormClr), gl.STATIC_DRAW);
22202
+ gl.bufferData(gl.ARRAY_BUFFER, Float32Array.from(posNormClr), gl.STATIC_DRAW);
22247
22203
  this.indexCount = tris.length;
22248
22204
  }
22249
22205
  updateMesh(gl) {
22250
22206
  this.updateConnectome(gl);
22207
+ this.updateLabels();
22251
22208
  }
22252
22209
  json() {
22253
22210
  const json = {};
@@ -23048,6 +23005,22 @@ var MESH_EXTENSIONS = [
23048
23005
  var LEFT_MOUSE_BUTTON = 0;
23049
23006
  var CENTER_MOUSE_BUTTON = 1;
23050
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;
23051
23024
  var defaultSaveImageOptions = {
23052
23025
  filename: "",
23053
23026
  isSaveDrawing: false,
@@ -23177,13 +23150,7 @@ var Niivue = class _Niivue {
23177
23150
  dragEnd: [0, 0],
23178
23151
  dragClipPlaneStartDepthAziElev: [0, 0, 0],
23179
23152
  lastTwoTouchDistance: 0,
23180
- multiTouchGesture: false,
23181
- loading$: new Subject()
23182
- // whether or not the scene is loading
23183
- });
23184
- // mapping of keys (event strings) to rxjs subjects
23185
- __publicField(this, "eventsToSubjects", {
23186
- loading: this.uiData.loading$
23153
+ multiTouchGesture: false
23187
23154
  });
23188
23155
  __publicField(this, "back", null);
23189
23156
  // base layer; defines image space to work in. Defined as this.volumes[0] in Niivue.loadVolumes
@@ -23472,8 +23439,6 @@ var Niivue = class _Niivue {
23472
23439
  __publicField(this, "initialized", false);
23473
23440
  __publicField(this, "currentDrawUndoBitmap");
23474
23441
  __publicField(this, "loadingText");
23475
- // rxjs subscriptions. Keeping a reference array like this allows us to unsubscribe later
23476
- __publicField(this, "subscriptions", []);
23477
23442
  for (const name in options) {
23478
23443
  if (typeof options[name] === "function") {
23479
23444
  this[name] = options[name];
@@ -23566,57 +23531,6 @@ var Niivue = class _Niivue {
23566
23531
  log.debug("attached to element with id: ", id);
23567
23532
  return this;
23568
23533
  }
23569
- // on handles matching event strings (event) with know rxjs subjects within NiiVue.
23570
- // if the event string exists (e.g. 'location') then the corresponding rxjs subject reference
23571
- // is extracted from this.eventsToSubjects and the callback passed as the second argument to NiiVue.on
23572
- // is added to the subsciptions to the next method. These callbacks are called whenever subject.next is called within
23573
- // various NiiVue methods.
23574
- /**
23575
- * register a callback function to run when known Niivue events happen
23576
- * @param event - the name of the event to watch for. Event names are shown in the type column
23577
- * @param callback - the function to call when the event happens
23578
- * @example
23579
- * niivue = new Niivue()
23580
- *
23581
- * // 'location' update event is fired when the crosshair changes position via user input
23582
- * function doSomethingWithLocationData(data)\{
23583
- * // data has the shape \{mm: [N, N, N], vox: [N, N, N], frac: [N, N, N], values: this.volumes.map(v =\> \{return val\})\}
23584
- * //...
23585
- * \}
23586
- */
23587
- on(event, callback) {
23588
- const knownEvents = Object.keys(this.eventsToSubjects);
23589
- if (!knownEvents.includes(event)) {
23590
- return;
23591
- }
23592
- const subject = this.eventsToSubjects[event];
23593
- const subscription = subject.subscribe({
23594
- next: (data) => callback(data)
23595
- });
23596
- this.subscriptions.push({ [event]: subscription });
23597
- }
23598
- /**
23599
- * off unsubscribes events and subjects (the opposite of on)
23600
- * @param event - the name of the event to watch for. Event names are shown in the type column
23601
- * @example
23602
- * niivue = new Niivue()
23603
- * niivue.off('location')
23604
- */
23605
- off(event) {
23606
- const knownEvents = Object.keys(this.eventsToSubjects);
23607
- if (!knownEvents.includes(event)) {
23608
- return;
23609
- }
23610
- const nsubs = this.subscriptions.length;
23611
- for (let i = 0; i < nsubs; i++) {
23612
- const key = Object.keys(this.subscriptions[i])[0];
23613
- if (key === event) {
23614
- this.subscriptions[i][event].unsubscribe();
23615
- this.subscriptions.splice(i, 1);
23616
- return;
23617
- }
23618
- }
23619
- }
23620
23534
  /**
23621
23535
  * attach the Niivue instance to a canvas element directly
23622
23536
  * @param canvas - the canvas element reference
@@ -24302,6 +24216,12 @@ var Niivue = class _Niivue {
24302
24216
  this.moveCrosshairInVox(0, -1, 0);
24303
24217
  } else if (e.code === "KeyK" && this.opts.sliceType !== 4 /* RENDER */) {
24304
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]);
24305
24225
  } else if (e.code === "ArrowLeft") {
24306
24226
  this.setFrame4D(this.volumes[0].id, this.volumes[0].frame4D - 1);
24307
24227
  } else if (e.code === "ArrowRight") {
@@ -24538,7 +24458,6 @@ var Niivue = class _Niivue {
24538
24458
  gl: this.gl,
24539
24459
  name: file.name
24540
24460
  }).then((mesh) => {
24541
- this.uiData.loading$.next(false);
24542
24461
  this.addMesh(mesh);
24543
24462
  }).catch((e2) => {
24544
24463
  throw e2;
@@ -24615,6 +24534,9 @@ var Niivue = class _Niivue {
24615
24534
  * @see {@link https://niivue.github.io/niivue/features/layout.html|live demo usage}
24616
24535
  */
24617
24536
  setMultiplanarLayout(layout) {
24537
+ if (typeof layout === "string") {
24538
+ layout = parseInt(layout);
24539
+ }
24618
24540
  this.opts.multiplanarLayout = layout;
24619
24541
  this.drawScene();
24620
24542
  }
@@ -24845,7 +24767,7 @@ var Niivue = class _Niivue {
24845
24767
  const perm = drawingBitmap.permRAS;
24846
24768
  const vx = dims[1] * dims[2] * dims[3];
24847
24769
  this.drawBitmap = new Uint8Array(vx);
24848
- 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);
24849
24771
  const layout = [0, 0, 0];
24850
24772
  for (let i = 0; i < 3; i++) {
24851
24773
  for (let j2 = 0; j2 < 3; j2++) {
@@ -26053,14 +25975,8 @@ var Niivue = class _Niivue {
26053
25975
  * @see {@link https://niivue.github.io/niivue/features/mask.html|live demo usage}
26054
25976
  */
26055
25977
  async loadVolumes(volumeList) {
26056
- this.on("loading", (isLoading) => {
26057
- if (isLoading) {
26058
- this.loadingText = "loading...";
26059
- this.drawScene();
26060
- } else {
26061
- this.loadingText = this.opts.loadingText;
26062
- }
26063
- });
25978
+ this.loadingText = "loading...";
25979
+ this.drawScene();
26064
25980
  if (this.thumbnailVisible) {
26065
25981
  this.deferredVolumes = volumeList;
26066
25982
  return this;
@@ -26068,9 +25984,7 @@ var Niivue = class _Niivue {
26068
25984
  this.volumes = [];
26069
25985
  this.gl.clearColor(0, 0, 0, 1);
26070
25986
  this.gl.clear(this.gl.COLOR_BUFFER_BIT);
26071
- this.uiData.loading$.next(false);
26072
25987
  for (let i = 0; i < volumeList.length; i++) {
26073
- this.uiData.loading$.next(true);
26074
25988
  if (volumeList[i].colorMap !== void 0) {
26075
25989
  volumeList[i].colormap = volumeList[i].colorMap;
26076
25990
  }
@@ -26091,7 +26005,6 @@ var Niivue = class _Niivue {
26091
26005
  colorbarVisible: volumeList[i].colorbarVisible
26092
26006
  };
26093
26007
  await this.addVolumeFromUrl(imageOptions);
26094
- this.uiData.loading$.next(false);
26095
26008
  }
26096
26009
  return this;
26097
26010
  }
@@ -26100,6 +26013,16 @@ var Niivue = class _Niivue {
26100
26013
  * @see {@link https://niivue.github.io/niivue/features/multiuser.meshes.html|live demo usage}
26101
26014
  */
26102
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
+ }
26103
26026
  const mesh = await NVMesh.loadFromUrl({ ...meshOptions, gl: this.gl });
26104
26027
  this.mediaUrlMap.set(mesh, meshOptions.url);
26105
26028
  this.onMeshAddedFromUrl(meshOptions, mesh);
@@ -26116,14 +26039,8 @@ var Niivue = class _Niivue {
26116
26039
  * @see {@link https://niivue.github.io/niivue/features/meshes.html|live demo usage}
26117
26040
  */
26118
26041
  async loadMeshes(meshList) {
26119
- this.on("loading", (isLoading) => {
26120
- if (isLoading) {
26121
- this.loadingText = "loading...";
26122
- this.drawScene();
26123
- } else {
26124
- this.loadingText = this.opts.loadingText;
26125
- }
26126
- });
26042
+ this.loadingText = "loading...";
26043
+ this.drawScene();
26127
26044
  if (this.thumbnailVisible) {
26128
26045
  this.deferredMeshes = meshList;
26129
26046
  return this;
@@ -26133,11 +26050,8 @@ var Niivue = class _Niivue {
26133
26050
  this.meshes = [];
26134
26051
  this.gl.clearColor(0, 0, 0, 1);
26135
26052
  this.gl.clear(this.gl.COLOR_BUFFER_BIT);
26136
- this.uiData.loading$.next(false);
26137
26053
  for (let i = 0; i < meshList.length; i++) {
26138
- this.uiData.loading$.next(true);
26139
26054
  await this.addMeshFromUrl(meshList[i]);
26140
- this.uiData.loading$.next(false);
26141
26055
  }
26142
26056
  this.drawScene();
26143
26057
  return this;
@@ -26190,6 +26104,22 @@ var Niivue = class _Niivue {
26190
26104
  );
26191
26105
  this.drawScene();
26192
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
+ }
26193
26123
  /**
26194
26124
  * load a connectome specified by json
26195
26125
  * @param connectome - model
@@ -26197,27 +26127,13 @@ var Niivue = class _Niivue {
26197
26127
  * @see {@link https://niivue.github.io/niivue/features/connectome.html|live demo usage}
26198
26128
  */
26199
26129
  loadConnectome(json) {
26200
- this.on("loading", (isLoading) => {
26201
- if (isLoading) {
26202
- this.loadingText = "loading...";
26203
- this.drawScene();
26204
- } else {
26205
- this.loadingText = this.opts.loadingText;
26206
- }
26207
- });
26130
+ this.loadingText = "loading...";
26131
+ this.drawScene();
26208
26132
  this.meshes = [];
26209
26133
  this.gl.clearColor(0, 0, 0, 1);
26210
26134
  this.gl.clear(this.gl.COLOR_BUFFER_BIT);
26211
- this.uiData.loading$.next(true);
26212
- let connectome = json;
26213
- const nodes = json.nodes;
26214
- if ("names" in nodes && "X" in nodes && "Y" in nodes && "Z" in nodes && "Color" in nodes && "Size" in nodes) {
26215
- connectome = NVConnectome.convertLegacyConnectome(json);
26216
- log.warn("converted legacy connectome", connectome);
26217
- }
26218
- const mesh = new NVConnectome(this.gl, connectome);
26135
+ const mesh = this.loadConnectomeAsMesh(json);
26219
26136
  this.addMesh(mesh);
26220
- this.uiData.loading$.next(false);
26221
26137
  this.drawScene();
26222
26138
  return this;
26223
26139
  }
@@ -26238,7 +26154,7 @@ var Niivue = class _Niivue {
26238
26154
  this.drawBitmap = new Uint8Array(vx);
26239
26155
  this.drawClearAllUndoBitmaps();
26240
26156
  this.drawAddUndoBitmap();
26241
- 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);
26242
26158
  this.refreshDrawing(false);
26243
26159
  }
26244
26160
  // not included in public docs
@@ -26299,29 +26215,29 @@ var Niivue = class _Niivue {
26299
26215
  gl.viewport(0, 0, this.back.dims[1], this.back.dims[2]);
26300
26216
  gl.disable(gl.BLEND);
26301
26217
  let img16 = img2ras16(this.back);
26302
- const background = this.r16Tex(null, gl.TEXTURE3, this.back.dims, img16);
26218
+ const background = this.r16Tex(null, TEXTURE11_GC_BACK, this.back.dims, img16);
26303
26219
  for (let i = 1; i < nv; i++) {
26304
26220
  img16[i] = this.drawBitmap[i];
26305
26221
  }
26306
- const label0 = this.r16Tex(null, gl.TEXTURE6, this.back.dims, img16);
26307
- 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);
26308
26224
  const kMAX_STRENGTH = 1e4;
26309
26225
  for (let i = 1; i < nv; i++) {
26310
26226
  if (img16[i] > 0) {
26311
26227
  img16[i] = kMAX_STRENGTH;
26312
26228
  }
26313
26229
  }
26314
- const strength0 = this.r16Tex(null, gl.TEXTURE4, this.back.dims, img16);
26315
- 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);
26316
26232
  gl.bindVertexArray(this.genericVAO);
26317
26233
  const shader = this.growCutShader;
26318
26234
  shader.use(gl);
26319
26235
  const iterations = 128;
26320
26236
  gl.uniform1i(shader.uniforms.finalPass, 0);
26321
- gl.uniform1i(shader.uniforms.inputTexture0, 3);
26237
+ gl.uniform1i(shader.uniforms.backTex, 11);
26322
26238
  for (let j = 0; j < iterations; j++) {
26323
- gl.uniform1i(shader.uniforms.inputTexture1, 6);
26324
- gl.uniform1i(shader.uniforms.inputTexture2, 4);
26239
+ gl.uniform1i(shader.uniforms.labelTex, 14);
26240
+ gl.uniform1i(shader.uniforms.strengthTex, 12);
26325
26241
  for (let i = 0; i < this.back.dims[3]; i++) {
26326
26242
  const coordZ = 1 / this.back.dims[3] * (i + 0.5);
26327
26243
  gl.uniform1f(shader.uniforms.coordZ, coordZ);
@@ -26337,8 +26253,8 @@ var Niivue = class _Niivue {
26337
26253
  if (j === iterations - 1) {
26338
26254
  gl.uniform1i(shader.uniforms.finalPass, 1);
26339
26255
  }
26340
- gl.uniform1i(shader.uniforms.inputTexture1, 7);
26341
- gl.uniform1i(shader.uniforms.inputTexture2, 5);
26256
+ gl.uniform1i(shader.uniforms.labelTex, 15);
26257
+ gl.uniform1i(shader.uniforms.strengthTex, 13);
26342
26258
  for (let i = 0; i < this.back.dims[3]; i++) {
26343
26259
  const coordZ = 1 / this.back.dims[3] * (i + 0.5);
26344
26260
  gl.uniform1f(shader.uniforms.coordZ, coordZ);
@@ -26384,14 +26300,6 @@ var Niivue = class _Niivue {
26384
26300
  for (let i = 1; i < nv; i++) {
26385
26301
  this.drawBitmap[i] = img16[i];
26386
26302
  }
26387
- gl.activeTexture(gl.TEXTURE2);
26388
- gl.bindTexture(gl.TEXTURE_3D, this.overlayTexture);
26389
- gl.activeTexture(gl.TEXTURE3);
26390
- gl.bindTexture(gl.TEXTURE_2D, this.fontTexture);
26391
- gl.activeTexture(gl.TEXTURE4);
26392
- gl.bindTexture(gl.TEXTURE_2D, this.bmpTexture);
26393
- gl.activeTexture(gl.TEXTURE7);
26394
- gl.bindTexture(gl.TEXTURE_3D, this.drawTexture);
26395
26303
  gl.deleteTexture(background);
26396
26304
  gl.deleteTexture(strength0);
26397
26305
  gl.deleteTexture(strength1);
@@ -26817,7 +26725,7 @@ var Niivue = class _Niivue {
26817
26725
  */
26818
26726
  closeDrawing() {
26819
26727
  this.drawClearAllUndoBitmaps();
26820
- this.rgbaTex(this.drawTexture, this.gl.TEXTURE7, [2, 2, 2, 2], true);
26728
+ this.rgbaTex(this.drawTexture, TEXTURE7_DRAW, [2, 2, 2, 2], true);
26821
26729
  this.drawBitmap = null;
26822
26730
  this.drawScene();
26823
26731
  }
@@ -26843,7 +26751,7 @@ var Niivue = class _Niivue {
26843
26751
  } else if (vx !== this.drawBitmap.length) {
26844
26752
  log.warn("Drawing bitmap must match the background image");
26845
26753
  }
26846
- this.gl.activeTexture(this.gl.TEXTURE7);
26754
+ this.gl.activeTexture(TEXTURE7_DRAW);
26847
26755
  this.gl.bindTexture(this.gl.TEXTURE_3D, this.drawTexture);
26848
26756
  this.gl.texSubImage3D(
26849
26757
  this.gl.TEXTURE_3D,
@@ -26954,11 +26862,11 @@ var Niivue = class _Niivue {
26954
26862
  this.bmpTexture = this.gl.createTexture();
26955
26863
  pngTexture = this.bmpTexture;
26956
26864
  this.bmpTextureWH = img.width / img.height;
26957
- this.gl.activeTexture(this.gl.TEXTURE4);
26865
+ this.gl.activeTexture(TEXTURE4_THUMBNAIL);
26958
26866
  this.bmpShader.use(this.gl);
26959
26867
  this.gl.uniform1i(this.bmpShader.uniforms.bmpTexture, 4);
26960
26868
  } else if (textureNum === 5) {
26961
- this.gl.activeTexture(this.gl.TEXTURE5);
26869
+ this.gl.activeTexture(TEXTURE5_MATCAP);
26962
26870
  if (this.matCapTexture !== null) {
26963
26871
  this.gl.deleteTexture(this.matCapTexture);
26964
26872
  }
@@ -26966,7 +26874,7 @@ var Niivue = class _Niivue {
26966
26874
  pngTexture = this.matCapTexture;
26967
26875
  } else {
26968
26876
  this.fontShader.use(this.gl);
26969
- this.gl.activeTexture(this.gl.TEXTURE3);
26877
+ this.gl.activeTexture(TEXTURE3_FONT);
26970
26878
  this.gl.uniform1i(this.fontShader.uniforms.fontTexture, 3);
26971
26879
  if (this.fontTexture !== null) {
26972
26880
  this.gl.deleteTexture(this.fontTexture);
@@ -27201,9 +27109,9 @@ var Niivue = class _Niivue {
27201
27109
  this.gl.cullFace(this.gl.FRONT);
27202
27110
  this.gl.enable(this.gl.BLEND);
27203
27111
  this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
27204
- this.volumeTexture = this.rgbaTex(this.volumeTexture, this.gl.TEXTURE0, [2, 2, 2, 2], true);
27205
- this.overlayTexture = this.rgbaTex(this.overlayTexture, this.gl.TEXTURE2, [2, 2, 2, 2], true);
27206
- 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);
27207
27115
  const rectStrip = [
27208
27116
  1,
27209
27117
  1,
@@ -27340,10 +27248,10 @@ var Niivue = class _Niivue {
27340
27248
  gl.disable(gl.CULL_FACE);
27341
27249
  gl.viewport(0, 0, hdr.dims[1], hdr.dims[2]);
27342
27250
  gl.disable(gl.BLEND);
27343
- const tempTex3D = this.rgbaTex(null, this.gl.TEXTURE5, hdr.dims);
27251
+ const tempTex3D = this.rgbaTex(null, TEXTURE8_GRADIENT_TEMP, hdr.dims);
27344
27252
  const blurShader = this.blurShader;
27345
27253
  blurShader.use(gl);
27346
- gl.activeTexture(gl.TEXTURE0);
27254
+ gl.activeTexture(TEXTURE0_BACK_VOL);
27347
27255
  gl.bindTexture(gl.TEXTURE_3D, this.volumeTexture);
27348
27256
  const blurRadius = 0.7;
27349
27257
  gl.uniform1i(blurShader.uniforms.intensityVol, 0);
@@ -27360,20 +27268,20 @@ var Niivue = class _Niivue {
27360
27268
  }
27361
27269
  const sobelShader = this.sobelShader;
27362
27270
  sobelShader.use(gl);
27363
- gl.activeTexture(gl.TEXTURE1);
27271
+ gl.activeTexture(TEXTURE8_GRADIENT_TEMP);
27364
27272
  gl.bindTexture(gl.TEXTURE_3D, tempTex3D);
27365
- gl.uniform1i(sobelShader.uniforms.intensityVol, 1);
27273
+ gl.uniform1i(sobelShader.uniforms.intensityVol, 8);
27366
27274
  const sobelRadius = 0.7;
27367
27275
  gl.uniform1f(sobelShader.uniforms.dX, sobelRadius / hdr.dims[1]);
27368
27276
  gl.uniform1f(sobelShader.uniforms.dY, sobelRadius / hdr.dims[2]);
27369
27277
  gl.uniform1f(sobelShader.uniforms.dZ, sobelRadius / hdr.dims[3]);
27370
27278
  gl.uniform1f(sobelShader.uniforms.coordZ, 0.5);
27371
27279
  gl.bindVertexArray(vao2);
27372
- gl.activeTexture(gl.TEXTURE0);
27280
+ gl.activeTexture(TEXTURE0_BACK_VOL);
27373
27281
  if (this.gradientTexture !== null) {
27374
27282
  gl.deleteTexture(this.gradientTexture);
27375
27283
  }
27376
- this.gradientTexture = this.rgbaTex(this.gradientTexture, this.gl.TEXTURE6, hdr.dims);
27284
+ this.gradientTexture = this.rgbaTex(this.gradientTexture, TEXTURE6_GRADIENT, hdr.dims);
27377
27285
  for (let i = 0; i < hdr.dims[3] - 1; i++) {
27378
27286
  const coordZ = 1 / hdr.dims[3] * (i + 0.5);
27379
27287
  gl.uniform1f(sobelShader.uniforms.coordZ, coordZ);
@@ -27555,7 +27463,7 @@ var Niivue = class _Niivue {
27555
27463
  this.back.matRAS = overlayItem.matRAS;
27556
27464
  this.back.dims = overlayItem.dimsRAS;
27557
27465
  this.back.pixDims = overlayItem.pixDimsRAS;
27558
- outTexture = this.rgbaTex(this.volumeTexture, this.gl.TEXTURE0, overlayItem.dimsRAS);
27466
+ outTexture = this.rgbaTex(this.volumeTexture, TEXTURE0_BACK_VOL, overlayItem.dimsRAS);
27559
27467
  const { volScale: volScale2, vox: vox2 } = this.sliceScale(true);
27560
27468
  this.volScale = volScale2;
27561
27469
  this.vox = vox2;
@@ -27604,7 +27512,7 @@ var Niivue = class _Niivue {
27604
27512
  );
27605
27513
  mat46.invert(mtx, mtx);
27606
27514
  if (layer === 1) {
27607
- outTexture = this.rgbaTex(this.overlayTexture, this.gl.TEXTURE2, this.back.dims);
27515
+ outTexture = this.rgbaTex(this.overlayTexture, TEXTURE2_OVERLAY_VOL, this.back.dims);
27608
27516
  this.overlayTexture = outTexture;
27609
27517
  this.overlayTextureID = outTexture;
27610
27518
  } else {
@@ -27617,7 +27525,7 @@ var Niivue = class _Niivue {
27617
27525
  this.gl.viewport(0, 0, this.back.dims[1], this.back.dims[2]);
27618
27526
  this.gl.disable(this.gl.BLEND);
27619
27527
  const tempTex3D = this.gl.createTexture();
27620
- this.gl.activeTexture(this.gl.TEXTURE6);
27528
+ this.gl.activeTexture(TEXTURE9_ORIENT);
27621
27529
  this.gl.bindTexture(this.gl.TEXTURE_3D, tempTex3D);
27622
27530
  this.gl.texParameteri(this.gl.TEXTURE_3D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
27623
27531
  this.gl.texParameteri(this.gl.TEXTURE_3D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
@@ -27766,22 +27674,22 @@ var Niivue = class _Niivue {
27766
27674
  const isUseCopyTexSubImage3D = false;
27767
27675
  if (isUseCopyTexSubImage3D) {
27768
27676
  if (layer > 1) {
27769
- blendTexture = this.rgbaTex(blendTexture, this.gl.TEXTURE5, this.back.dims, true);
27677
+ blendTexture = this.rgbaTex(blendTexture, TEXTURE10_BLEND, this.back.dims, true);
27770
27678
  this.gl.bindTexture(this.gl.TEXTURE_3D, blendTexture);
27771
27679
  for (let i = 0; i < this.back.dims[3]; i++) {
27772
27680
  this.gl.framebufferTextureLayer(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.overlayTexture, 0, i);
27773
- this.gl.activeTexture(this.gl.TEXTURE5);
27681
+ this.gl.activeTexture(TEXTURE10_BLEND);
27774
27682
  this.gl.copyTexSubImage3D(this.gl.TEXTURE_3D, 0, 0, 0, i, 0, 0, this.back.dims[1], this.back.dims[2]);
27775
27683
  }
27776
27684
  } else {
27777
- blendTexture = this.rgbaTex(blendTexture, this.gl.TEXTURE5, [2, 2, 2, 2], true);
27685
+ blendTexture = this.rgbaTex(blendTexture, TEXTURE10_BLEND, [2, 2, 2, 2], true);
27778
27686
  }
27779
27687
  } else {
27780
27688
  if (layer > 1) {
27781
27689
  if (!this.back.dims) {
27782
27690
  throw new Error("back.dims undefined");
27783
27691
  }
27784
- blendTexture = this.rgbaTex(blendTexture, this.gl.TEXTURE5, this.back.dims);
27692
+ blendTexture = this.rgbaTex(blendTexture, TEXTURE10_BLEND, this.back.dims);
27785
27693
  this.gl.bindTexture(this.gl.TEXTURE_3D, blendTexture);
27786
27694
  const passShader = this.passThroughShader;
27787
27695
  passShader.use(this.gl);
@@ -27793,11 +27701,11 @@ var Niivue = class _Niivue {
27793
27701
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
27794
27702
  }
27795
27703
  } else {
27796
- blendTexture = this.rgbaTex(blendTexture, this.gl.TEXTURE5, [2, 2, 2, 2]);
27704
+ blendTexture = this.rgbaTex(blendTexture, TEXTURE10_BLEND, [2, 2, 2, 2]);
27797
27705
  }
27798
27706
  }
27799
27707
  orientShader.use(this.gl);
27800
- this.gl.activeTexture(this.gl.TEXTURE1);
27708
+ this.gl.activeTexture(TEXTURE1_COLORMAPS);
27801
27709
  let colormapLabelTexture = null;
27802
27710
  if (overlayItem.colormapLabel !== null && overlayItem.colormapLabel.lut.length > 7) {
27803
27711
  const nLabel = overlayItem.colormapLabel.max - overlayItem.colormapLabel.min + 1;
@@ -27840,8 +27748,8 @@ var Niivue = class _Niivue {
27840
27748
  this.gl.uniform1f(orientShader.uniforms.cal_minNeg ?? null, mnNeg);
27841
27749
  this.gl.uniform1f(orientShader.uniforms.cal_maxNeg ?? null, mxNeg);
27842
27750
  this.gl.bindTexture(this.gl.TEXTURE_3D, tempTex3D);
27843
- this.gl.uniform1i(orientShader.uniforms.intensityVol ?? null, 6);
27844
- 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);
27845
27753
  this.gl.uniform1i(orientShader.uniforms.colormap ?? null, 1);
27846
27754
  this.gl.uniform1f(orientShader.uniforms.scl_inter ?? null, hdr.scl_inter);
27847
27755
  this.gl.uniform1f(orientShader.uniforms.scl_slope ?? null, hdr.scl_slope);
@@ -28004,11 +27912,11 @@ var Niivue = class _Niivue {
28004
27912
  this.gl.uniform1f(shader.uniforms.drawOpacity, this.drawOpacity);
28005
27913
  if (colormapLabelTexture !== null) {
28006
27914
  this.gl.deleteTexture(colormapLabelTexture);
28007
- this.gl.activeTexture(this.gl.TEXTURE1);
27915
+ this.gl.activeTexture(TEXTURE1_COLORMAPS);
28008
27916
  this.gl.bindTexture(this.gl.TEXTURE_2D, this.colormapTexture);
28009
27917
  }
28010
27918
  this.gl.uniform1i(shader.uniforms.drawing, 7);
28011
- this.gl.activeTexture(this.gl.TEXTURE7);
27919
+ this.gl.activeTexture(TEXTURE7_DRAW);
28012
27920
  this.gl.bindTexture(this.gl.TEXTURE_3D, this.drawTexture);
28013
27921
  this.updateInterpolation(layer);
28014
27922
  }
@@ -28081,7 +27989,7 @@ var Niivue = class _Niivue {
28081
27989
  /**
28082
27990
  * modulate intensity of one image based on intensity of another
28083
27991
  * @param idTarget - the ID of the NVImage to be biased
28084
- * @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)
28085
27993
  * @param modulateAlpha - does the modulation influence alpha transparency (values greater than 1).
28086
27994
  * @example niivue.setModulationImage(niivue.volumes[0].id, niivue.volumes[1].id);
28087
27995
  * @see {@link https://niivue.github.io/niivue/features/modulate.html|live demo scalar usage}
@@ -28090,7 +27998,9 @@ var Niivue = class _Niivue {
28090
27998
  setModulationImage(idTarget, idModulation, modulateAlpha = 0) {
28091
27999
  const idxTarget = this.getVolumeIndexByID(idTarget);
28092
28000
  let idxModulation = null;
28093
- idxModulation = this.getVolumeIndexByID(idModulation);
28001
+ if (idModulation.length > 0) {
28002
+ idxModulation = this.getVolumeIndexByID(idModulation);
28003
+ }
28094
28004
  this.volumes[idxTarget].modulationImage = idxModulation;
28095
28005
  this.volumes[idxTarget].modulateAlpha = modulateAlpha;
28096
28006
  this.updateGLVolume();
@@ -28181,7 +28091,7 @@ var Niivue = class _Niivue {
28181
28091
  return null;
28182
28092
  }
28183
28093
  texture = this.gl.createTexture();
28184
- this.gl.activeTexture(this.gl.TEXTURE1);
28094
+ this.gl.activeTexture(TEXTURE1_COLORMAPS);
28185
28095
  this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
28186
28096
  this.gl.texStorage2D(this.gl.TEXTURE_2D, 1, this.gl.RGBA8, nCol, nRow);
28187
28097
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
@@ -28821,7 +28731,7 @@ var Niivue = class _Niivue {
28821
28731
  throw new Error("colorbarShader undefined");
28822
28732
  }
28823
28733
  this.colorbarShader.use(this.gl);
28824
- this.gl.activeTexture(this.gl.TEXTURE1);
28734
+ this.gl.activeTexture(TEXTURE1_COLORMAPS);
28825
28735
  this.gl.bindTexture(this.gl.TEXTURE_2D, this.colormapTexture);
28826
28736
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
28827
28737
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
@@ -29064,9 +28974,9 @@ var Niivue = class _Niivue {
29064
28974
  interp = this.gl.NEAREST;
29065
28975
  }
29066
28976
  if (layer === 0) {
29067
- this.gl.activeTexture(this.gl.TEXTURE0);
28977
+ this.gl.activeTexture(TEXTURE0_BACK_VOL);
29068
28978
  } else {
29069
- this.gl.activeTexture(this.gl.TEXTURE2);
28979
+ this.gl.activeTexture(TEXTURE2_OVERLAY_VOL);
29070
28980
  }
29071
28981
  this.gl.texParameteri(this.gl.TEXTURE_3D, this.gl.TEXTURE_MIN_FILTER, interp);
29072
28982
  this.gl.texParameteri(this.gl.TEXTURE_3D, this.gl.TEXTURE_MAG_FILTER, interp);
@@ -29636,6 +29546,12 @@ var Niivue = class _Niivue {
29636
29546
  if (graph.opacity <= 0 || graph.LTWH[2] <= 5 || graph.LTWH[3] <= 5) {
29637
29547
  return;
29638
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
+ }
29639
29555
  graph.backColor = [0.15, 0.15, 0.15, graph.opacity];
29640
29556
  graph.lineColor = [1, 1, 1, 1];
29641
29557
  if (this.opts.backColor[0] + this.opts.backColor[1] + this.opts.backColor[2] > 1.5) {
@@ -29899,7 +29815,7 @@ var Niivue = class _Niivue {
29899
29815
  shader.use(this.gl);
29900
29816
  gl.uniform1i(shader.uniforms.backgroundMasksOverlays, this.backgroundMasksOverlays);
29901
29817
  if (this.gradientTextureAmount > 0) {
29902
- gl.activeTexture(gl.TEXTURE6);
29818
+ gl.activeTexture(TEXTURE6_GRADIENT);
29903
29819
  gl.bindTexture(gl.TEXTURE_3D, this.gradientTexture);
29904
29820
  const modelMatrix = this.calculateModelMatrix(azimuth, elevation);
29905
29821
  const iModelMatrix = mat46.create();
@@ -30307,7 +30223,7 @@ var Niivue = class _Niivue {
30307
30223
  continue;
30308
30224
  }
30309
30225
  if (shader.isMatcap) {
30310
- gl.activeTexture(gl.TEXTURE6);
30226
+ gl.activeTexture(TEXTURE5_MATCAP);
30311
30227
  gl.bindTexture(gl.TEXTURE_2D, this.matCapTexture);
30312
30228
  }
30313
30229
  gl.bindVertexArray(this.meshes[i].vao);
@@ -30367,7 +30283,7 @@ var Niivue = class _Niivue {
30367
30283
  radius = range[0] * 0.02;
30368
30284
  }
30369
30285
  radius *= this.opts.crosshairWidth;
30370
- 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);
30371
30287
  this.crosshairs3D.mm = mm;
30372
30288
  }
30373
30289
  if (!this.surfaceShader) {