@niivue/niivue 0.24.2 → 0.26.3

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/dist/niivue.es.js CHANGED
@@ -91,7 +91,7 @@ function fromValues$3(m00, m01, m02, m10, m11, m12, m20, m21, m22) {
91
91
  out[8] = m22;
92
92
  return out;
93
93
  }
94
- function multiply$2(out, a, b) {
94
+ function multiply$1(out, a, b) {
95
95
  var a00 = a[0], a01 = a[1], a02 = a[2];
96
96
  var a10 = a[3], a11 = a[4], a12 = a[5];
97
97
  var a20 = a[6], a21 = a[7], a22 = a[8];
@@ -286,7 +286,7 @@ function invert(out, a) {
286
286
  out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
287
287
  return out;
288
288
  }
289
- function multiply$1(out, a, b) {
289
+ function multiply(out, a, b) {
290
290
  var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
291
291
  var a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7];
292
292
  var a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11];
@@ -500,7 +500,7 @@ function multiplyScalar(out, a, b) {
500
500
  out[15] = a[15] * b;
501
501
  return out;
502
502
  }
503
- var mul$1 = multiply$1;
503
+ var mul = multiply;
504
504
  function create$1() {
505
505
  var out = new ARRAY_TYPE(3);
506
506
  if (ARRAY_TYPE != Float32Array) {
@@ -609,6 +609,7 @@ function angle(a, b) {
609
609
  var ax = a[0], ay = a[1], az = a[2], bx = b[0], by = b[1], bz = b[2], mag1 = Math.sqrt(ax * ax + ay * ay + az * az), mag2 = Math.sqrt(bx * bx + by * by + bz * bz), mag = mag1 * mag2, cosine = mag && dot(a, b) / mag;
610
610
  return Math.acos(Math.min(Math.max(cosine, -1), 1));
611
611
  }
612
+ var sub = subtract;
612
613
  var len = length$1;
613
614
  (function() {
614
615
  var vec = create$1();
@@ -662,13 +663,6 @@ function add(out, a, b) {
662
663
  out[3] = a[3] + b[3];
663
664
  return out;
664
665
  }
665
- function multiply(out, a, b) {
666
- out[0] = a[0] * b[0];
667
- out[1] = a[1] * b[1];
668
- out[2] = a[2] * b[2];
669
- out[3] = a[3] * b[3];
670
- return out;
671
- }
672
666
  function transformMat4(out, a, m) {
673
667
  var x2 = a[0], y = a[1], z = a[2], w = a[3];
674
668
  out[0] = m[0] * x2 + m[4] * y + m[8] * z + m[12] * w;
@@ -677,7 +671,6 @@ function transformMat4(out, a, m) {
677
671
  out[3] = m[3] * x2 + m[7] * y + m[11] * z + m[15] * w;
678
672
  return out;
679
673
  }
680
- var mul = multiply;
681
674
  (function() {
682
675
  var vec = create();
683
676
  return function(a, stride, offset, count, fn, arg2) {
@@ -786,7 +779,19 @@ float frac2ndc(vec3 frac) {
786
779
 
787
780
  }
788
781
  void main() {
782
+ if (fColor.x > 2.0) {
783
+ fColor = vec4(1.0, 0.0, 0.0, 0.5);
784
+ return;
785
+ }
789
786
  fColor = vec4(0.0,0.0,0.0,0.0);
787
+ vec4 clipPlaneColorX = clipPlaneColor;
788
+ //if (clipPlaneColor.a < 0.0)
789
+ // clipPlaneColorX.a = - 1.0;
790
+ bool isColorPlaneInVolume = false;
791
+ if (clipPlaneColorX.a < 0.0) {
792
+ isColorPlaneInVolume = true;
793
+ clipPlaneColorX.a = 0.0;
794
+ }
790
795
  //fColor = vec4(vColor.rgb, 1.0); return;
791
796
  vec3 start = vColor;
792
797
  gl_FragDepth = 0.0;
@@ -813,19 +818,23 @@ void main() {
813
818
  vec4 deltaDirFast = vec4(dir.xyz * stepSizeFast, stepSizeFast);
814
819
  while (samplePos.a <= len) {
815
820
  float val = texture(volume, samplePos.xyz).a;
816
- if (val > 0.01) break;
821
+ if (val > 0.01) {
822
+ break;
823
+ }
817
824
  samplePos += deltaDirFast; //advance ray position
818
825
  }
819
826
  if ((samplePos.a >= len) && ((overlays < 1.0) || (backgroundMasksOverlays > 0))) {
820
- if (isClip)
821
- fColor += clipPlaneColor;
827
+ if (isClip){
828
+ fColor += clipPlaneColorX;
829
+ }
822
830
  return;
823
831
  }
824
832
  fColor = vec4(1.0, 1.0, 1.0, 1.0);
825
833
  //gl_FragDepth = frac2ndc(samplePos.xyz); //crude due to fast pass resolution
826
834
  samplePos -= deltaDirFast;
827
- if (samplePos.a < 0.0)
835
+ if (samplePos.a < 0.0){
828
836
  vec4 samplePos = vec4(start.xyz, 0.0); //ray position
837
+ }
829
838
  //end: fast pass
830
839
  vec4 colAcc = vec4(0.0,0.0,0.0,0.0);
831
840
  vec4 firstHit = vec4(0.0,0.0,0.0,2.0 * lenNoClip);
@@ -836,23 +845,29 @@ void main() {
836
845
  while (samplePos.a <= len) {
837
846
  vec4 colorSample = texture(volume, samplePos.xyz);
838
847
  samplePos += deltaDir; //advance ray position
839
- if (colorSample.a < 0.01) continue;
840
- if (firstHit.a > lenNoClip)
841
- firstHit = samplePos;
842
- backNearest = min(backNearest, samplePos.a);
843
- colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection);
844
- colorSample.rgb *= colorSample.a;
845
- colAcc= (1.0 - colAcc.a) * colorSample + colAcc;
846
- if ( colAcc.a > earlyTermination )
847
- break;
848
+ if (colorSample.a >= 0.01) {
849
+ if (firstHit.a > lenNoClip)
850
+ firstHit = samplePos;
851
+ backNearest = min(backNearest, samplePos.a);
852
+ colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection);
853
+ colorSample.rgb *= colorSample.a;
854
+ colAcc= (1.0 - colAcc.a) * colorSample + colAcc;
855
+ if ( colAcc.a > earlyTermination )
856
+ break;
857
+ }
848
858
  }
849
- if (firstHit.a < len)
859
+ if (firstHit.a < len){
850
860
  gl_FragDepth = frac2ndc(firstHit.xyz);
861
+ }
851
862
  colAcc.a = (colAcc.a / earlyTermination) * backOpacity;
852
863
  fColor = colAcc;
853
- if (isClip) //CR
854
- fColor.rgb = mix(fColor.rgb, clipPlaneColor.rgb, clipPlaneColor.a * 0.15);
855
- if (overlays < 1.0) return;
864
+ //if (isClip) //CR
865
+ if ((isColorPlaneInVolume) && (clipPos.a != samplePos.a) && (abs(firstHit.a - clipPos.a) < deltaDir.a))
866
+ fColor.rgb = mix(fColor.rgb, clipPlaneColorX.rgb, abs(clipPlaneColor.a));
867
+ //fColor.rgb = mix(fColor.rgb, clipPlaneColorX.rgb, clipPlaneColorX.a * 0.65);
868
+ if (overlays < 1.0) {
869
+ return;
870
+ }
856
871
  //overlay pass
857
872
  len = lenNoClip;
858
873
  samplePos = vec4(start.xyz, 0.0); //ray position
@@ -861,48 +876,56 @@ void main() {
861
876
  deltaDirFast = vec4(dir.xyz * stepSizeFast, stepSizeFast);
862
877
  while (samplePos.a <= len) {
863
878
  float val = texture(overlay, samplePos.xyz).a;
864
- if (val > 0.01) break;
879
+ if (val > 0.01) {
880
+ break;
881
+ }
865
882
  samplePos += deltaDirFast; //advance ray position
866
883
  }
867
884
  if (samplePos.a >= len) {
868
- if (isClip && (fColor.a == 0.0))
869
- fColor += clipPlaneColor;
885
+ if (isClip && (fColor.a == 0.0)) {
886
+ fColor += clipPlaneColorX;
887
+ }
870
888
  return;
871
889
  }
872
890
  samplePos -= deltaDirFast;
873
- if (samplePos.a < 0.0)
891
+ if (samplePos.a < 0.0){
874
892
  vec4 samplePos = vec4(start.xyz, 0.0); //ray position
893
+ }
875
894
  //end: fast pass
876
895
  float overFarthest = len;
877
896
  colAcc = vec4(0.0, 0.0, 0.0, 0.0);
878
897
  samplePos += deltaDir * ran; //jitter ray
879
898
  vec4 overFirstHit = vec4(0.0,0.0,0.0,2.0 * len);
880
- if (backgroundMasksOverlays > 0)
899
+ if (backgroundMasksOverlays > 0){
881
900
  samplePos = firstHit;
901
+ }
882
902
  while (samplePos.a <= len) {
883
903
  vec4 colorSample = texture(overlay, samplePos.xyz);
884
904
  samplePos += deltaDir; //advance ray position
885
- if (colorSample.a < 0.01) continue;
886
- if (overFirstHit.a > len)
887
- overFirstHit = samplePos;
888
- colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection);
889
- colorSample.rgb *= colorSample.a;
890
- colAcc= (1.0 - colAcc.a) * colorSample + colAcc;
891
- overFarthest = samplePos.a;
892
- if ( colAcc.a > earlyTermination )
893
- break;
905
+ if (colorSample.a >= 0.01) {
906
+ if (overFirstHit.a > len)
907
+ overFirstHit = samplePos;
908
+ colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection);
909
+ colorSample.rgb *= colorSample.a;
910
+ colAcc= (1.0 - colAcc.a) * colorSample + colAcc;
911
+ overFarthest = samplePos.a;
912
+ if ( colAcc.a > earlyTermination )
913
+ break;
914
+ }
894
915
  }
895
916
  if (samplePos.a >= len) {
896
- if (isClip && (fColor.a == 0.0))
897
- fColor += clipPlaneColor;
917
+ if (isClip && (fColor.a == 0.0)){
918
+ fColor += clipPlaneColorX;
919
+ }
898
920
  return;
899
921
  }
900
922
  //if (overFirstHit.a < len)
901
- gl_FragDepth = frac2ndc(overFirstHit.xyz);
923
+ gl_FragDepth = frac2ndc(overFirstHit.xyz);
902
924
  float overMix = colAcc.a;
903
925
  float overlayDepth = 0.3;
904
- if (fColor.a <= 0.0)
926
+ if (fColor.a <= 0.0){
905
927
  overMix = 1.0;
928
+ }
906
929
  else if (((overFarthest) > backNearest)) {
907
930
  float dx = (overFarthest - backNearest)/1.73;
908
931
  dx = fColor.a * pow(dx, overlayDepth);
@@ -917,15 +940,22 @@ layout(location=0) in vec3 pos;
917
940
  uniform int axCorSag;
918
941
  uniform float slice;
919
942
  uniform vec2 canvasWidthHeight;
943
+ uniform vec3 panXYscale;
920
944
  uniform vec4 leftTopWidthHeight;
921
945
  out vec3 texPos;
922
946
  void main(void) {
923
947
  //convert pixel x,y space 1..canvasWidth,1..canvasHeight to WebGL 1..-1,-1..1
924
948
  vec2 frac;
925
- frac.x = (leftTopWidthHeight.x + (pos.x * leftTopWidthHeight.z)) / canvasWidthHeight.x; //0..1
926
- frac.y = 1.0 - ((leftTopWidthHeight.y + ((1.0 - pos.y) * leftTopWidthHeight.w)) / canvasWidthHeight.y); //1..0
949
+ vec3 vpos = pos;
950
+ frac.x = (leftTopWidthHeight.x + (vpos.x * leftTopWidthHeight.z)) / canvasWidthHeight.x; //0..1
951
+ frac.y = 1.0 - ((leftTopWidthHeight.y + ((1.0 - vpos.y) * leftTopWidthHeight.w)) / canvasWidthHeight.y); //1..0
952
+ //frac.x = pos.x; //0..1
953
+ //frac.y = pos.y; //1..0
954
+
927
955
  frac = (frac * 2.0) - 1.0;
928
956
  gl_Position = vec4(frac, 0.0, 1.0);
957
+ gl_Position.x += panXYscale.x;
958
+ gl_Position.y += panXYscale.y;
929
959
  if (axCorSag == 1)
930
960
  texPos = vec3(pos.x, slice, pos.y);
931
961
  else if (axCorSag == 2)
@@ -974,7 +1004,68 @@ void main() {
974
1004
  color.rgb = mix(color.rgb, ocolor.rgb, ocolor.a);
975
1005
  color.a = aout;
976
1006
  }`;
977
- var fragLineShader = `#version 300 es
1007
+ var vertSliceMMShader = `#version 300 es
1008
+ #line 4
1009
+ layout(location=0) in vec3 pos;
1010
+ uniform int axCorSag;
1011
+ uniform mat4 mvpMtx;
1012
+ uniform mat4 frac2mm;
1013
+ uniform float slice;
1014
+ out vec3 texPos;
1015
+ void main(void) {
1016
+ texPos = vec3(pos.x, pos.y, slice);
1017
+ if (axCorSag > 1)
1018
+ texPos = vec3(slice, pos.x, pos.y);
1019
+ else if (axCorSag > 0)
1020
+ texPos = vec3(pos.x, slice, pos.y);
1021
+ vec4 mm = frac2mm * vec4(texPos, 1.0);
1022
+ //vec4 mm = vec4(texPos, 1.0) * frac2mm;
1023
+ gl_Position = mvpMtx * mm;
1024
+ //gl_Position = mm;
1025
+ }`;
1026
+ var fragSliceMMShader = `#version 300 es
1027
+ #line 228
1028
+ precision highp int;
1029
+ precision highp float;
1030
+ uniform highp sampler3D volume, overlay;
1031
+ uniform int backgroundMasksOverlays;
1032
+ uniform float overlays;
1033
+ uniform float opacity;
1034
+ uniform float drawOpacity;
1035
+ uniform highp sampler3D drawing;
1036
+ in vec3 texPos;
1037
+ out vec4 color;
1038
+ void main() {
1039
+ //color = vec4(1.0, 0.0, 1.0, 1.0);return;
1040
+ vec4 background = texture(volume, texPos);
1041
+ color = vec4(background.rgb, opacity);
1042
+ vec4 ocolor = vec4(0.0);
1043
+ if (overlays > 0.0) {
1044
+ ocolor = texture(overlay, texPos);
1045
+ }
1046
+ float draw = texture(drawing, texPos).r;
1047
+ if (draw > 0.0) {
1048
+ vec3 dcolor = vec3(0.0, 0.0, 0.0);
1049
+ if (draw >= (4.0/255.0))
1050
+ dcolor.rgb = vec3(draw,0.0,draw);
1051
+ else if (draw >= (3.0/255.0))
1052
+ dcolor.b = 1.0;
1053
+ else if (draw >= (2.0/255.0))
1054
+ dcolor.g = 1.0;
1055
+ else
1056
+ dcolor.r = 1.0;
1057
+ color.rgb = mix(color.rgb, dcolor, drawOpacity);
1058
+ color.a = max(drawOpacity, color.a);
1059
+ }
1060
+ if ((backgroundMasksOverlays > 0) && (background.a == 0.0))
1061
+ return;
1062
+ //float aout = ocolor.a + (1.0 - ocolor.a) * color.a;
1063
+ //if (aout <= 0.0) return;
1064
+ //color.rgb = ((ocolor.rgb * ocolor.a) + (color.rgb * color.a * (1.0 - ocolor.a))) / aout;
1065
+ color.rgb = mix(color.rgb, ocolor.rgb, ocolor.a);
1066
+ color.a = 1.0;
1067
+ }`;
1068
+ var fragRectShader = `#version 300 es
978
1069
  #line 189
979
1070
  precision highp int;
980
1071
  precision highp float;
@@ -1003,10 +1094,13 @@ var fragColorbarShader = `#version 300 es
1003
1094
  precision highp int;
1004
1095
  precision highp float;
1005
1096
  uniform highp sampler2D colormap;
1097
+ uniform float layer;
1006
1098
  in vec2 vColor;
1007
1099
  out vec4 color;
1008
1100
  void main() {
1009
- color = vec4(texture(colormap, vColor).rgb, 1.0);
1101
+ float nlayer = float(textureSize(colormap, 0).y);
1102
+ float fmap = (0.5 + layer) / nlayer;
1103
+ color = vec4(texture(colormap, vec2(vColor.x, fmap)).rgb, 1.0);
1010
1104
  }`;
1011
1105
  var vertGraphShader = `#version 300 es
1012
1106
  #line 229
@@ -1047,7 +1141,7 @@ void main(void) {
1047
1141
  frac = (frac * 2.0) - 1.0;
1048
1142
  gl_Position = vec4(frac, 0.0, 1.0);
1049
1143
  }`;
1050
- var vertLineShader = `#version 300 es
1144
+ var vertRectShader = `#version 300 es
1051
1145
  #line 229
1052
1146
  layout(location=0) in vec3 pos;
1053
1147
  uniform vec2 canvasWidthHeight;
@@ -1060,6 +1154,20 @@ void main(void) {
1060
1154
  frac = (frac * 2.0) - 1.0;
1061
1155
  gl_Position = vec4(frac, 0.0, 1.0);
1062
1156
  }`;
1157
+ var vertLineShader = `#version 300 es
1158
+ #line 229
1159
+ layout(location=0) in vec3 pos;
1160
+ uniform vec2 canvasWidthHeight;
1161
+ uniform float thickness;
1162
+ uniform vec4 startXYendXY;
1163
+ void main(void) {
1164
+ vec2 posXY = mix(startXYendXY.xy, startXYendXY.zw, pos.x);
1165
+ vec2 dir = normalize(startXYendXY.xy - startXYendXY.zw);
1166
+ posXY += vec2(-dir.y, dir.x) * thickness * (pos.y - 0.5);
1167
+ posXY.x = (posXY.x) / canvasWidthHeight.x; //0..1
1168
+ posXY.y = 1.0 - (posXY.y / canvasWidthHeight.y); //1..0
1169
+ gl_Position = vec4((posXY * 2.0) - 1.0, 0.0, 1.0);
1170
+ }`;
1063
1171
  var vertBmpShader = `#version 300 es
1064
1172
  #line 229
1065
1173
  layout(location=0) in vec3 pos;
@@ -1146,7 +1254,7 @@ in vec2 TexCoord;
1146
1254
  out vec4 FragColor;
1147
1255
  uniform float coordZ;
1148
1256
  uniform float layer;
1149
- uniform float numLayers;
1257
+ //uniform float numLayers;
1150
1258
  uniform highp sampler2D colormap;
1151
1259
  uniform lowp sampler3D blend3D;
1152
1260
  uniform float opacity;
@@ -1176,7 +1284,9 @@ void main(void) {
1176
1284
  }
1177
1285
  idx = ((idx - uint(1)) % uint(100))+uint(1);
1178
1286
  float fx = (float(idx)+0.5) / 256.0;
1179
- float y = (2.0 * layer + 1.0)/(2.0 * numLayers);
1287
+ float nlayer = float(textureSize(colormap, 0).y) * 0.5; //0.5 as both each layer has positive and negative color slot
1288
+ float y = (2.0 * layer + 1.0)/(4.0 * nlayer);
1289
+ //float y = (2.0 * layer + 1.0)/(4.0 * numLayers);
1180
1290
  FragColor = texture(colormap, vec2(fx, y)).rgba;
1181
1291
  FragColor.a *= opacity;
1182
1292
  if (layer < 2.0) return;
@@ -1195,28 +1305,43 @@ in vec2 TexCoord;
1195
1305
  out vec4 FragColor;
1196
1306
  uniform float coordZ;
1197
1307
  uniform float layer;
1198
- uniform float numLayers;
1308
+ //uniform float numLayers;
1199
1309
  uniform float scl_slope;
1200
1310
  uniform float scl_inter;
1201
1311
  uniform float cal_max;
1202
1312
  uniform float cal_min;
1203
1313
  uniform highp sampler2D colormap;
1204
1314
  uniform lowp sampler3D blend3D;
1315
+ uniform int modulation;
1316
+ uniform highp sampler3D modulationVol;
1205
1317
  uniform float opacity;
1206
1318
  uniform mat4 mtx;
1207
1319
  void main(void) {
1208
1320
  vec4 vx = vec4(TexCoord.xy, coordZ, 1.0) * mtx;
1209
1321
  float f = (scl_slope * float(texture(intensityVol, vx.xyz).r)) + scl_inter;
1322
+ bool isNegative = (f < 0.0);
1210
1323
  float r = max(0.00001, abs(cal_max - cal_min));
1211
1324
  float mn = min(cal_min, cal_max);
1212
- f = mix(0.0, 1.0, (f - mn) / r);
1213
- //float y = 1.0 / numLayers;
1214
- //y = ((layer + 0.5) * y);
1325
+ float txl = mix(0.0, 1.0, (f - mn) / r);
1215
1326
  //https://stackoverflow.com/questions/5879403/opengl-texture-coordinates-in-pixel-space
1216
- float y = (2.0 * layer + 1.0)/(2.0 * numLayers);
1217
- FragColor = texture(colormap, vec2(f, y)).rgba;
1327
+ float nlayer = float(textureSize(colormap, 0).y) * 0.5; //0.5 as both each layer has positive and negative color slot
1328
+ float y = (2.0 * layer + 1.0)/(4.0 * nlayer);
1329
+ FragColor = texture(colormap, vec2(txl, y)).rgba;
1330
+ if (isNegative) {
1331
+ y = (2.0 * layer + nlayer + nlayer + 1.0)/(4.0 * nlayer);
1332
+ //select texels at positions 0 and 1 of lookup table:
1333
+ vec4 v0 = texture(colormap, vec2(0.5/256.0, y));
1334
+ vec4 v1 = texture(colormap, vec2(1.5/256.0, y));
1335
+ txl = mix(0.0, 1.0, (- f - mn) / r);
1336
+ //detect bogus color: negative color slot not used than
1337
+ // v0 = 1,1,1,0 and v1 = 0,0,0,1
1338
+ if ((v0.r != 1.0) || (v0.a != 0.0) || (v1.r != 0.0) || (v1.a != 1.0))
1339
+ FragColor = texture(colormap, vec2(txl, y));
1340
+ }
1218
1341
  if (layer > 0.7)
1219
1342
  FragColor.a = step(0.00001, FragColor.a);
1343
+ if (modulation > 0)
1344
+ FragColor.rgb *= texture(modulationVol, vx.xyz).r;
1220
1345
  FragColor.a *= opacity;
1221
1346
  if (layer < 1.0) return;
1222
1347
  vec2 texXY = TexCoord.xy*0.5 +vec2(0.5,0.5);
@@ -1234,7 +1359,7 @@ in vec2 TexCoord;
1234
1359
  out vec4 FragColor;
1235
1360
  uniform float coordZ;
1236
1361
  uniform float layer;
1237
- uniform float numLayers;
1362
+ //uniform float numLayers;
1238
1363
  uniform float scl_slope;
1239
1364
  uniform float scl_inter;
1240
1365
  uniform float cal_max;
@@ -1244,10 +1369,14 @@ uniform lowp sampler3D blend3D;
1244
1369
  uniform float opacity;
1245
1370
  uniform mat4 mtx;
1246
1371
  uniform bool hasAlpha;
1372
+ uniform int modulation;
1373
+ uniform highp sampler3D modulationVol;
1247
1374
  void main(void) {
1248
1375
  vec4 vx = vec4(TexCoord.xy, coordZ, 1.0) * mtx;
1249
1376
  uvec4 aColor = texture(intensityVol, vx.xyz);
1250
1377
  FragColor = vec4(float(aColor.r) / 255.0, float(aColor.g) / 255.0, float(aColor.b) / 255.0, float(aColor.a) / 255.0);
1378
+ if (modulation > 0)
1379
+ FragColor.rgb *= texture(modulationVol, vx.xyz).r;
1251
1380
  if (!hasAlpha)
1252
1381
  FragColor.a = (FragColor.r * 0.21 + FragColor.g * 0.72 + FragColor.b * 0.07);
1253
1382
  FragColor.a *= opacity;
@@ -1290,15 +1419,13 @@ var fragGrowCutShader = `#version 300 es
1290
1419
  layout(location = 0) out int label;
1291
1420
  layout(location = 1) out int strength;
1292
1421
  in vec2 TexCoord;
1293
- //out vec4 FragColor;
1422
+ uniform int finalPass;
1294
1423
  uniform float coordZ;
1295
1424
  uniform lowp sampler3D in3D;
1296
1425
  uniform highp isampler3D inputTexture0; // background
1297
1426
  uniform highp isampler3D inputTexture1; // label
1298
1427
  uniform highp isampler3D inputTexture2; // strength
1299
1428
  void main(void) {
1300
- //int MAX_STRENGTH = int(255);
1301
- int MAX_STRENGTH = int(10000);
1302
1429
  vec3 interpolatedTextureCoordinate = vec3(TexCoord.xy, coordZ);
1303
1430
  ivec3 size = textureSize(inputTexture0, 0);
1304
1431
  ivec3 texelIndex = ivec3(floor(interpolatedTextureCoordinate * vec3(size)));
@@ -1322,6 +1449,29 @@ void main(void) {
1322
1449
  }
1323
1450
  }
1324
1451
  }
1452
+ if (finalPass < 1)
1453
+ return;
1454
+ int ok = 1;
1455
+ ivec4 labelCount = ivec4(0,0,0,0);
1456
+ for (int k = -1; k <= 1; k++)
1457
+ for (int j = -1; j <= 1; j++)
1458
+ for (int i = -1; i <= 1; i++) {
1459
+ ivec3 neighborIndex = texelIndex + ivec3(i,j,k);
1460
+ int ilabel = texelFetch(inputTexture1, neighborIndex, 0).r;
1461
+ if ((ilabel < 0) || (ilabel > 3))
1462
+ ok = 0;
1463
+ else
1464
+ labelCount[ilabel]++;
1465
+ }
1466
+ if (ok != 1) {
1467
+ return;
1468
+ }
1469
+ int maxIdx = 0;
1470
+ for (int i = 1; i < 4; i++) {
1471
+ if (labelCount[i] > labelCount[maxIdx])
1472
+ maxIdx = i;
1473
+ }
1474
+ label = maxIdx;
1325
1475
  }`;
1326
1476
  var vertSurfaceShader = `#version 300 es
1327
1477
  layout(location=0) in vec3 pos;
@@ -1373,6 +1523,22 @@ void main(void) {
1373
1523
  vV = -vec3(modelMtx*vec4(pos,1.0));
1374
1524
  vClr = clr;
1375
1525
  }`;
1526
+ var fragMeshDepthShader = `#version 300 es
1527
+ precision highp int;
1528
+ precision highp float;
1529
+ uniform float opacity;
1530
+ out vec4 color;
1531
+ vec4 packFloatToVec4i(const float value) {
1532
+ //this Papaya function uses BSD 3-clause license Copyright (c) 2012-2015, RII-UTHSCSA
1533
+ const vec4 bitSh = vec4(256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0);
1534
+ const vec4 bitMsk = vec4(0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0);
1535
+ vec4 res = fract(value * bitSh);
1536
+ res -= res.xxyz * bitMsk;
1537
+ return res;
1538
+ }
1539
+ void main() {
1540
+ color = packFloatToVec4i(gl_FragCoord.z);
1541
+ }`;
1376
1542
  var fragMeshToonShader = `#version 300 es
1377
1543
  precision highp int;
1378
1544
  precision highp float;
@@ -1672,6 +1838,18 @@ function __extends(d, b) {
1672
1838
  }
1673
1839
  d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
1674
1840
  }
1841
+ var __assign = function() {
1842
+ __assign = Object.assign || function __assign2(t) {
1843
+ for (var s, i2 = 1, n = arguments.length; i2 < n; i2++) {
1844
+ s = arguments[i2];
1845
+ for (var p in s)
1846
+ if (Object.prototype.hasOwnProperty.call(s, p))
1847
+ t[p] = s[p];
1848
+ }
1849
+ return t;
1850
+ };
1851
+ return __assign.apply(this, arguments);
1852
+ };
1675
1853
  function __values(o) {
1676
1854
  var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i2 = 0;
1677
1855
  if (m)
@@ -1916,6 +2094,22 @@ function reportUnhandledError(err2) {
1916
2094
  }
1917
2095
  function noop() {
1918
2096
  }
2097
+ var COMPLETE_NOTIFICATION = function() {
2098
+ return createNotification("C", void 0, void 0);
2099
+ }();
2100
+ function errorNotification(error) {
2101
+ return createNotification("E", void 0, error);
2102
+ }
2103
+ function nextNotification(value2) {
2104
+ return createNotification("N", value2, void 0);
2105
+ }
2106
+ function createNotification(kind, value2, error) {
2107
+ return {
2108
+ kind,
2109
+ value: value2,
2110
+ error
2111
+ };
2112
+ }
1919
2113
  var context = null;
1920
2114
  function errorContext(cb) {
1921
2115
  if (config.useDeprecatedSynchronousErrorHandling) {
@@ -1954,24 +2148,24 @@ var Subscriber = function(_super) {
1954
2148
  return new SafeSubscriber(next, error, complete);
1955
2149
  };
1956
2150
  Subscriber2.prototype.next = function(value2) {
1957
- if (this.isStopped)
1958
- ;
1959
- else {
2151
+ if (this.isStopped) {
2152
+ handleStoppedNotification(nextNotification(value2), this);
2153
+ } else {
1960
2154
  this._next(value2);
1961
2155
  }
1962
2156
  };
1963
2157
  Subscriber2.prototype.error = function(err2) {
1964
- if (this.isStopped)
1965
- ;
1966
- else {
2158
+ if (this.isStopped) {
2159
+ handleStoppedNotification(errorNotification(err2), this);
2160
+ } else {
1967
2161
  this.isStopped = true;
1968
2162
  this._error(err2);
1969
2163
  }
1970
2164
  };
1971
2165
  Subscriber2.prototype.complete = function() {
1972
- if (this.isStopped)
1973
- ;
1974
- else {
2166
+ if (this.isStopped) {
2167
+ handleStoppedNotification(COMPLETE_NOTIFICATION, this);
2168
+ } else {
1975
2169
  this.isStopped = true;
1976
2170
  this._complete();
1977
2171
  }
@@ -2084,6 +2278,12 @@ function handleUnhandledError(error) {
2084
2278
  function defaultErrorHandler(err2) {
2085
2279
  throw err2;
2086
2280
  }
2281
+ function handleStoppedNotification(notification, subscriber) {
2282
+ var onStoppedNotification = config.onStoppedNotification;
2283
+ onStoppedNotification && timeoutProvider.setTimeout(function() {
2284
+ return onStoppedNotification(notification, subscriber);
2285
+ });
2286
+ }
2087
2287
  var EMPTY_OBSERVER = {
2088
2288
  closed: true,
2089
2289
  next: noop,
@@ -2361,6 +2561,289 @@ var AnonymousSubject = function(_super) {
2361
2561
  };
2362
2562
  return AnonymousSubject2;
2363
2563
  }(Subject);
2564
+ var dateTimestampProvider = {
2565
+ now: function() {
2566
+ return (dateTimestampProvider.delegate || Date).now();
2567
+ },
2568
+ delegate: void 0
2569
+ };
2570
+ var ReplaySubject = function(_super) {
2571
+ __extends(ReplaySubject2, _super);
2572
+ function ReplaySubject2(_bufferSize, _windowTime, _timestampProvider) {
2573
+ if (_bufferSize === void 0) {
2574
+ _bufferSize = Infinity;
2575
+ }
2576
+ if (_windowTime === void 0) {
2577
+ _windowTime = Infinity;
2578
+ }
2579
+ if (_timestampProvider === void 0) {
2580
+ _timestampProvider = dateTimestampProvider;
2581
+ }
2582
+ var _this = _super.call(this) || this;
2583
+ _this._bufferSize = _bufferSize;
2584
+ _this._windowTime = _windowTime;
2585
+ _this._timestampProvider = _timestampProvider;
2586
+ _this._buffer = [];
2587
+ _this._infiniteTimeWindow = true;
2588
+ _this._infiniteTimeWindow = _windowTime === Infinity;
2589
+ _this._bufferSize = Math.max(1, _bufferSize);
2590
+ _this._windowTime = Math.max(1, _windowTime);
2591
+ return _this;
2592
+ }
2593
+ ReplaySubject2.prototype.next = function(value2) {
2594
+ var _a2 = this, isStopped = _a2.isStopped, _buffer = _a2._buffer, _infiniteTimeWindow = _a2._infiniteTimeWindow, _timestampProvider = _a2._timestampProvider, _windowTime = _a2._windowTime;
2595
+ if (!isStopped) {
2596
+ _buffer.push(value2);
2597
+ !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);
2598
+ }
2599
+ this._trimBuffer();
2600
+ _super.prototype.next.call(this, value2);
2601
+ };
2602
+ ReplaySubject2.prototype._subscribe = function(subscriber) {
2603
+ this._throwIfClosed();
2604
+ this._trimBuffer();
2605
+ var subscription = this._innerSubscribe(subscriber);
2606
+ var _a2 = this, _infiniteTimeWindow = _a2._infiniteTimeWindow, _buffer = _a2._buffer;
2607
+ var copy2 = _buffer.slice();
2608
+ for (var i2 = 0; i2 < copy2.length && !subscriber.closed; i2 += _infiniteTimeWindow ? 1 : 2) {
2609
+ subscriber.next(copy2[i2]);
2610
+ }
2611
+ this._checkFinalizedStatuses(subscriber);
2612
+ return subscription;
2613
+ };
2614
+ ReplaySubject2.prototype._trimBuffer = function() {
2615
+ var _a2 = this, _bufferSize = _a2._bufferSize, _timestampProvider = _a2._timestampProvider, _buffer = _a2._buffer, _infiniteTimeWindow = _a2._infiniteTimeWindow;
2616
+ var adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;
2617
+ _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);
2618
+ if (!_infiniteTimeWindow) {
2619
+ var now = _timestampProvider.now();
2620
+ var last = 0;
2621
+ for (var i2 = 1; i2 < _buffer.length && _buffer[i2] <= now; i2 += 2) {
2622
+ last = i2;
2623
+ }
2624
+ last && _buffer.splice(0, last + 1);
2625
+ }
2626
+ };
2627
+ return ReplaySubject2;
2628
+ }(Subject);
2629
+ var Action = function(_super) {
2630
+ __extends(Action2, _super);
2631
+ function Action2(scheduler, work) {
2632
+ return _super.call(this) || this;
2633
+ }
2634
+ Action2.prototype.schedule = function(state, delay) {
2635
+ return this;
2636
+ };
2637
+ return Action2;
2638
+ }(Subscription);
2639
+ var intervalProvider = {
2640
+ setInterval: function(handler, timeout) {
2641
+ var args = [];
2642
+ for (var _i = 2; _i < arguments.length; _i++) {
2643
+ args[_i - 2] = arguments[_i];
2644
+ }
2645
+ var delegate = intervalProvider.delegate;
2646
+ if (delegate === null || delegate === void 0 ? void 0 : delegate.setInterval) {
2647
+ return delegate.setInterval.apply(delegate, __spreadArray([handler, timeout], __read(args)));
2648
+ }
2649
+ return setInterval.apply(void 0, __spreadArray([handler, timeout], __read(args)));
2650
+ },
2651
+ clearInterval: function(handle) {
2652
+ var delegate = intervalProvider.delegate;
2653
+ return ((delegate === null || delegate === void 0 ? void 0 : delegate.clearInterval) || clearInterval)(handle);
2654
+ },
2655
+ delegate: void 0
2656
+ };
2657
+ var AsyncAction = function(_super) {
2658
+ __extends(AsyncAction2, _super);
2659
+ function AsyncAction2(scheduler, work) {
2660
+ var _this = _super.call(this, scheduler, work) || this;
2661
+ _this.scheduler = scheduler;
2662
+ _this.work = work;
2663
+ _this.pending = false;
2664
+ return _this;
2665
+ }
2666
+ AsyncAction2.prototype.schedule = function(state, delay) {
2667
+ if (delay === void 0) {
2668
+ delay = 0;
2669
+ }
2670
+ if (this.closed) {
2671
+ return this;
2672
+ }
2673
+ this.state = state;
2674
+ var id = this.id;
2675
+ var scheduler = this.scheduler;
2676
+ if (id != null) {
2677
+ this.id = this.recycleAsyncId(scheduler, id, delay);
2678
+ }
2679
+ this.pending = true;
2680
+ this.delay = delay;
2681
+ this.id = this.id || this.requestAsyncId(scheduler, this.id, delay);
2682
+ return this;
2683
+ };
2684
+ AsyncAction2.prototype.requestAsyncId = function(scheduler, _id, delay) {
2685
+ if (delay === void 0) {
2686
+ delay = 0;
2687
+ }
2688
+ return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);
2689
+ };
2690
+ AsyncAction2.prototype.recycleAsyncId = function(_scheduler, id, delay) {
2691
+ if (delay === void 0) {
2692
+ delay = 0;
2693
+ }
2694
+ if (delay != null && this.delay === delay && this.pending === false) {
2695
+ return id;
2696
+ }
2697
+ intervalProvider.clearInterval(id);
2698
+ return void 0;
2699
+ };
2700
+ AsyncAction2.prototype.execute = function(state, delay) {
2701
+ if (this.closed) {
2702
+ return new Error("executing a cancelled action");
2703
+ }
2704
+ this.pending = false;
2705
+ var error = this._execute(state, delay);
2706
+ if (error) {
2707
+ return error;
2708
+ } else if (this.pending === false && this.id != null) {
2709
+ this.id = this.recycleAsyncId(this.scheduler, this.id, null);
2710
+ }
2711
+ };
2712
+ AsyncAction2.prototype._execute = function(state, _delay) {
2713
+ var errored = false;
2714
+ var errorValue;
2715
+ try {
2716
+ this.work(state);
2717
+ } catch (e) {
2718
+ errored = true;
2719
+ errorValue = e ? e : new Error("Scheduled action threw falsy error");
2720
+ }
2721
+ if (errored) {
2722
+ this.unsubscribe();
2723
+ return errorValue;
2724
+ }
2725
+ };
2726
+ AsyncAction2.prototype.unsubscribe = function() {
2727
+ if (!this.closed) {
2728
+ var _a2 = this, id = _a2.id, scheduler = _a2.scheduler;
2729
+ var actions = scheduler.actions;
2730
+ this.work = this.state = this.scheduler = null;
2731
+ this.pending = false;
2732
+ arrRemove(actions, this);
2733
+ if (id != null) {
2734
+ this.id = this.recycleAsyncId(scheduler, id, null);
2735
+ }
2736
+ this.delay = null;
2737
+ _super.prototype.unsubscribe.call(this);
2738
+ }
2739
+ };
2740
+ return AsyncAction2;
2741
+ }(Action);
2742
+ var Scheduler = function() {
2743
+ function Scheduler2(schedulerActionCtor, now) {
2744
+ if (now === void 0) {
2745
+ now = Scheduler2.now;
2746
+ }
2747
+ this.schedulerActionCtor = schedulerActionCtor;
2748
+ this.now = now;
2749
+ }
2750
+ Scheduler2.prototype.schedule = function(work, delay, state) {
2751
+ if (delay === void 0) {
2752
+ delay = 0;
2753
+ }
2754
+ return new this.schedulerActionCtor(this, work).schedule(state, delay);
2755
+ };
2756
+ Scheduler2.now = dateTimestampProvider.now;
2757
+ return Scheduler2;
2758
+ }();
2759
+ var AsyncScheduler = function(_super) {
2760
+ __extends(AsyncScheduler2, _super);
2761
+ function AsyncScheduler2(SchedulerAction, now) {
2762
+ if (now === void 0) {
2763
+ now = Scheduler.now;
2764
+ }
2765
+ var _this = _super.call(this, SchedulerAction, now) || this;
2766
+ _this.actions = [];
2767
+ _this._active = false;
2768
+ _this._scheduled = void 0;
2769
+ return _this;
2770
+ }
2771
+ AsyncScheduler2.prototype.flush = function(action) {
2772
+ var actions = this.actions;
2773
+ if (this._active) {
2774
+ actions.push(action);
2775
+ return;
2776
+ }
2777
+ var error;
2778
+ this._active = true;
2779
+ do {
2780
+ if (error = action.execute(action.state, action.delay)) {
2781
+ break;
2782
+ }
2783
+ } while (action = actions.shift());
2784
+ this._active = false;
2785
+ if (error) {
2786
+ while (action = actions.shift()) {
2787
+ action.unsubscribe();
2788
+ }
2789
+ throw error;
2790
+ }
2791
+ };
2792
+ return AsyncScheduler2;
2793
+ }(Scheduler);
2794
+ var asyncScheduler = new AsyncScheduler(AsyncAction);
2795
+ var async = asyncScheduler;
2796
+ function isScheduler(value2) {
2797
+ return value2 && isFunction(value2.schedule);
2798
+ }
2799
+ function isValidDate(value2) {
2800
+ return value2 instanceof Date && !isNaN(value2);
2801
+ }
2802
+ function timer(dueTime, intervalOrScheduler, scheduler) {
2803
+ if (dueTime === void 0) {
2804
+ dueTime = 0;
2805
+ }
2806
+ if (scheduler === void 0) {
2807
+ scheduler = async;
2808
+ }
2809
+ var intervalDuration = -1;
2810
+ if (intervalOrScheduler != null) {
2811
+ if (isScheduler(intervalOrScheduler)) {
2812
+ scheduler = intervalOrScheduler;
2813
+ } else {
2814
+ intervalDuration = intervalOrScheduler;
2815
+ }
2816
+ }
2817
+ return new Observable(function(subscriber) {
2818
+ var due = isValidDate(dueTime) ? +dueTime - scheduler.now() : dueTime;
2819
+ if (due < 0) {
2820
+ due = 0;
2821
+ }
2822
+ var n = 0;
2823
+ return scheduler.schedule(function() {
2824
+ if (!subscriber.closed) {
2825
+ subscriber.next(n++);
2826
+ if (0 <= intervalDuration) {
2827
+ this.schedule(void 0, intervalDuration);
2828
+ } else {
2829
+ subscriber.complete();
2830
+ }
2831
+ }
2832
+ }, due);
2833
+ });
2834
+ }
2835
+ function interval(period, scheduler) {
2836
+ if (period === void 0) {
2837
+ period = 0;
2838
+ }
2839
+ if (scheduler === void 0) {
2840
+ scheduler = asyncScheduler;
2841
+ }
2842
+ if (period < 0) {
2843
+ period = 0;
2844
+ }
2845
+ return timer(period, period, scheduler);
2846
+ }
2364
2847
  var NiivueObject3D = function(id, vertexBuffer, mode, indexCount, indexBuffer = null, vao = null) {
2365
2848
  this.BLEND = 1;
2366
2849
  this.CULL_FACE = 2;
@@ -2785,12 +3268,12 @@ var utilities$1 = { exports: {} };
2785
3268
  return data.getFloat64(start, littleEndian);
2786
3269
  };
2787
3270
  nifti3.Utils.getLongAt = function(data, start, littleEndian) {
2788
- var ctr, array = [], value2 = 0;
3271
+ var ctr, array2 = [], value2 = 0;
2789
3272
  for (ctr = 0; ctr < 8; ctr += 1) {
2790
- array[ctr] = nifti3.Utils.getByteAt(data, start + ctr, littleEndian);
3273
+ array2[ctr] = nifti3.Utils.getByteAt(data, start + ctr, littleEndian);
2791
3274
  }
2792
- for (ctr = array.length - 1; ctr >= 0; ctr--) {
2793
- value2 = value2 * 256 + array[ctr];
3275
+ for (ctr = array2.length - 1; ctr >= 0; ctr--) {
3276
+ value2 = value2 * 256 + array2[ctr];
2794
3277
  }
2795
3278
  return value2;
2796
3279
  };
@@ -7674,24 +8157,24 @@ var utilities = { exports: {} };
7674
8157
  }
7675
8158
  return tmp.buffer;
7676
8159
  };
7677
- daikon2.Utils.fillBuffer = function(array, buffer2, offset, numBytes) {
8160
+ daikon2.Utils.fillBuffer = function(array2, buffer2, offset, numBytes) {
7678
8161
  var ctr;
7679
8162
  if (numBytes === 1) {
7680
- for (ctr = 0; ctr < array.length; ctr += 1) {
7681
- buffer2.setUint8(offset + ctr, array[ctr]);
8163
+ for (ctr = 0; ctr < array2.length; ctr += 1) {
8164
+ buffer2.setUint8(offset + ctr, array2[ctr]);
7682
8165
  }
7683
8166
  } else if (numBytes === 2) {
7684
- for (ctr = 0; ctr < array.length; ctr += 1) {
7685
- buffer2.setUint16(offset + ctr * 2, array[ctr], true);
8167
+ for (ctr = 0; ctr < array2.length; ctr += 1) {
8168
+ buffer2.setUint16(offset + ctr * 2, array2[ctr], true);
7686
8169
  }
7687
8170
  }
7688
8171
  };
7689
- daikon2.Utils.fillBufferRGB = function(array, buffer2, offset) {
7690
- var r, g, b, ctr, numElements = parseInt(array.length / 3);
8172
+ daikon2.Utils.fillBufferRGB = function(array2, buffer2, offset) {
8173
+ var r, g, b, ctr, numElements = parseInt(array2.length / 3);
7691
8174
  for (ctr = 0; ctr < numElements; ctr += 1) {
7692
- r = array[ctr * 3];
7693
- g = array[ctr * 3 + 1];
7694
- b = array[ctr * 3 + 2];
8175
+ r = array2[ctr * 3];
8176
+ g = array2[ctr * 3 + 1];
8177
+ b = array2[ctr * 3 + 2];
7695
8178
  buffer2.setUint8(offset + ctr, parseInt((r + b + g) / 3), true);
7696
8179
  }
7697
8180
  };
@@ -13766,9 +14249,9 @@ var jpegBaseline = { exports: {} };
13766
14249
  }
13767
14250
  function readDataBlock() {
13768
14251
  var length2 = readUint16();
13769
- var array = data.subarray(offset, offset + length2 - 2);
13770
- offset += array.length;
13771
- return array;
14252
+ var array2 = data.subarray(offset, offset + length2 - 2);
14253
+ offset += array2.length;
14254
+ return array2;
13772
14255
  }
13773
14256
  function prepareComponents(frame2) {
13774
14257
  var mcusPerLine = Math.ceil(frame2.samplesPerLine / 8 / frame2.maxH);
@@ -16853,11 +17336,11 @@ var jpx = { exports: {} };
16853
17336
  }
16854
17337
  return dict.get(name);
16855
17338
  };
16856
- Util2.inherit = function Util_inherit(sub, base, prototype) {
16857
- sub.prototype = Object.create(base.prototype);
16858
- sub.prototype.constructor = sub;
17339
+ Util2.inherit = function Util_inherit(sub2, base, prototype) {
17340
+ sub2.prototype = Object.create(base.prototype);
17341
+ sub2.prototype.constructor = sub2;
16859
17342
  for (var prop in prototype) {
16860
- sub.prototype[prop] = prototype[prop];
17343
+ sub2.prototype[prop] = prototype[prop];
16861
17344
  }
16862
17345
  };
16863
17346
  Util2.loadScript = function Util_loadScript(src, callback) {
@@ -18476,10 +18959,10 @@ var require$$1 = /* @__PURE__ */ getAugmentedNamespace(__viteBrowserExternal$1);
18476
18959
  return u8array;
18477
18960
  }
18478
18961
  Module["intArrayFromString"] = intArrayFromString;
18479
- function intArrayToString(array) {
18962
+ function intArrayToString(array2) {
18480
18963
  var ret = [];
18481
- for (var i2 = 0; i2 < array.length; i2++) {
18482
- var chr = array[i2];
18964
+ for (var i2 = 0; i2 < array2.length; i2++) {
18965
+ var chr = array2[i2];
18483
18966
  if (chr > 255) {
18484
18967
  chr &= 255;
18485
18968
  }
@@ -18489,18 +18972,18 @@ var require$$1 = /* @__PURE__ */ getAugmentedNamespace(__viteBrowserExternal$1);
18489
18972
  }
18490
18973
  Module["intArrayToString"] = intArrayToString;
18491
18974
  function writeStringToMemory(string, buffer2, dontAddNull) {
18492
- var array = intArrayFromString(string, dontAddNull);
18975
+ var array2 = intArrayFromString(string, dontAddNull);
18493
18976
  var i2 = 0;
18494
- while (i2 < array.length) {
18495
- var chr = array[i2];
18977
+ while (i2 < array2.length) {
18978
+ var chr = array2[i2];
18496
18979
  HEAP8[buffer2 + i2 >> 0] = chr;
18497
18980
  i2 = i2 + 1;
18498
18981
  }
18499
18982
  }
18500
18983
  Module["writeStringToMemory"] = writeStringToMemory;
18501
- function writeArrayToMemory(array, buffer2) {
18502
- for (var i2 = 0; i2 < array.length; i2++) {
18503
- HEAP8[buffer2++ >> 0] = array[i2];
18984
+ function writeArrayToMemory(array2, buffer2) {
18985
+ for (var i2 = 0; i2 < array2.length; i2++) {
18986
+ HEAP8[buffer2++ >> 0] = array2[i2];
18504
18987
  }
18505
18988
  }
18506
18989
  Module["writeArrayToMemory"] = writeArrayToMemory;
@@ -18674,9 +19157,9 @@ var require$$1 = /* @__PURE__ */ getAugmentedNamespace(__viteBrowserExternal$1);
18674
19157
  function __isLeapYear(year) {
18675
19158
  return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
18676
19159
  }
18677
- function __arraySum(array, index) {
19160
+ function __arraySum(array2, index) {
18678
19161
  var sum = 0;
18679
- for (var i2 = 0; i2 <= index; sum += array[i2++])
19162
+ for (var i2 = 0; i2 <= index; sum += array2[i2++])
18680
19163
  ;
18681
19164
  return sum;
18682
19165
  }
@@ -103059,7 +103542,7 @@ function isPlatformLittleEndian() {
103059
103542
  new DataView(buffer2).setInt16(0, 256, true);
103060
103543
  return new Int16Array(buffer2)[0] === 256;
103061
103544
  }
103062
- function NVImage(dataBuffer, name = "", colorMap = "gray", opacity = 1, pairedImgData = null, cal_min = NaN, cal_max = NaN, trustCalMinMax = true, percentileFrac = 0.02, ignoreZeroVoxels = false, visible = true, isDICOMDIR = false, useQFormNotSForm = false) {
103545
+ function NVImage(dataBuffer, name = "", colorMap = "gray", opacity = 1, pairedImgData = null, cal_min = NaN, cal_max = NaN, trustCalMinMax = true, percentileFrac = 0.02, ignoreZeroVoxels = false, visible = true, isDICOMDIR = false, useQFormNotSForm = false, colorMapNegative = "") {
103063
103546
  this.DT_NONE = 0;
103064
103547
  this.DT_UNKNOWN = 0;
103065
103548
  this.DT_BINARY = 1;
@@ -103088,7 +103571,9 @@ function NVImage(dataBuffer, name = "", colorMap = "gray", opacity = 1, pairedIm
103088
103571
  this.percentileFrac = percentileFrac;
103089
103572
  this.ignoreZeroVoxels = ignoreZeroVoxels;
103090
103573
  this.trustCalMinMax = trustCalMinMax;
103574
+ this.colorMapNegative = colorMapNegative;
103091
103575
  this.visible = visible;
103576
+ this.modulationImage = null;
103092
103577
  this.series = [];
103093
103578
  if (!dataBuffer) {
103094
103579
  return;
@@ -103140,7 +103625,7 @@ function NVImage(dataBuffer, name = "", colorMap = "gray", opacity = 1, pairedIm
103140
103625
  let nVol4D = imgRaw.byteLength / this.nVox3D / (this.hdr.numBitsPerVoxel / 8);
103141
103626
  if (nVol4D !== this.nFrame4D)
103142
103627
  console.log("This header does not match voxel data", this.hdr, imgRaw.byteLength);
103143
- if (this.hdr.intent_code === 1007 && this.nFrame4D === 3 && this.hdr.datatypeCode === this.DT_FLOAT) {
103628
+ if ((this.hdr.intent_code === 1007 || this.hdr.intent_code === 2003) && this.nFrame4D === 3 && this.hdr.datatypeCode === this.DT_FLOAT) {
103144
103629
  let tmp = new Float32Array(imgRaw);
103145
103630
  let f32 = tmp.slice();
103146
103631
  this.hdr.datatypeCode = this.DT_RGB;
@@ -103352,7 +103837,25 @@ function NVImage(dataBuffer, name = "", colorMap = "gray", opacity = 1, pairedIm
103352
103837
  this.hdr.cal_max = cal_max;
103353
103838
  this.calMinMax();
103354
103839
  }
103840
+ NVImage.prototype.computeObliqueAngle = function(mtx44) {
103841
+ let mtx = clone$1(mtx44);
103842
+ transpose(mtx, mtx44);
103843
+ let dxtmp = Math.sqrt(mtx[0] * mtx[0] + mtx[1] * mtx[1] + mtx[2] * mtx[2]);
103844
+ let xmax = Math.max(Math.max(Math.abs(mtx[0]), Math.abs(mtx[1])), Math.abs(mtx[2])) / dxtmp;
103845
+ let dytmp = Math.sqrt(mtx[4] * mtx[4] + mtx[5] * mtx[5] + mtx[6] * mtx[6]);
103846
+ let ymax = Math.max(Math.max(Math.abs(mtx[4]), Math.abs(mtx[5])), Math.abs(mtx[6])) / dytmp;
103847
+ let dztmp = Math.sqrt(mtx[8] * mtx[8] + mtx[9] * mtx[9] + mtx[10] * mtx[10]);
103848
+ let zmax = Math.max(Math.max(Math.abs(mtx[8]), Math.abs(mtx[9])), Math.abs(mtx[10])) / dztmp;
103849
+ let fig_merit = Math.min(Math.min(xmax, ymax), zmax);
103850
+ let oblique_angle = Math.abs(Math.acos(fig_merit) * 180 / 3.141592653);
103851
+ if (oblique_angle > 0.01)
103852
+ console.log("Warning voxels not aligned with world space: " + oblique_angle + " degrees from plumb.\n");
103853
+ else
103854
+ oblique_angle = 0;
103855
+ return oblique_angle;
103856
+ };
103355
103857
  NVImage.prototype.calculateOblique = function() {
103858
+ this.oblique_angle = this.computeObliqueAngle(this.matRAS);
103356
103859
  let LPI = this.vox2mm([0, 0, 0], this.matRAS);
103357
103860
  let X1mm = this.vox2mm([1 / this.pixDimsRAS[1], 0, 0], this.matRAS);
103358
103861
  let Y1mm = this.vox2mm([0, 1 / this.pixDimsRAS[2], 0], this.matRAS);
@@ -103368,6 +103871,21 @@ NVImage.prototype.calculateOblique = function() {
103368
103871
  let maxShear = Math.max(Math.max(XY, XZ), YZ);
103369
103872
  if (maxShear > 0.1)
103370
103873
  log$2.debug("Warning: shear detected (gantry tilt) of %f degrees", maxShear);
103874
+ let dim = fromValues(this.dimsRAS[1], this.dimsRAS[2], this.dimsRAS[3], 1);
103875
+ let sform = clone$1(this.matRAS);
103876
+ transpose(sform, sform);
103877
+ let shim = fromValues(-0.5, -0.5, -0.5, 0);
103878
+ translate(sform, sform, shim);
103879
+ sform[0] *= dim[0];
103880
+ sform[1] *= dim[0];
103881
+ sform[2] *= dim[0];
103882
+ sform[4] *= dim[1];
103883
+ sform[5] *= dim[1];
103884
+ sform[6] *= dim[1];
103885
+ sform[8] *= dim[2];
103886
+ sform[9] *= dim[2];
103887
+ sform[10] *= dim[2];
103888
+ this.frac2mm = clone$1(sform);
103371
103889
  };
103372
103890
  NVImage.prototype.THD_daxes_to_NIFTI = function(xyzDelta, xyzOrigin, orientSpecific) {
103373
103891
  let hdr = this.hdr;
@@ -103768,11 +104286,12 @@ NVImage.prototype.readMGH = function(buffer2) {
103768
104286
  let hdr = this.hdr;
103769
104287
  hdr.dims = [3, 1, 1, 1, 0, 0, 0, 0];
103770
104288
  hdr.pixDims = [1, 1, 1, 1, 1, 0, 0, 0];
103771
- var reader = new DataView(buffer2);
103772
104289
  var raw = buffer2;
104290
+ var reader = new DataView(raw);
103773
104291
  if (reader.getUint8(0) === 31 && reader.getUint8(1) === 139) {
103774
- var raw = decompressSync(new Uint8Array(buffer2));
103775
- reader = new DataView(raw.buffer);
104292
+ let raw8 = decompressSync(new Uint8Array(buffer2));
104293
+ raw = raw8.buffer;
104294
+ reader = new DataView(raw);
103776
104295
  }
103777
104296
  let version = reader.getInt32(0, false);
103778
104297
  let width = reader.getInt32(4, false);
@@ -104062,7 +104581,7 @@ NVImage.prototype.readMHA = function(buffer2, pairedImgData) {
104062
104581
  line = readStr();
104063
104582
  }
104064
104583
  let mmMat = fromValues$3(hdr.pixDims[1], 0, 0, 0, hdr.pixDims[2], 0, 0, 0, hdr.pixDims[3]);
104065
- multiply$2(mat33, mmMat, mat33);
104584
+ multiply$1(mat33, mmMat, mat33);
104066
104585
  hdr.affine = [
104067
104586
  [-mat33[0], -mat33[3], -mat33[6], -offset[0]],
104068
104587
  [-mat33[1], -mat33[4], -mat33[7], -offset[1]],
@@ -104463,7 +104982,7 @@ NVImage.prototype.readNRRD = function(dataBuffer, pairedImgData) {
104463
104982
  offset[1] = -offset[1];
104464
104983
  if (rot33[8] < 0)
104465
104984
  offset[2] = -offset[2];
104466
- multiply$2(mat33, rot33, mat33);
104985
+ multiply$1(mat33, rot33, mat33);
104467
104986
  let mat = fromValues$2(mat33[0], mat33[3], mat33[6], offset[0], mat33[1], mat33[4], mat33[7], offset[1], mat33[2], mat33[5], mat33[8], offset[2], 0, 0, 0, 1);
104468
104987
  let mm000 = this.vox2mm([0, 0, 0], mat);
104469
104988
  let mm100 = this.vox2mm([1, 0, 0], mat);
@@ -104579,7 +105098,7 @@ NVImage.prototype.calculateRAS = function() {
104579
105098
  rotM[3 + 2 * 4] = (header.dims[perm[2]] - 1) * flip[2];
104580
105099
  let residualR = create$2();
104581
105100
  invert(residualR, rotM);
104582
- multiply$1(residualR, residualR, R2);
105101
+ multiply(residualR, residualR, R2);
104583
105102
  this.matRAS = clone$1(residualR);
104584
105103
  rotM = fromValues$2(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1);
104585
105104
  rotM[perm[0] - 1 + 0 * 4] = -flip[0] * 2 + 1;
@@ -104881,7 +105400,8 @@ NVImage.loadFromUrl = async function({
104881
105400
  trustCalMinMax = true,
104882
105401
  percentileFrac = 0.02,
104883
105402
  ignoreZeroVoxels = false,
104884
- visible = true
105403
+ visible = true,
105404
+ colorMapNegative = ""
104885
105405
  } = {}) {
104886
105406
  if (url === "") {
104887
105407
  throw Error("url must not be empty");
@@ -104914,7 +105434,7 @@ NVImage.loadFromUrl = async function({
104914
105434
  pairedImgData = await resp.arrayBuffer();
104915
105435
  }
104916
105436
  if (dataBuffer) {
104917
- nvimage = new NVImage(dataBuffer, name, colorMap, opacity, pairedImgData, cal_min, cal_max, trustCalMinMax, percentileFrac, ignoreZeroVoxels, visible);
105437
+ nvimage = new NVImage(dataBuffer, name, colorMap, opacity, pairedImgData, cal_min, cal_max, trustCalMinMax, percentileFrac, ignoreZeroVoxels, visible, false, false, colorMapNegative);
104918
105438
  } else {
104919
105439
  alert("Unable to load buffer properly from volume");
104920
105440
  }
@@ -105114,14 +105634,20 @@ function getExtents$1(positions, forceOriginInVolume = true) {
105114
105634
  return { min: min2, max: max2, furthestVertexFromOrigin, origin };
105115
105635
  }
105116
105636
  NVImage.prototype.toNiivueObject3D = function(id, gl) {
105117
- let LPI = this.vox2mm([0, 0, 0], this.matRAS);
105118
- let LAI = this.vox2mm([0, this.dimsRAS[2] - 1, 0], this.matRAS);
105119
- let LPS = this.vox2mm([0, 0, this.dimsRAS[3] - 1], this.matRAS);
105120
- let LAS = this.vox2mm([0, this.dimsRAS[2] - 1, this.dimsRAS[3] - 1], this.matRAS);
105121
- let RPI = this.vox2mm([this.dimsRAS[1] - 1, 0, 0], this.matRAS);
105122
- let RAI = this.vox2mm([this.dimsRAS[1] - 1, this.dimsRAS[2] - 1, 0], this.matRAS);
105123
- let RPS = this.vox2mm([this.dimsRAS[1] - 1, 0, this.dimsRAS[3] - 1], this.matRAS);
105124
- let RAS = this.vox2mm([this.dimsRAS[1] - 1, this.dimsRAS[2] - 1, this.dimsRAS[3] - 1], this.matRAS);
105637
+ let L = -0.5;
105638
+ let P = -0.5;
105639
+ let I2 = -0.5;
105640
+ let R2 = this.dimsRAS[1] - 1 + 0.5;
105641
+ let A2 = this.dimsRAS[2] - 1 + 0.5;
105642
+ let S = this.dimsRAS[3] - 1 + 0.5;
105643
+ let LPI = this.vox2mm([L, P, I2], this.matRAS);
105644
+ let LAI = this.vox2mm([L, A2, I2], this.matRAS);
105645
+ let LPS = this.vox2mm([L, P, S], this.matRAS);
105646
+ let LAS = this.vox2mm([L, A2, S], this.matRAS);
105647
+ let RPI = this.vox2mm([R2, P, I2], this.matRAS);
105648
+ let RAI = this.vox2mm([R2, A2, I2], this.matRAS);
105649
+ let RPS = this.vox2mm([R2, P, S], this.matRAS);
105650
+ let RAS = this.vox2mm([R2, A2, S], this.matRAS);
105125
105651
  let posTex = [
105126
105652
  ...LPS,
105127
105653
  ...[0, 0, 1],
@@ -105204,11 +105730,16 @@ NVImage.prototype.toNiivueObject3D = function(id, gl) {
105204
105730
  ...RAI,
105205
105731
  ...RPI
105206
105732
  ]);
105207
- obj3D.extentsMin = extents.min;
105208
- obj3D.extentsMax = extents.max;
105733
+ obj3D.extentsMin = extents.min.slice();
105734
+ obj3D.extentsMax = extents.max.slice();
105209
105735
  obj3D.furthestVertexFromOrigin = extents.furthestVertexFromOrigin;
105210
105736
  obj3D.originNegate = clone(extents.origin);
105211
105737
  negate(obj3D.originNegate, obj3D.originNegate);
105738
+ obj3D.fieldOfViewDeObliqueMM = [
105739
+ this.dimsRAS[1] * this.pixDimsRAS[1],
105740
+ this.dimsRAS[2] * this.pixDimsRAS[2],
105741
+ this.dimsRAS[3] * this.pixDimsRAS[3]
105742
+ ];
105212
105743
  return obj3D;
105213
105744
  };
105214
105745
  const colortables = function() {
@@ -105260,6 +105791,8 @@ colortables.prototype.makeLut = function(Rs, Gs, Bs, As, Is) {
105260
105791
  if (this.gamma === 1)
105261
105792
  return lut;
105262
105793
  for (var i2 = 0; i2 < 255 * 4; i2++) {
105794
+ if (i2 % 4 === 3)
105795
+ continue;
105263
105796
  lut[i2] = Math.pow(lut[i2] / 255, 1 / this.gamma) * 255;
105264
105797
  }
105265
105798
  return lut;
@@ -105933,7 +106466,7 @@ NVMesh.readTRK = function(buffer2) {
105933
106466
  identity$1(mat);
105934
106467
  }
105935
106468
  var vox2mmMat = create$2();
105936
- mul$1(vox2mmMat, mat, zoomMat);
106469
+ mul(vox2mmMat, mat, zoomMat);
105937
106470
  let i32 = null;
105938
106471
  let f32 = null;
105939
106472
  i32 = new Int32Array(buffer2.slice(hdr_sz));
@@ -106753,19 +107286,30 @@ NVMesh.readPLY = function(buffer2) {
106753
107286
  }
106754
107287
  let indices2 = new Int32Array(nface * 3);
106755
107288
  let f = 0;
106756
- let isTriangular2 = true;
106757
107289
  for (var i2 = 0; i2 < nface; i2++) {
106758
107290
  line = readStr();
106759
107291
  let items = line.split(/\s/);
106760
- if (parseInt(items[0]) > 3)
106761
- isTriangular2 = false;
106762
- indices2[f] = parseInt(items[1]);
106763
- indices2[f + 1] = parseInt(items[2]);
106764
- indices2[f + 2] = parseInt(items[3]);
106765
- f += 3;
106766
- }
106767
- if (!isTriangular2)
106768
- console.log("Only able to read PLY meshes limited to triangles.");
107292
+ let nTri = parseInt(items[0]) - 2;
107293
+ if (nTri < 1)
107294
+ break;
107295
+ if (f + nTri * 3 > indices2.length) {
107296
+ var c2 = new Int32Array(indices2.length + indices2.length);
107297
+ c2.set(indices2);
107298
+ indices2 = c2.slice();
107299
+ }
107300
+ let idx0 = parseInt(items[1]);
107301
+ let idx1 = parseInt(items[2]);
107302
+ for (let j2 = 0; j2 < nTri; j2++) {
107303
+ let idx2 = parseInt(items[3 + j2]);
107304
+ indices2[f + 0] = idx0;
107305
+ indices2[f + 1] = idx1;
107306
+ indices2[f + 2] = idx2;
107307
+ idx1 = idx2;
107308
+ f += 3;
107309
+ }
107310
+ }
107311
+ if (indices2.length !== f)
107312
+ indices2 = indices2.slice(0, f);
106769
107313
  return {
106770
107314
  positions: positions2,
106771
107315
  indices: indices2
@@ -106956,12 +107500,21 @@ NVMesh.readOBJ = function(buffer2) {
106956
107500
  }
106957
107501
  if (str[0] === "f") {
106958
107502
  let items = str.split(" ");
107503
+ let new_t = items.length - 3;
107504
+ if (new_t < 1)
107505
+ break;
106959
107506
  let tn = items[1].split("/");
106960
- t.push(parseInt(tn - 1));
107507
+ let t0 = parseInt(tn[0]) - 1;
106961
107508
  tn = items[2].split("/");
106962
- t.push(parseInt(tn - 1));
106963
- tn = items[3].split("/");
106964
- t.push(parseInt(tn - 1));
107509
+ let tprev = parseInt(tn[0]) - 1;
107510
+ for (let j = 0; j < new_t; j++) {
107511
+ tn = items[3 + j].split("/");
107512
+ let tcurr = parseInt(tn[0]) - 1;
107513
+ t.push(t0);
107514
+ t.push(tprev);
107515
+ t.push(tcurr);
107516
+ tprev = tcurr;
107517
+ }
106965
107518
  }
106966
107519
  }
106967
107520
  var positions = new Float32Array(pts);
@@ -109643,6 +110196,219 @@ var defaultFontMetrics = {
109643
110196
  glyphs,
109644
110197
  kerning
109645
110198
  };
110199
+ var DEFAULT_WEBSOCKET_CONFIG = {
110200
+ url: "",
110201
+ deserializer: function(e) {
110202
+ return JSON.parse(e.data);
110203
+ },
110204
+ serializer: function(value2) {
110205
+ return JSON.stringify(value2);
110206
+ }
110207
+ };
110208
+ var WEBSOCKETSUBJECT_INVALID_ERROR_OBJECT = "WebSocketSubject.error must be called with an object with an error code, and an optional reason: { code: number, reason: string }";
110209
+ var WebSocketSubject = function(_super) {
110210
+ __extends(WebSocketSubject2, _super);
110211
+ function WebSocketSubject2(urlConfigOrSource, destination) {
110212
+ var _this = _super.call(this) || this;
110213
+ _this._socket = null;
110214
+ if (urlConfigOrSource instanceof Observable) {
110215
+ _this.destination = destination;
110216
+ _this.source = urlConfigOrSource;
110217
+ } else {
110218
+ var config2 = _this._config = __assign({}, DEFAULT_WEBSOCKET_CONFIG);
110219
+ _this._output = new Subject();
110220
+ if (typeof urlConfigOrSource === "string") {
110221
+ config2.url = urlConfigOrSource;
110222
+ } else {
110223
+ for (var key2 in urlConfigOrSource) {
110224
+ if (urlConfigOrSource.hasOwnProperty(key2)) {
110225
+ config2[key2] = urlConfigOrSource[key2];
110226
+ }
110227
+ }
110228
+ }
110229
+ if (!config2.WebSocketCtor && WebSocket) {
110230
+ config2.WebSocketCtor = WebSocket;
110231
+ } else if (!config2.WebSocketCtor) {
110232
+ throw new Error("no WebSocket constructor can be found");
110233
+ }
110234
+ _this.destination = new ReplaySubject();
110235
+ }
110236
+ return _this;
110237
+ }
110238
+ WebSocketSubject2.prototype.lift = function(operator) {
110239
+ var sock = new WebSocketSubject2(this._config, this.destination);
110240
+ sock.operator = operator;
110241
+ sock.source = this;
110242
+ return sock;
110243
+ };
110244
+ WebSocketSubject2.prototype._resetState = function() {
110245
+ this._socket = null;
110246
+ if (!this.source) {
110247
+ this.destination = new ReplaySubject();
110248
+ }
110249
+ this._output = new Subject();
110250
+ };
110251
+ WebSocketSubject2.prototype.multiplex = function(subMsg, unsubMsg, messageFilter) {
110252
+ var self2 = this;
110253
+ return new Observable(function(observer) {
110254
+ try {
110255
+ self2.next(subMsg());
110256
+ } catch (err2) {
110257
+ observer.error(err2);
110258
+ }
110259
+ var subscription = self2.subscribe({
110260
+ next: function(x2) {
110261
+ try {
110262
+ if (messageFilter(x2)) {
110263
+ observer.next(x2);
110264
+ }
110265
+ } catch (err2) {
110266
+ observer.error(err2);
110267
+ }
110268
+ },
110269
+ error: function(err2) {
110270
+ return observer.error(err2);
110271
+ },
110272
+ complete: function() {
110273
+ return observer.complete();
110274
+ }
110275
+ });
110276
+ return function() {
110277
+ try {
110278
+ self2.next(unsubMsg());
110279
+ } catch (err2) {
110280
+ observer.error(err2);
110281
+ }
110282
+ subscription.unsubscribe();
110283
+ };
110284
+ });
110285
+ };
110286
+ WebSocketSubject2.prototype._connectSocket = function() {
110287
+ var _this = this;
110288
+ var _a2 = this._config, WebSocketCtor = _a2.WebSocketCtor, protocol = _a2.protocol, url = _a2.url, binaryType = _a2.binaryType;
110289
+ var observer = this._output;
110290
+ var socket = null;
110291
+ try {
110292
+ socket = protocol ? new WebSocketCtor(url, protocol) : new WebSocketCtor(url);
110293
+ this._socket = socket;
110294
+ if (binaryType) {
110295
+ this._socket.binaryType = binaryType;
110296
+ }
110297
+ } catch (e) {
110298
+ observer.error(e);
110299
+ return;
110300
+ }
110301
+ var subscription = new Subscription(function() {
110302
+ _this._socket = null;
110303
+ if (socket && socket.readyState === 1) {
110304
+ socket.close();
110305
+ }
110306
+ });
110307
+ socket.onopen = function(evt) {
110308
+ var _socket = _this._socket;
110309
+ if (!_socket) {
110310
+ socket.close();
110311
+ _this._resetState();
110312
+ return;
110313
+ }
110314
+ var openObserver = _this._config.openObserver;
110315
+ if (openObserver) {
110316
+ openObserver.next(evt);
110317
+ }
110318
+ var queue = _this.destination;
110319
+ _this.destination = Subscriber.create(function(x2) {
110320
+ if (socket.readyState === 1) {
110321
+ try {
110322
+ var serializer = _this._config.serializer;
110323
+ socket.send(serializer(x2));
110324
+ } catch (e) {
110325
+ _this.destination.error(e);
110326
+ }
110327
+ }
110328
+ }, function(err2) {
110329
+ var closingObserver = _this._config.closingObserver;
110330
+ if (closingObserver) {
110331
+ closingObserver.next(void 0);
110332
+ }
110333
+ if (err2 && err2.code) {
110334
+ socket.close(err2.code, err2.reason);
110335
+ } else {
110336
+ observer.error(new TypeError(WEBSOCKETSUBJECT_INVALID_ERROR_OBJECT));
110337
+ }
110338
+ _this._resetState();
110339
+ }, function() {
110340
+ var closingObserver = _this._config.closingObserver;
110341
+ if (closingObserver) {
110342
+ closingObserver.next(void 0);
110343
+ }
110344
+ socket.close();
110345
+ _this._resetState();
110346
+ });
110347
+ if (queue && queue instanceof ReplaySubject) {
110348
+ subscription.add(queue.subscribe(_this.destination));
110349
+ }
110350
+ };
110351
+ socket.onerror = function(e) {
110352
+ _this._resetState();
110353
+ observer.error(e);
110354
+ };
110355
+ socket.onclose = function(e) {
110356
+ if (socket === _this._socket) {
110357
+ _this._resetState();
110358
+ }
110359
+ var closeObserver = _this._config.closeObserver;
110360
+ if (closeObserver) {
110361
+ closeObserver.next(e);
110362
+ }
110363
+ if (e.wasClean) {
110364
+ observer.complete();
110365
+ } else {
110366
+ observer.error(e);
110367
+ }
110368
+ };
110369
+ socket.onmessage = function(e) {
110370
+ try {
110371
+ var deserializer = _this._config.deserializer;
110372
+ observer.next(deserializer(e));
110373
+ } catch (err2) {
110374
+ observer.error(err2);
110375
+ }
110376
+ };
110377
+ };
110378
+ WebSocketSubject2.prototype._subscribe = function(subscriber) {
110379
+ var _this = this;
110380
+ var source = this.source;
110381
+ if (source) {
110382
+ return source.subscribe(subscriber);
110383
+ }
110384
+ if (!this._socket) {
110385
+ this._connectSocket();
110386
+ }
110387
+ this._output.subscribe(subscriber);
110388
+ subscriber.add(function() {
110389
+ var _socket = _this._socket;
110390
+ if (_this._output.observers.length === 0) {
110391
+ if (_socket && (_socket.readyState === 1 || _socket.readyState === 0)) {
110392
+ _socket.close();
110393
+ }
110394
+ _this._resetState();
110395
+ }
110396
+ });
110397
+ return subscriber;
110398
+ };
110399
+ WebSocketSubject2.prototype.unsubscribe = function() {
110400
+ var _socket = this._socket;
110401
+ if (_socket && (_socket.readyState === 1 || _socket.readyState === 0)) {
110402
+ _socket.close();
110403
+ }
110404
+ this._resetState();
110405
+ _super.prototype.unsubscribe.call(this);
110406
+ };
110407
+ return WebSocketSubject2;
110408
+ }(AnonymousSubject);
110409
+ function webSocket(urlConfigOrSource) {
110410
+ return new WebSocketSubject(urlConfigOrSource);
110411
+ }
109646
110412
  const log = new Log();
109647
110413
  const cmapper = new colortables();
109648
110414
  function Niivue(options = {}) {
@@ -109651,11 +110417,13 @@ function Niivue(options = {}) {
109651
110417
  textHeight: 0.06,
109652
110418
  colorbarHeight: 0.05,
109653
110419
  crosshairWidth: 1,
110420
+ rulerWidth: 4,
109654
110421
  show3Dcrosshair: false,
109655
110422
  backColor: [0, 0, 0, 1],
109656
110423
  crosshairColor: [1, 0, 0, 1],
109657
110424
  selectionBoxColor: [1, 1, 1, 0.5],
109658
110425
  clipPlaneColor: [0.7, 0, 0.7, 0.5],
110426
+ rulerColor: [1, 0, 0, 0.8],
109659
110427
  colorBarMargin: 0.05,
109660
110428
  trustCalMinMax: true,
109661
110429
  clipPlaneHotKey: "KeyC",
@@ -109663,14 +110431,27 @@ function Niivue(options = {}) {
109663
110431
  keyDebounceTime: 50,
109664
110432
  isNearestInterpolation: false,
109665
110433
  isAtlasOutline: false,
110434
+ isRuler: false,
110435
+ isColorbar: false,
109666
110436
  isRadiologicalConvention: false,
110437
+ meshThicknessOn2D: Infinity,
110438
+ isDragShowsMeasurementTool: false,
110439
+ isCornerOrientationText: false,
110440
+ sagittalNoseLeft: false,
110441
+ isSliceMM: false,
109667
110442
  logging: false,
109668
110443
  loadingText: "waiting for images...",
109669
110444
  dragAndDropEnabled: true,
109670
110445
  drawingEnabled: false,
109671
110446
  penValue: 1,
109672
110447
  isFilledPen: false,
109673
- thumbnail: ""
110448
+ thumbnail: "",
110449
+ onLocationChange: () => {
110450
+ },
110451
+ onIntensityChange: () => {
110452
+ },
110453
+ onImageLoaded: () => {
110454
+ }
109674
110455
  };
109675
110456
  this.canvas = null;
109676
110457
  this.gl = null;
@@ -109678,8 +110459,11 @@ function Niivue(options = {}) {
109678
110459
  this.volumeTexture = null;
109679
110460
  this.drawTexture = null;
109680
110461
  this.drawBitmap = null;
109681
- this.drawUndoBitmap = null;
110462
+ this.maxDrawUndoBitmaps = 8;
110463
+ this.currentDrawUndoBitmap = this.maxDrawUndoBitmaps;
110464
+ this.drawUndoBitmaps = [];
109682
110465
  this.drawOpacity = 0.8;
110466
+ this.colorbarHeight = 0;
109683
110467
  this.drawPenLocation = [NaN, NaN, NaN];
109684
110468
  this.drawPenAxCorSag = -1;
109685
110469
  this.drawFillOverwrites = true;
@@ -109687,7 +110471,8 @@ function Niivue(options = {}) {
109687
110471
  this.overlayTexture = null;
109688
110472
  this.overlayTextureID = [];
109689
110473
  this.sliceShader = null;
109690
- this.lineShader = null;
110474
+ this.sliceMMShader = null;
110475
+ this.rectShader = null;
109691
110476
  this.graphShader = null;
109692
110477
  this.renderShader = null;
109693
110478
  this.pickingShader = null;
@@ -109720,6 +110505,7 @@ function Niivue(options = {}) {
109720
110505
  this.sliceTypeSagittal = 2;
109721
110506
  this.sliceTypeMultiplanar = 3;
109722
110507
  this.sliceTypeRender = 4;
110508
+ this.sliceMosaicString = "";
109723
110509
  this.sliceType = this.sliceTypeMultiplanar;
109724
110510
  this.scene = {};
109725
110511
  this.syncOpts = {};
@@ -109736,6 +110522,7 @@ function Niivue(options = {}) {
109736
110522
  this.scene.mouseButtonLeftDown = false;
109737
110523
  this.scene.mouseButtonRightDown = false;
109738
110524
  this.scene.mouseDepthPicker = false;
110525
+ this.scene.pan2Dxyzmm = [0, 0, 0, 1];
109739
110526
  this.scene.scale2D = 1;
109740
110527
  this.scene.prevX = 0;
109741
110528
  this.scene.prevY = 0;
@@ -109752,13 +110539,7 @@ function Niivue(options = {}) {
109752
110539
  this.volScale = [];
109753
110540
  this.vox = [];
109754
110541
  this.mousePos = [0, 0];
109755
- this.numScreenSlices = 0;
109756
- this.screenSlices = [
109757
- { leftTopWidthHeight: [1, 0, 0, 1], axCorSag: this.sliceTypeAxial },
109758
- { leftTopWidthHeight: [1, 0, 0, 1], axCorSag: this.sliceTypeAxial },
109759
- { leftTopWidthHeight: [1, 0, 0, 1], axCorSag: this.sliceTypeAxial },
109760
- { leftTopWidthHeight: [1, 0, 0, 1], axCorSag: this.sliceTypeAxial }
109761
- ];
110542
+ this.screenSlices = [];
109762
110543
  this.isDragging = false;
109763
110544
  this.dragStart = [0, 0];
109764
110545
  this.dragEnd = [0, 0];
@@ -109768,10 +110549,7 @@ function Niivue(options = {}) {
109768
110549
  this.volumeObject3D = null;
109769
110550
  this.pivot3D = [0, 0, 0];
109770
110551
  this.furthestFromPivot = 10;
109771
- this.intensityRange$ = new Subject();
109772
- this.scene.location$ = new Subject();
109773
110552
  this.scene.loading$ = new Subject();
109774
- this.imageLoaded$ = new Subject();
109775
110553
  this.currentClipPlaneIndex = 0;
109776
110554
  this.lastCalled = new Date().getTime();
109777
110555
  this.multiTouchGesture = false;
@@ -109810,8 +110588,18 @@ function Niivue(options = {}) {
109810
110588
  {
109811
110589
  Name: "Toon",
109812
110590
  Frag: fragMeshToonShader
110591
+ },
110592
+ {
110593
+ Name: "Depth",
110594
+ Frag: fragMeshDepthShader
109813
110595
  }
109814
110596
  ];
110597
+ this.isController = false;
110598
+ this.isInSession = false;
110599
+ this.sessionKey = "";
110600
+ this.sessionUrl = "";
110601
+ this.serverConnection$ = null;
110602
+ this.interval$ = null;
109815
110603
  this.initialized = false;
109816
110604
  for (let prop in this.defaults) {
109817
110605
  this.opts[prop] = options[prop] === void 0 ? this.defaults[prop] : options[prop];
@@ -109825,13 +110613,32 @@ function Niivue(options = {}) {
109825
110613
  this.loadingText = this.opts.loadingText;
109826
110614
  log.setLogLevel(this.opts.logging);
109827
110615
  this.eventsToSubjects = {
109828
- location: this.scene.location$,
109829
- loading: this.scene.loading$,
109830
- imageLoaded: this.imageLoaded$,
109831
- intensityRange: this.intensityRange$
110616
+ loading: this.scene.loading$
109832
110617
  };
109833
110618
  this.subscriptions = [];
109834
110619
  }
110620
+ Niivue.prototype.saveScene = function(filename = "") {
110621
+ function saveBlob(blob, name) {
110622
+ const a = document.createElement("a");
110623
+ document.body.appendChild(a);
110624
+ a.style.display = "none";
110625
+ const url = window.URL.createObjectURL(blob);
110626
+ a.href = url;
110627
+ a.download = name;
110628
+ a.click();
110629
+ a.remove();
110630
+ }
110631
+ let canvas = this.canvas;
110632
+ this.drawScene();
110633
+ canvas.toBlob((blob) => {
110634
+ if (filename === "") {
110635
+ filename = `niivue-screenshot-${new Date().toString()}.png`;
110636
+ filename = filename.replace(/\s/g, "_");
110637
+ }
110638
+ console.log(filename);
110639
+ saveBlob(blob, filename);
110640
+ });
110641
+ };
109835
110642
  Niivue.prototype.attachTo = async function(id) {
109836
110643
  await this.attachToCanvas(document.getElementById(id));
109837
110644
  log.debug("attached to element with id: ", id);
@@ -109902,6 +110709,85 @@ Niivue.prototype.sync = function() {
109902
110709
  }
109903
110710
  this.otherNV.drawScene();
109904
110711
  };
110712
+ Niivue.prototype.connectToServer = function(wsServerUrl, sessionName) {
110713
+ const url = new URL(wsServerUrl);
110714
+ url.pathname = "websockets";
110715
+ url.search = "?session=" + sessionName;
110716
+ this.serverConnection$ = webSocket(url.href);
110717
+ console.log(url.href);
110718
+ };
110719
+ Niivue.prototype.setUpdateInterval = function() {
110720
+ this.interval$ = interval(300);
110721
+ this.interval$.subscribe(() => {
110722
+ this.serverConnection$.next({
110723
+ op: "update",
110724
+ azimuth: this.scene.renderAzimuth,
110725
+ elevation: this.scene.renderElevation,
110726
+ clipPlane: this.scene.clipPlane,
110727
+ zoom: this.volScaleMultiplier,
110728
+ key: this.sessionKey
110729
+ });
110730
+ });
110731
+ };
110732
+ Niivue.prototype.subscribeToServer = function(sessionCreatedCallback, sessionJoinedCallback) {
110733
+ this.serverConnection$.subscribe({
110734
+ next: (msg2) => {
110735
+ switch (msg2["op"]) {
110736
+ case "update":
110737
+ this.scene.renderAzimuth = msg2["azimuth"];
110738
+ this.scene.renderElevation = msg2["elevation"];
110739
+ this.volScaleMultiplier = msg2["zoom"];
110740
+ this.scene.clipPlane = msg2["clipPlane"];
110741
+ this.drawScene();
110742
+ break;
110743
+ case "create":
110744
+ console.log(msg2);
110745
+ if (!msg2["isError"]) {
110746
+ this.isInSession = true;
110747
+ this.sessionKey = msg2["key"];
110748
+ this.setUpdateInterval();
110749
+ }
110750
+ if (sessionCreatedCallback) {
110751
+ sessionCreatedCallback(msg2["message"], msg2["url"], msg2["key"], msg2["isError"]);
110752
+ }
110753
+ break;
110754
+ case "join":
110755
+ this.isInSession = true;
110756
+ this.isController = msg2["isController"];
110757
+ if (this.isController) {
110758
+ this.setUpdateInterval();
110759
+ }
110760
+ if (sessionJoinedCallback) {
110761
+ sessionJoinedCallback(msg2["message"], msg2["url"], msg2["isController"]);
110762
+ }
110763
+ break;
110764
+ }
110765
+ },
110766
+ error: (err2) => console.log(err2),
110767
+ complete: () => console.log("complete")
110768
+ });
110769
+ };
110770
+ Niivue.prototype.createSession = function(wsServerUrl, sessionName, sessionCreatedCallback) {
110771
+ this.connectToServer(wsServerUrl, sessionName);
110772
+ this.subscribeToServer(sessionCreatedCallback);
110773
+ this.serverConnection$.next({
110774
+ op: "create"
110775
+ });
110776
+ };
110777
+ Niivue.prototype.joinSession = function(wsServerUrl, sessionName, key2, sessionJoinedCallback) {
110778
+ this.connectToServer(wsServerUrl, sessionName);
110779
+ this.subscribeToServer(null, sessionJoinedCallback);
110780
+ this.serverConnection$.next({
110781
+ op: "join",
110782
+ key: key2
110783
+ });
110784
+ };
110785
+ Niivue.prototype.closeSession = function() {
110786
+ this.interval$.complete();
110787
+ this.serverConnection$.complete();
110788
+ this.isInSession = false;
110789
+ this.isController = false;
110790
+ };
109905
110791
  Niivue.prototype.arrayEquals = function(a, b) {
109906
110792
  return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((val, index) => val === b[index]);
109907
110793
  };
@@ -109956,13 +110842,13 @@ Niivue.prototype.mouseRightButtonHandler = function(e) {
109956
110842
  this.dragClipPlaneStartDepthAziElev = this.scene.clipPlaneDepthAziElev;
109957
110843
  return;
109958
110844
  };
109959
- Niivue.prototype.calculateMinMaxVoxIdx = function(array) {
109960
- if (array.length > 2) {
110845
+ Niivue.prototype.calculateMinMaxVoxIdx = function(array2) {
110846
+ if (array2.length > 2) {
109961
110847
  throw new Error("array must not contain more than two values");
109962
110848
  }
109963
110849
  return [
109964
- Math.floor(Math.min(array[0], array[1])),
109965
- Math.floor(Math.max(array[0], array[1]))
110850
+ Math.floor(Math.min(array2[0], array2[1])),
110851
+ Math.floor(Math.max(array2[0], array2[1]))
109966
110852
  ];
109967
110853
  };
109968
110854
  function intensityRaw2Scaled(hdr, raw) {
@@ -109971,14 +110857,18 @@ function intensityRaw2Scaled(hdr, raw) {
109971
110857
  return raw * hdr.scl_slope + hdr.scl_inter;
109972
110858
  }
109973
110859
  Niivue.prototype.calculateNewRange = function(volIdx = 0) {
109974
- if (this.sliceType === this.sliceTypeRender) {
110860
+ if (this.sliceType === this.sliceTypeRender && this.sliceMosaicString.length < 1) {
109975
110861
  return;
109976
110862
  }
109977
110863
  if (this.dragStart[0] === this.dragEnd[0] && this.dragStart[1] === this.dragEnd[1])
109978
110864
  return;
109979
110865
  let frac = this.canvasPos2frac([this.dragStart[0], this.dragStart[1]]);
110866
+ if (frac[0] < 0)
110867
+ return;
109980
110868
  let startVox = this.frac2vox(frac, volIdx);
109981
110869
  frac = this.canvasPos2frac([this.dragEnd[0], this.dragEnd[1]]);
110870
+ if (frac[0] < 0)
110871
+ return;
109982
110872
  let endVox = this.frac2vox(frac, volIdx);
109983
110873
  let hi = -Number.MAX_VALUE, lo = Number.MAX_VALUE;
109984
110874
  let xrange;
@@ -110019,7 +110909,7 @@ Niivue.prototype.calculateNewRange = function(volIdx = 0) {
110019
110909
  var mxScale = intensityRaw2Scaled(hdr, hi);
110020
110910
  this.volumes[volIdx].cal_min = mnScale;
110021
110911
  this.volumes[volIdx].cal_max = mxScale;
110022
- this.intensityRange$.next(this.volumes[volIdx]);
110912
+ this.opts.onIntensityChange(this.volumes[volIdx]);
110023
110913
  };
110024
110914
  Niivue.prototype.mouseUpListener = function() {
110025
110915
  this.scene.mousedown = false;
@@ -110027,10 +110917,14 @@ Niivue.prototype.mouseUpListener = function() {
110027
110917
  this.scene.mouseButtonLeftDown = false;
110028
110918
  if (this.drawPenFillPts.length > 0)
110029
110919
  this.drawPenFilled();
110920
+ else if (this.drawPenAxCorSag >= 0)
110921
+ this.drawAddUndoBitmap();
110030
110922
  this.drawPenLocation = [NaN, NaN, NaN];
110031
110923
  this.drawPenAxCorSag = -1;
110032
110924
  if (this.isDragging) {
110033
110925
  this.isDragging = false;
110926
+ if (this.opts.isDragShowsMeasurementTool)
110927
+ return;
110034
110928
  this.calculateNewRange();
110035
110929
  this.refreshLayers(this.volumes[0], 0, this.volumes.length);
110036
110930
  }
@@ -110073,15 +110967,24 @@ Niivue.prototype.mouseMoveListener = function(e) {
110073
110967
  this.scene.prevY = this.scene.currY;
110074
110968
  }
110075
110969
  };
110076
- Niivue.prototype.resetBriCon = function() {
110970
+ Niivue.prototype.resetBriCon = function(msg2 = null) {
110971
+ let isRender = false;
110077
110972
  if (this.sliceType === this.sliceTypeRender) {
110973
+ isRender = true;
110974
+ }
110975
+ if (msg2 !== null) {
110976
+ if (this.inRenderTile(msg2.offsetX, msg2.offsetY) >= 0)
110977
+ isRender = true;
110978
+ }
110979
+ if (isRender) {
110078
110980
  this.scene.mouseDepthPicker = true;
110079
110981
  this.drawScene();
110982
+ this.drawScene();
110080
110983
  return;
110081
110984
  }
110082
110985
  this.volumes[0].cal_min = this.volumes[0].robust_min;
110083
110986
  this.volumes[0].cal_max = this.volumes[0].robust_max;
110084
- this.intensityRange$.next(this.volumes[0]);
110987
+ this.opts.onIntensityChange(this.volumes[0]);
110085
110988
  this.refreshLayers(this.volumes[0], 0, this.volumes.length);
110086
110989
  this.drawScene();
110087
110990
  };
@@ -110112,9 +111015,6 @@ Niivue.prototype.handlePinchZoom = function(e) {
110112
111015
  };
110113
111016
  Niivue.prototype.keyUpListener = function(e) {
110114
111017
  if (e.code === this.opts.clipPlaneHotKey) {
110115
- if (this.sliceType != this.sliceTypeRender) {
110116
- return;
110117
- }
110118
111018
  let now = new Date().getTime();
110119
111019
  let elapsed = now - this.lastCalled;
110120
111020
  if (elapsed > this.opts.keyDebounceTime) {
@@ -110312,8 +111212,26 @@ Niivue.prototype.dropListener = async function(e) {
110312
111212
  }
110313
111213
  this.drawScene();
110314
111214
  };
111215
+ Niivue.prototype.setCornerOrientationText = function(isCornerOrientationText) {
111216
+ this.opts.isCornerOrientationText = isCornerOrientationText;
111217
+ this.updateGLVolume();
111218
+ };
110315
111219
  Niivue.prototype.setRadiologicalConvention = function(isRadiologicalConvention) {
110316
111220
  this.opts.isRadiologicalConvention = isRadiologicalConvention;
111221
+ this.updateGLVolume();
111222
+ };
111223
+ Niivue.prototype.setMeshThicknessOn2D = function(meshThicknessOn2D) {
111224
+ this.opts.meshThicknessOn2D = meshThicknessOn2D;
111225
+ this.updateGLVolume();
111226
+ };
111227
+ Niivue.prototype.setSliceMosaicString = function(str) {
111228
+ this.sliceMosaicString = str;
111229
+ this.updateGLVolume();
111230
+ };
111231
+ Niivue.prototype.setSliceMM = function(isSliceMM) {
111232
+ this.opts.isSliceMM = isSliceMM;
111233
+ console.log("SliceMM feature is experimental");
111234
+ this.updateGLVolume();
110317
111235
  };
110318
111236
  Niivue.prototype.getRadiologicalConvention = function() {
110319
111237
  return this.opts.isRadiologicalConvention;
@@ -110322,13 +111240,13 @@ Niivue.prototype.addVolume = function(volume) {
110322
111240
  this.volumes.push(volume);
110323
111241
  let idx = this.volumes.length === 1 ? 0 : this.volumes.length - 1;
110324
111242
  this.setVolume(volume, idx);
110325
- this.imageLoaded$.next(volume);
111243
+ this.opts.onImageLoaded(volume);
110326
111244
  };
110327
111245
  Niivue.prototype.addMesh = function(mesh) {
110328
111246
  this.meshes.push(mesh);
110329
111247
  let idx = this.meshes.length === 1 ? 0 : this.meshes.length - 1;
110330
111248
  this.setMesh(mesh, idx);
110331
- this.imageLoaded$.next(mesh);
111249
+ this.opts.onImageLoaded(mesh);
110332
111250
  };
110333
111251
  Niivue.prototype.getVolumeIndexByID = function(id) {
110334
111252
  let n = this.volumes.length;
@@ -110340,9 +111258,112 @@ Niivue.prototype.getVolumeIndexByID = function(id) {
110340
111258
  }
110341
111259
  return -1;
110342
111260
  };
111261
+ function encodeRLE(data) {
111262
+ let dl = data.length;
111263
+ let dp = 0;
111264
+ let r = new Uint8Array(dl + Math.ceil(0.01 * dl));
111265
+ let rI = new Int8Array(r.buffer);
111266
+ let rp = 0;
111267
+ while (dp < dl) {
111268
+ let v = data[dp];
111269
+ dp++;
111270
+ let rl = 1;
111271
+ while (rl < 129 && dp < dl && data[dp] === v) {
111272
+ dp++;
111273
+ rl++;
111274
+ }
111275
+ if (rl > 1) {
111276
+ rI[rp] = -rl + 1;
111277
+ rp++;
111278
+ r[rp] = v;
111279
+ rp++;
111280
+ continue;
111281
+ }
111282
+ while (dp < dl) {
111283
+ if (rl > 127)
111284
+ break;
111285
+ if (dp + 2 < dl) {
111286
+ if (v !== data[dp] && data[dp + 2] === data[dp] && data[dp + 1] === data[dp])
111287
+ break;
111288
+ }
111289
+ v = data[dp];
111290
+ dp++;
111291
+ rl++;
111292
+ }
111293
+ r[rp] = rl - 1;
111294
+ rp++;
111295
+ for (let i2 = 0; i2 < rl; i2++) {
111296
+ r[rp] = data[dp - rl + i2];
111297
+ rp++;
111298
+ }
111299
+ }
111300
+ log.info("PackBits " + dl + " -> " + rp + " bytes (x" + dl / rp + ")");
111301
+ return r.slice(0, rp);
111302
+ }
111303
+ function decodeRLE(rle2, decodedlen) {
111304
+ let r = new Uint8Array(rle2.buffer);
111305
+ let rI = new Int8Array(r.buffer);
111306
+ let rp = 0;
111307
+ let d = new Uint8Array(decodedlen);
111308
+ let dp = 0;
111309
+ while (rp < r.length) {
111310
+ let hdr = rI[rp];
111311
+ rp++;
111312
+ if (hdr < 0) {
111313
+ let v = rI[rp];
111314
+ rp++;
111315
+ for (let i2 = 0; i2 < 1 - hdr; i2++) {
111316
+ d[dp] = v;
111317
+ dp++;
111318
+ }
111319
+ } else {
111320
+ for (let i2 = 0; i2 < hdr + 1; i2++) {
111321
+ d[dp] = rI[rp];
111322
+ rp++;
111323
+ dp++;
111324
+ }
111325
+ }
111326
+ }
111327
+ return d;
111328
+ }
111329
+ Niivue.prototype.drawAddUndoBitmap = async function(fnm) {
111330
+ if (!this.drawBitmap || this.drawBitmap.length < 1) {
111331
+ console.log("drawAddUndoBitmap error: No drawing open");
111332
+ return false;
111333
+ }
111334
+ this.currentDrawUndoBitmap++;
111335
+ if (this.currentDrawUndoBitmap >= this.maxDrawUndoBitmaps)
111336
+ this.currentDrawUndoBitmap = 0;
111337
+ this.drawUndoBitmaps[this.currentDrawUndoBitmap] = encodeRLE(this.drawBitmap);
111338
+ };
111339
+ Niivue.prototype.drawClearAllUndoBitmaps = async function() {
111340
+ this.currentDrawUndoBitmap = this.maxDrawUndoBitmaps;
111341
+ if (this.drawUndoBitmaps.length < 1)
111342
+ return;
111343
+ for (let i2 = this.drawUndoBitmaps.length - 1; i2 >= 0; i2--)
111344
+ array[i2] = [];
111345
+ };
111346
+ Niivue.prototype.drawUndo = function() {
111347
+ if (this.drawUndoBitmaps.length < 1) {
111348
+ console.log("undo bitmaps not loaded");
111349
+ return;
111350
+ }
111351
+ this.currentDrawUndoBitmap--;
111352
+ if (this.currentDrawUndoBitmap < 0)
111353
+ this.currentDrawUndoBitmap = this.drawUndoBitmaps.length - 1;
111354
+ if (this.currentDrawUndoBitmap >= this.drawUndoBitmaps.length)
111355
+ this.currentDrawUndoBitmap = 0;
111356
+ if (this.drawUndoBitmaps[this.currentDrawUndoBitmap].length < 2) {
111357
+ console.log("drawUndo is misbehaving");
111358
+ return;
111359
+ }
111360
+ this.drawBitmap = decodeRLE(this.drawUndoBitmaps[this.currentDrawUndoBitmap], this.drawBitmap.length);
111361
+ this.refreshDrawing(true);
111362
+ };
110343
111363
  Niivue.prototype.loadDrawing = async function(fnm) {
110344
111364
  if (this.drawBitmap)
110345
111365
  console.log("Overwriting open drawing!");
111366
+ this.drawClearAllUndoBitmaps();
110346
111367
  let volume = await NVImage.loadFromUrl({ url: fnm });
110347
111368
  let dims = volume.hdr.dims;
110348
111369
  if (dims[1] !== this.back.hdr.dims[1] || dims[2] !== this.back.hdr.dims[2] || dims[3] !== this.back.hdr.dims[3]) {
@@ -110353,7 +111374,6 @@ Niivue.prototype.loadDrawing = async function(fnm) {
110353
111374
  console.log("Drawings should be UINT8");
110354
111375
  let perm = volume.permRAS;
110355
111376
  let vx = dims[1] * dims[2] * dims[3];
110356
- this.drawUndoBitmap = null;
110357
111377
  this.drawBitmap = new Uint8Array(vx);
110358
111378
  this.drawTexture = this.r8Tex(this.drawTexture, this.gl.TEXTURE7, this.back.dims, true);
110359
111379
  let layout = [0, 0, 0];
@@ -110405,6 +111425,7 @@ Niivue.prototype.loadDrawing = async function(fnm) {
110405
111425
  }
110406
111426
  }
110407
111427
  }
111428
+ this.drawAddUndoBitmap();
110408
111429
  this.refreshDrawing(false);
110409
111430
  this.drawScene();
110410
111431
  };
@@ -110517,9 +111538,12 @@ Niivue.prototype.setMeshLayerProperty = function(mesh, layer, key2, val) {
110517
111538
  this.meshes[idx].setLayerProperty(layer, key2, val, this.gl);
110518
111539
  this.updateGLVolume();
110519
111540
  };
111541
+ Niivue.prototype.setPan2Dxyzmm = function(xyzmmZoom) {
111542
+ this.scene.pan2Dxyzmm = xyzmmZoom;
111543
+ this.drawScene();
111544
+ };
110520
111545
  Niivue.prototype.setScale2D = function(zoomFactor) {
110521
111546
  this.scene.scale2D = zoomFactor;
110522
- console.log("bingo");
110523
111547
  this.drawScene();
110524
111548
  };
110525
111549
  Niivue.prototype.setRenderAzimuthElevation = function(a, e) {
@@ -110614,12 +111638,12 @@ Niivue.prototype.moveVolumeToTop = function(volume) {
110614
111638
  this.setVolume(volume, this.volumes.length - 1);
110615
111639
  };
110616
111640
  Niivue.prototype.mouseDown = function mouseDown(x2, y) {
110617
- if (this.sliceType != this.sliceTypeRender)
111641
+ if (this.inRenderTile(x2, y) < 0)
110618
111642
  return;
110619
111643
  this.mousePos = [x2, y];
110620
111644
  };
110621
111645
  Niivue.prototype.mouseMove = function mouseMove(x2, y) {
110622
- if (this.sliceType != this.sliceTypeRender)
111646
+ if (this.inRenderTile(x2, y) < 0)
110623
111647
  return;
110624
111648
  this.scene.renderAzimuth += x2 - this.mousePos[0];
110625
111649
  this.scene.renderElevation += y - this.mousePos[1];
@@ -110646,8 +111670,6 @@ Niivue.prototype.setClipPlane = function(depthAzimuthElevation) {
110646
111670
  let v = this.sph2cartDeg(depthAzimuthElevation[1] + 180, depthAzimuthElevation[2]);
110647
111671
  this.scene.clipPlane = [v[0], v[1], v[2], depthAzimuthElevation[0]];
110648
111672
  this.scene.clipPlaneDepthAziElev = depthAzimuthElevation;
110649
- if (this.sliceType != this.sliceTypeRender)
110650
- return;
110651
111673
  this.drawScene();
110652
111674
  };
110653
111675
  Niivue.prototype.setCrosshairColor = function(color) {
@@ -110698,6 +111720,8 @@ Niivue.prototype.setScale = function(scale2) {
110698
111720
  };
110699
111721
  Niivue.prototype.setClipPlaneColor = function(color) {
110700
111722
  this.opts.clipPlaneColor = color;
111723
+ this.renderShader.use(this.gl);
111724
+ this.gl.uniform4fv(this.renderShader.uniforms["clipPlaneColor"], this.opts.clipPlaneColor);
110701
111725
  this.drawScene();
110702
111726
  };
110703
111727
  Niivue.prototype.overlayRGBA = function(volume) {
@@ -110762,6 +111786,7 @@ Niivue.prototype.loadVolumes = async function(volumeList) {
110762
111786
  url: volumeList[i2].url,
110763
111787
  name: volumeList[i2].name,
110764
111788
  colorMap: volumeList[i2].colorMap,
111789
+ colorMapNegative: volumeList[i2].colorMapNegative,
110765
111790
  opacity: volumeList[i2].opacity,
110766
111791
  urlImgData: volumeList[i2].urlImgData,
110767
111792
  cal_min: volumeList[i2].cal_min,
@@ -110841,7 +111866,8 @@ Niivue.prototype.createEmptyDrawing = function() {
110841
111866
  return;
110842
111867
  let vx = this.back.dims[1] * this.back.dims[2] * this.back.dims[3];
110843
111868
  this.drawBitmap = new Uint8Array(vx);
110844
- this.drawUndoBitmap = null;
111869
+ this.drawClearAllUndoBitmaps();
111870
+ this.drawAddUndoBitmap();
110845
111871
  this.drawTexture = this.r8Tex(this.drawTexture, this.gl.TEXTURE7, this.back.dims, true);
110846
111872
  this.refreshDrawing(false);
110847
111873
  };
@@ -110918,16 +111944,6 @@ function img2ras16(volume) {
110918
111944
  }
110919
111945
  return img16;
110920
111946
  }
110921
- Niivue.prototype.drawUndo = function() {
110922
- let hdr = this.back.hdr;
110923
- let nv = hdr.dims[1] * hdr.dims[2] * hdr.dims[3];
110924
- if (this.drawUndoBitmap.length !== nv || this.drawBitmap.length !== nv) {
110925
- console.log("bitmap dims are wrong");
110926
- return;
110927
- }
110928
- this.drawBitmap = this.drawUndoBitmap.slice();
110929
- this.refreshDrawing(true);
110930
- };
110931
111947
  Niivue.prototype.drawGrowCut = function() {
110932
111948
  let hdr = this.back.hdr;
110933
111949
  let nv = hdr.dims[1] * hdr.dims[2] * hdr.dims[3];
@@ -110935,7 +111951,6 @@ Niivue.prototype.drawGrowCut = function() {
110935
111951
  console.log("bitmap dims are wrong");
110936
111952
  return;
110937
111953
  }
110938
- this.drawUndoBitmap = this.drawBitmap.slice();
110939
111954
  let gl = this.gl;
110940
111955
  let fb = gl.createFramebuffer();
110941
111956
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
@@ -110948,16 +111963,19 @@ Niivue.prototype.drawGrowCut = function() {
110948
111963
  img16[i2] = this.drawBitmap[i2];
110949
111964
  let label0 = this.r16Tex(null, gl.TEXTURE6, this.back.dims, img16);
110950
111965
  let label1 = this.r16Tex(null, gl.TEXTURE7, this.back.dims, img16);
111966
+ let kMAX_STRENGTH = 1e4;
110951
111967
  for (let i2 = 1; i2 < nv; i2++)
110952
111968
  if (img16[i2] > 0)
110953
- img16[i2] = 1e4;
111969
+ img16[i2] = kMAX_STRENGTH;
110954
111970
  let strength0 = this.r16Tex(null, gl.TEXTURE4, this.back.dims, img16);
110955
111971
  let strength1 = this.r16Tex(null, gl.TEXTURE5, this.back.dims, img16);
110956
111972
  this.gl.bindVertexArray(this.genericVAO);
110957
111973
  let shader = this.growCutShader;
110958
111974
  shader.use(gl);
111975
+ let iterations = 128;
111976
+ gl.uniform1i(shader.uniforms["finalPass"], 0);
110959
111977
  gl.uniform1i(shader.uniforms["inputTexture0"], 3);
110960
- for (let j = 0; j < 45; j++) {
111978
+ for (let j = 0; j < iterations; j++) {
110961
111979
  gl.uniform1i(shader.uniforms["inputTexture1"], 6);
110962
111980
  gl.uniform1i(shader.uniforms["inputTexture2"], 4);
110963
111981
  for (let i2 = 0; i2 < this.back.dims[3]; i2++) {
@@ -110971,6 +111989,8 @@ Niivue.prototype.drawGrowCut = function() {
110971
111989
  console.error("Incomplete framebuffer");
110972
111990
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
110973
111991
  }
111992
+ if (j === iterations - 1)
111993
+ gl.uniform1i(shader.uniforms["finalPass"], 1);
110974
111994
  gl.uniform1i(shader.uniforms["inputTexture1"], 7);
110975
111995
  gl.uniform1i(shader.uniforms["inputTexture2"], 5);
110976
111996
  for (let i2 = 0; i2 < this.back.dims[3]; i2++) {
@@ -110986,8 +112006,8 @@ Niivue.prototype.drawGrowCut = function() {
110986
112006
  }
110987
112007
  }
110988
112008
  gl.drawBuffers([gl.COLOR_ATTACHMENT0]);
110989
- let readAttach = gl.COLOR_ATTACHMENT0;
110990
- let readTex = label1;
112009
+ let readAttach = gl.COLOR_ATTACHMENT1;
112010
+ let readTex = label0;
110991
112011
  gl.readBuffer(readAttach);
110992
112012
  const format = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT);
110993
112013
  const type2 = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE);
@@ -111023,6 +112043,7 @@ Niivue.prototype.drawGrowCut = function() {
111023
112043
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
111024
112044
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
111025
112045
  gl.deleteFramebuffer(fb);
112046
+ this.drawAddUndoBitmap();
111026
112047
  this.refreshDrawing(true);
111027
112048
  };
111028
112049
  Niivue.prototype.drawPt = function(x2, y, z, penValue) {
@@ -111034,7 +112055,7 @@ Niivue.prototype.drawPt = function(x2, y, z, penValue) {
111034
112055
  z = Math.min(Math.max(z, 0), dz - 1);
111035
112056
  this.drawBitmap[x2 + y * dx + z * dx * dy] = penValue;
111036
112057
  };
111037
- Niivue.prototype.drawLine = function(ptA, ptB, penValue) {
112058
+ Niivue.prototype.drawPenLine = function(ptA, ptB, penValue) {
111038
112059
  let dx = Math.abs(ptA[0] - ptB[0]);
111039
112060
  let dy = Math.abs(ptA[1] - ptB[1]);
111040
112061
  let dz = Math.abs(ptA[2] - ptB[2]);
@@ -111106,6 +112127,74 @@ Niivue.prototype.drawLine = function(ptA, ptB, penValue) {
111106
112127
  }
111107
112128
  }
111108
112129
  };
112130
+ Niivue.prototype.drawFloodFill = function(seedXYZ, newColor = 0) {
112131
+ newColor = Math.abs(newColor);
112132
+ let dims = [this.back.dims[1], this.back.dims[2], this.back.dims[3]];
112133
+ if (seedXYZ[0] < 0 || seedXYZ[1] < 0 || seedXYZ[2] < 0)
112134
+ return;
112135
+ if (seedXYZ[0] >= dims[0] || seedXYZ[1] >= dims[1] || seedXYZ[2] >= dims[2])
112136
+ return;
112137
+ let nx = dims[0];
112138
+ let nxy = nx * dims[1];
112139
+ let nxyz = nxy * dims[2];
112140
+ let img = this.drawBitmap.slice();
112141
+ if (img.length !== nxy * dims[2])
112142
+ return;
112143
+ function xyz2vx(pt) {
112144
+ return pt[0] + pt[1] * nx + pt[2] * nxy;
112145
+ }
112146
+ function vx2xyz(vx) {
112147
+ let Z = Math.floor(vx / nxy);
112148
+ let Y = Math.floor((vx - Z * nxy) / nx);
112149
+ let X = Math.floor(vx % nx);
112150
+ return [X, Y, Z];
112151
+ }
112152
+ let seedVx = xyz2vx(seedXYZ);
112153
+ let seedColor = img[seedVx];
112154
+ if (seedColor === newColor) {
112155
+ console.log("drawFloodFill selected voxel is already desired color");
112156
+ return;
112157
+ }
112158
+ for (let i2 = 1; i2 < nxyz; i2++) {
112159
+ img[i2] = 0;
112160
+ if (this.drawBitmap[i2] === seedColor)
112161
+ img[i2] = 1;
112162
+ }
112163
+ let Q = [];
112164
+ Q.push(seedVx);
112165
+ img[seedVx] = 2;
112166
+ while (Q.length > 0) {
112167
+ let testNeighbor = function(offset) {
112168
+ let xyzN = xyz.slice();
112169
+ xyzN[0] += offset[0];
112170
+ xyzN[1] += offset[1];
112171
+ xyzN[2] += offset[2];
112172
+ if (xyzN[0] < 0 || xyzN[1] < 0 || xyzN[2] < 0)
112173
+ return;
112174
+ if (xyzN[0] >= dims[0] || xyzN[1] >= dims[1] || xyzN[2] >= dims[2])
112175
+ return;
112176
+ let vxT = xyz2vx(xyzN);
112177
+ if (img[vxT] !== 1)
112178
+ return;
112179
+ img[vxT] = 2;
112180
+ Q.push(vxT);
112181
+ };
112182
+ let vx = Q[0];
112183
+ Q.shift();
112184
+ let xyz = vx2xyz(vx);
112185
+ testNeighbor([0, 0, -1]);
112186
+ testNeighbor([0, 0, 1]);
112187
+ testNeighbor([0, -1, 0]);
112188
+ testNeighbor([0, 1, 0]);
112189
+ testNeighbor([-1, 0, 0]);
112190
+ testNeighbor([1, 0, 0]);
112191
+ }
112192
+ for (let i2 = 1; i2 < nxyz; i2++)
112193
+ if (img[i2] === 2)
112194
+ this.drawBitmap[i2] = newColor;
112195
+ this.drawAddUndoBitmap();
112196
+ this.refreshDrawing(false);
112197
+ };
111109
112198
  Niivue.prototype.drawPenFilled = function() {
111110
112199
  let nPts = this.drawPenFillPts.length;
111111
112200
  if (nPts < 2) {
@@ -111221,15 +112310,17 @@ Niivue.prototype.drawPenFilled = function() {
111221
112310
  }
111222
112311
  }
111223
112312
  }
111224
- if (!this.drawFillOverwrites && this.drawUndoBitmap.length === this.drawBitmap.length) {
111225
- let nv = this.drawUndoBitmap.length;
112313
+ if (!this.drawFillOverwrites && this.drawUndoBitmaps[this.currentDrawUndoBitmap].length > 0) {
112314
+ let nv = this.drawBitmap.length;
112315
+ let bmp = decodeRLE(this.drawUndoBitmaps[this.currentDrawUndoBitmap], nv);
111226
112316
  for (let i2 = 0; i2 < nv; i2++) {
111227
- if (this.drawUndoBitmap[i2] === 0)
112317
+ if (bmp[i2] === 0)
111228
112318
  continue;
111229
- this.drawBitmap[i2] = this.drawUndoBitmap[i2];
112319
+ this.drawBitmap[i2] = bmp[i2];
111230
112320
  }
111231
112321
  }
111232
112322
  this.drawPenFillPts = [];
112323
+ this.drawAddUndoBitmap();
111233
112324
  this.refreshDrawing(false);
111234
112325
  };
111235
112326
  Niivue.prototype.closeDrawing = function() {
@@ -111473,8 +112564,15 @@ Niivue.prototype.init = async function() {
111473
112564
  this.gl.uniform1i(this.sliceShader.uniforms["overlay"], 2);
111474
112565
  this.gl.uniform1i(this.sliceShader.uniforms["drawing"], 7);
111475
112566
  this.gl.uniform1f(this.sliceShader.uniforms["drawOpacity"], this.drawOpacity);
111476
- this.lineShader = new Shader(this.gl, vertLineShader, fragLineShader);
111477
- this.graphShader = new Shader(this.gl, vertGraphShader, fragLineShader);
112567
+ this.sliceMMShader = new Shader(this.gl, vertSliceMMShader, fragSliceMMShader);
112568
+ this.sliceMMShader.use(this.gl);
112569
+ this.gl.uniform1i(this.sliceMMShader.uniforms["volume"], 0);
112570
+ this.gl.uniform1i(this.sliceMMShader.uniforms["overlay"], 2);
112571
+ this.gl.uniform1i(this.sliceMMShader.uniforms["drawing"], 7);
112572
+ this.gl.uniform1f(this.sliceMMShader.uniforms["drawOpacity"], this.drawOpacity);
112573
+ this.rectShader = new Shader(this.gl, vertRectShader, fragRectShader);
112574
+ this.lineShader = new Shader(this.gl, vertLineShader, fragRectShader);
112575
+ this.graphShader = new Shader(this.gl, vertGraphShader, fragRectShader);
111478
112576
  this.renderShader = new Shader(this.gl, vertRenderShader, fragRenderShader);
111479
112577
  this.renderShader.use(this.gl);
111480
112578
  this.gl.uniform1i(this.renderShader.uniforms["volume"], 0);
@@ -111602,7 +112700,6 @@ Niivue.prototype.refreshLayers = function(overlayItem, layer, numLayers) {
111602
112700
  if (layer === 0) {
111603
112701
  this.volumeObject3D = overlayItem.toNiivueObject3D(this.VOLUME_ID, this.gl);
111604
112702
  invert(mtx, mtx);
111605
- log.debug(`mtx layer ${layer}`, mtx);
111606
112703
  this.back.matRAS = overlayItem.matRAS;
111607
112704
  this.back.dims = overlayItem.dimsRAS;
111608
112705
  this.back.pixDims = overlayItem.pixDimsRAS;
@@ -111719,10 +112816,54 @@ Niivue.prototype.refreshLayers = function(overlayItem, layer, numLayers) {
111719
112816
  this.gl.uniform1i(orientShader.uniforms["blend3D"], 5);
111720
112817
  this.gl.uniform1i(orientShader.uniforms["colormap"], 1);
111721
112818
  this.gl.uniform1f(orientShader.uniforms["layer"], layer);
111722
- this.gl.uniform1f(orientShader.uniforms["numLayers"], numLayers);
111723
112819
  this.gl.uniform1f(orientShader.uniforms["scl_inter"], hdr.scl_inter);
111724
112820
  this.gl.uniform1f(orientShader.uniforms["scl_slope"], hdr.scl_slope);
111725
112821
  this.gl.uniform1f(orientShader.uniforms["opacity"], opacity);
112822
+ this.gl.uniform1i(orientShader.uniforms["modulationVol"], 7);
112823
+ let modulateTexture = null;
112824
+ if (overlayItem.modulationImage && overlayItem.modulationImage >= 0 && overlayItem.modulationImage < this.volumes.length) {
112825
+ console.log(this.volumes);
112826
+ let mhdr = this.volumes[overlayItem.modulationImage].hdr;
112827
+ if (mhdr.dims[1] === hdr.dims[1] && mhdr.dims[2] === hdr.dims[2] && mhdr.dims[3] === hdr.dims[3]) {
112828
+ this.gl.uniform1i(orientShader.uniforms["modulation"], 1);
112829
+ modulateTexture = this.r8Tex(modulateTexture, this.gl.TEXTURE7, hdr.dims, true);
112830
+ this.gl.activeTexture(this.gl.TEXTURE7);
112831
+ this.gl.bindTexture(this.gl.TEXTURE_3D, modulateTexture);
112832
+ let vx = hdr.dims[1] * hdr.dims[2] * hdr.dims[3];
112833
+ let modulateVolume = new Uint8Array(vx);
112834
+ let mn = mhdr.cal_min;
112835
+ let scale2 = 255 / (mhdr.cal_max - mhdr.cal_min);
112836
+ let imgRaw = this.volumes[overlayItem.modulationImage].img.buffer;
112837
+ let img2 = new Uint8Array(imgRaw);
112838
+ switch (mhdr.datatypeCode) {
112839
+ case overlayItem.DT_SIGNED_SHORT:
112840
+ img2 = new Int16Array(imgRaw);
112841
+ break;
112842
+ case overlayItem.DT_FLOAT:
112843
+ img2 = new Float32Array(imgRaw);
112844
+ break;
112845
+ case overlayItem.DT_DOUBLE:
112846
+ img2 = new Float64Array(imgRaw);
112847
+ break;
112848
+ case overlayItem.DT_RGB:
112849
+ img2 = new Uint8Array(imgRaw);
112850
+ break;
112851
+ case overlayItem.DT_UINT16:
112852
+ img2 = new Uint16Array(imgRaw);
112853
+ break;
112854
+ }
112855
+ console.log(this.volumes[overlayItem.modulationImage]);
112856
+ for (let i2 = 0; i2 < vx; i2++) {
112857
+ let v = img2[i2] * mhdr.scl_slope + mhdr.scl_inter;
112858
+ v = (v - mn) * scale2;
112859
+ v = Math.min(Math.max(v, 0), 255);
112860
+ modulateVolume[i2] = v;
112861
+ }
112862
+ this.gl.texSubImage3D(this.gl.TEXTURE_3D, 0, 0, 0, 0, hdr.dims[1], hdr.dims[2], hdr.dims[3], this.gl.RED, this.gl.UNSIGNED_BYTE, modulateVolume);
112863
+ } else
112864
+ console.log("Modulation image dimensions do not match target");
112865
+ } else
112866
+ this.gl.uniform1i(orientShader.uniforms["modulation"], 0);
111726
112867
  this.gl.uniformMatrix4fv(orientShader.uniforms["mtx"], false, mtx);
111727
112868
  if (hdr.intent_code === 1002) {
111728
112869
  let x2 = 1 / this.back.dims[1];
@@ -111743,6 +112884,7 @@ Niivue.prototype.refreshLayers = function(overlayItem, layer, numLayers) {
111743
112884
  }
111744
112885
  this.gl.bindVertexArray(this.unusedVAO);
111745
112886
  this.gl.deleteTexture(tempTex3D);
112887
+ this.gl.deleteTexture(modulateTexture);
111746
112888
  this.gl.deleteTexture(blendTexture);
111747
112889
  this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
111748
112890
  this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
@@ -111760,10 +112902,16 @@ Niivue.prototype.refreshLayers = function(overlayItem, layer, numLayers) {
111760
112902
  this.pickingShader.use(this.gl);
111761
112903
  this.gl.uniform1f(this.pickingShader.uniforms["overlays"], this.overlays.length);
111762
112904
  this.gl.uniform3fv(this.pickingShader.uniforms["texVox"], vox);
112905
+ this.sliceMMShader.use(this.gl);
112906
+ this.gl.uniform1f(this.sliceMMShader.uniforms["overlays"], this.overlays.length);
112907
+ this.gl.uniform1f(this.sliceMMShader.uniforms["drawOpacity"], this.drawOpacity);
112908
+ this.gl.uniform1i(this.sliceMMShader.uniforms["drawing"], 7);
111763
112909
  this.sliceShader.use(this.gl);
111764
112910
  this.gl.uniform1f(this.sliceShader.uniforms["overlays"], this.overlays.length);
111765
112911
  this.gl.uniform1f(this.sliceShader.uniforms["drawOpacity"], this.drawOpacity);
111766
112912
  this.gl.uniform1i(this.sliceShader.uniforms["drawing"], 7);
112913
+ this.gl.activeTexture(this.gl.TEXTURE7);
112914
+ this.gl.bindTexture(this.gl.TEXTURE_3D, this.drawTexture);
111767
112915
  this.updateInterpolation(layer);
111768
112916
  };
111769
112917
  Niivue.prototype.colorMaps = function(sort = true) {
@@ -111774,6 +112922,18 @@ Niivue.prototype.setColorMap = function(id, colorMap) {
111774
112922
  this.volumes[idx].colorMap = colorMap;
111775
112923
  this.updateGLVolume();
111776
112924
  };
112925
+ Niivue.prototype.setColorMapNegative = function(id, colorMapNegative) {
112926
+ let idx = this.getVolumeIndexByID(id);
112927
+ this.volumes[idx].colorMapNegative = colorMapNegative;
112928
+ this.updateGLVolume();
112929
+ };
112930
+ Niivue.prototype.setModulationImage = function(idTarget, idModulation) {
112931
+ let idxTarget = this.getVolumeIndexByID(idTarget);
112932
+ let idxModulation = null;
112933
+ idxModulation = this.getVolumeIndexByID(idModulation);
112934
+ this.volumes[idxTarget].modulationImage = idxModulation;
112935
+ this.updateGLVolume();
112936
+ };
111777
112937
  Niivue.prototype.setGamma = function(gamma = 1) {
111778
112938
  cmapper.gamma = gamma;
111779
112939
  this.updateGLVolume();
@@ -111808,40 +112968,87 @@ Niivue.prototype.refreshColormaps = function() {
111808
112968
  this.colormapTexture = this.gl.createTexture();
111809
112969
  this.gl.activeTexture(this.gl.TEXTURE1);
111810
112970
  this.gl.bindTexture(this.gl.TEXTURE_2D, this.colormapTexture);
111811
- this.gl.texStorage2D(this.gl.TEXTURE_2D, 1, this.gl.RGBA8, 256, nLayer);
112971
+ this.gl.texStorage2D(this.gl.TEXTURE_2D, 1, this.gl.RGBA8, 256, nLayer * 2);
111812
112972
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
111813
112973
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
111814
112974
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_R, this.gl.CLAMP_TO_EDGE);
111815
112975
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
111816
112976
  this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT, 1);
111817
112977
  let luts2 = this.colormap(this.volumes[0].colorMap);
111818
- if (nLayer > 1) {
111819
- for (let i2 = 1; i2 < nLayer; i2++) {
111820
- let lut = this.colormap(this.volumes[i2].colorMap);
111821
- let c2 = new Uint8ClampedArray(luts2.length + lut.length);
111822
- c2.set(luts2);
111823
- c2.set(lut, luts2.length);
111824
- luts2 = c2;
111825
- }
111826
- }
111827
- this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, 0, 0, 256, nLayer, this.gl.RGBA, this.gl.UNSIGNED_BYTE, luts2);
112978
+ function addColormap(lut) {
112979
+ let c2 = new Uint8ClampedArray(luts2.length + lut.length);
112980
+ c2.set(luts2);
112981
+ c2.set(lut, luts2.length);
112982
+ luts2 = c2;
112983
+ }
112984
+ for (let i2 = 1; i2 < nLayer; i2++)
112985
+ addColormap(this.colormap(this.volumes[i2].colorMap));
112986
+ let bogusLut = new Uint8ClampedArray(1024);
112987
+ bogusLut[0] = 255;
112988
+ bogusLut[1] = 255;
112989
+ bogusLut[2] = 255;
112990
+ bogusLut[3] = 0;
112991
+ bogusLut[4] = 0;
112992
+ bogusLut[5] = 0;
112993
+ bogusLut[6] = 0;
112994
+ bogusLut[7] = 255;
112995
+ for (let i2 = 0; i2 < nLayer; i2++) {
112996
+ let lut = bogusLut.slice();
112997
+ if (this.volumes[i2].colorMapNegative.length > 0)
112998
+ lut = this.colormap(this.volumes[i2].colorMapNegative);
112999
+ addColormap(lut);
113000
+ }
113001
+ this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, 0, 0, 256, nLayer * 2, this.gl.RGBA, this.gl.UNSIGNED_BYTE, luts2);
111828
113002
  return this;
111829
113003
  };
111830
113004
  Niivue.prototype.sliceScale = function() {
111831
- var dims = [
111832
- 1,
111833
- this.back.dims[1] * this.back.pixDims[1],
111834
- this.back.dims[2] * this.back.pixDims[2],
111835
- this.back.dims[3] * this.back.pixDims[3]
111836
- ];
111837
- var longestAxis = Math.max(dims[1], Math.max(dims[2], dims[3]));
113005
+ let dimsMM = this.screenFieldOfViewMM(this.sliceTypeAxial);
113006
+ var longestAxis = Math.max(dimsMM[0], Math.max(dimsMM[1], dimsMM[2]));
111838
113007
  var volScale = [
111839
- dims[1] / longestAxis,
111840
- dims[2] / longestAxis,
111841
- dims[3] / longestAxis
113008
+ dimsMM[0] / longestAxis,
113009
+ dimsMM[1] / longestAxis,
113010
+ dimsMM[2] / longestAxis
111842
113011
  ];
111843
113012
  var vox = [this.back.dims[1], this.back.dims[2], this.back.dims[3]];
111844
- return { volScale, vox };
113013
+ return { volScale, vox, longestAxis, dimsMM };
113014
+ };
113015
+ function swizzleVec3(vec, order = [0, 1, 2]) {
113016
+ let vout = create$1();
113017
+ vout[0] = vec[order[0]];
113018
+ vout[1] = vec[order[1]];
113019
+ vout[2] = vec[order[2]];
113020
+ return vout;
113021
+ }
113022
+ Niivue.prototype.inRenderTile = function(x2, y) {
113023
+ for (let i2 = 0; i2 < this.screenSlices.length; i2++) {
113024
+ if (this.screenSlices[i2].axCorSag !== this.sliceTypeRender)
113025
+ continue;
113026
+ let ltwh = this.screenSlices[i2].leftTopWidthHeight;
113027
+ if (x2 > ltwh[0] && y > ltwh[1] && x2 < ltwh[0] + ltwh[2] && y < ltwh[1] + ltwh[3])
113028
+ return i2;
113029
+ }
113030
+ return -1;
113031
+ };
113032
+ Niivue.prototype.sliceScroll3D = function(posChange = 0) {
113033
+ if (posChange === 0)
113034
+ return;
113035
+ if (this.volumes.length > 0 && this.scene.clipPlaneDepthAziElev[0] < 1.8) {
113036
+ let depthAziElev = this.scene.clipPlaneDepthAziElev.slice();
113037
+ if (posChange > 0)
113038
+ depthAziElev[0] = Math.min(1.5, depthAziElev[0] + 0.025);
113039
+ if (posChange < 0)
113040
+ depthAziElev[0] = Math.max(-1.5, depthAziElev[0] - 0.025);
113041
+ if (depthAziElev[0] !== this.scene.clipPlaneDepthAziElev[0]) {
113042
+ this.scene.clipPlaneDepthAziElev = depthAziElev;
113043
+ return this.setClipPlane(this.scene.clipPlaneDepthAziElev);
113044
+ }
113045
+ return;
113046
+ }
113047
+ if (posChange > 0)
113048
+ this.volScaleMultiplier = Math.min(2, this.volScaleMultiplier * 1.1);
113049
+ if (posChange < 0)
113050
+ this.volScaleMultiplier = Math.max(0.5, this.volScaleMultiplier * 0.9);
113051
+ this.drawScene();
111845
113052
  };
111846
113053
  Niivue.prototype.mouseClick = function(x2, y, posChange = 0, isDelta = true) {
111847
113054
  var posNow;
@@ -111864,48 +113071,23 @@ Niivue.prototype.mouseClick = function(x2, y, posChange = 0, isDelta = true) {
111864
113071
  return;
111865
113072
  }
111866
113073
  }
111867
- if (this.sliceType === this.sliceTypeRender) {
111868
- if (posChange === 0)
111869
- return;
111870
- if (this.volumes.length > 0 && this.scene.clipPlaneDepthAziElev[0] < 1.8) {
111871
- let depthAziElev = this.scene.clipPlaneDepthAziElev.slice();
111872
- if (posChange > 0)
111873
- depthAziElev[0] = Math.min(1.5, depthAziElev[0] + 0.025);
111874
- if (posChange < 0)
111875
- depthAziElev[0] = Math.max(-1.5, depthAziElev[0] - 0.025);
111876
- if (depthAziElev[0] !== this.scene.clipPlaneDepthAziElev[0]) {
111877
- this.scene.clipPlaneDepthAziElev = depthAziElev;
111878
- return this.setClipPlane(this.scene.clipPlaneDepthAziElev);
111879
- }
111880
- return;
111881
- }
111882
- if (posChange > 0)
111883
- this.volScaleMultiplier = Math.min(2, this.volScaleMultiplier * 1.1);
111884
- if (posChange < 0)
111885
- this.volScaleMultiplier = Math.max(0.5, this.volScaleMultiplier * 0.9);
113074
+ if (this.inRenderTile(x2, y) >= 0) {
113075
+ this.sliceScroll3D(posChange);
111886
113076
  this.drawScene();
111887
113077
  return;
111888
113078
  }
111889
- if (this.numScreenSlices < 1 || this.gl.canvas.height < 1 || this.gl.canvas.width < 1)
113079
+ if (this.screenSlices.length < 1 || this.gl.canvas.height < 1 || this.gl.canvas.width < 1)
111890
113080
  return;
111891
- for (let i2 = 0; i2 < this.numScreenSlices; i2++) {
113081
+ for (let i2 = 0; i2 < this.screenSlices.length; i2++) {
111892
113082
  var axCorSag = this.screenSlices[i2].axCorSag;
111893
113083
  if (this.drawPenAxCorSag >= 0 && this.drawPenAxCorSag !== axCorSag)
111894
113084
  continue;
111895
113085
  if (axCorSag > this.sliceTypeSagittal)
111896
113086
  continue;
111897
- var ltwh = this.screenSlices[i2].leftTopWidthHeight;
111898
- let isMirror = false;
111899
- if (ltwh[2] < 0) {
111900
- isMirror = true;
111901
- ltwh[0] += ltwh[2];
111902
- ltwh[2] = -ltwh[2];
111903
- }
111904
- var fracX = (x2 - ltwh[0]) / ltwh[2];
111905
- if (isMirror)
111906
- fracX = 1 - fracX;
111907
- var fracY = 1 - (y - ltwh[1]) / ltwh[3];
111908
- if (fracX >= 0 && fracX < 1 && fracY >= 0 && fracY < 1) {
113087
+ let texFrac = this.screenXY2TextureFrac(x2, y, i2);
113088
+ if (texFrac[0] < 0)
113089
+ continue;
113090
+ {
111909
113091
  if (!isDelta) {
111910
113092
  this.scene.crosshairPos[2 - axCorSag] = posChange;
111911
113093
  this.drawScene();
@@ -111920,42 +113102,35 @@ Niivue.prototype.mouseClick = function(x2, y, posChange = 0, isDelta = true) {
111920
113102
  posFuture = 0;
111921
113103
  this.scene.crosshairPos[2 - axCorSag] = posFuture;
111922
113104
  this.drawScene();
111923
- this.scene.location$.next({
113105
+ this.opts.onLocationChange({
111924
113106
  mm: this.frac2mm(this.scene.crosshairPos),
111925
113107
  vox: this.frac2vox(this.scene.crosshairPos),
111926
113108
  frac: this.scene.crosshairPos,
113109
+ xy: [x2, y],
111927
113110
  values: this.volumes.map((v) => {
111928
113111
  let mm = this.frac2mm(this.scene.crosshairPos);
111929
113112
  let vox = v.mm2vox(mm);
111930
113113
  let val = v.getValue(...vox);
111931
- return val;
113114
+ return { name: v.name, value: val, id: v.id, mm, vox };
111932
113115
  })
111933
113116
  });
111934
113117
  return;
111935
113118
  }
111936
- if (axCorSag === this.sliceTypeAxial) {
111937
- this.scene.crosshairPos[0] = fracX;
111938
- this.scene.crosshairPos[1] = fracY;
111939
- }
111940
- if (axCorSag === this.sliceTypeCoronal) {
111941
- this.scene.crosshairPos[0] = fracX;
111942
- this.scene.crosshairPos[2] = fracY;
111943
- }
111944
- if (axCorSag === this.sliceTypeSagittal) {
111945
- this.scene.crosshairPos[1] = fracX;
111946
- this.scene.crosshairPos[2] = fracY;
111947
- }
113119
+ this.scene.crosshairPos = texFrac.slice();
111948
113120
  if (this.opts.drawingEnabled) {
111949
113121
  let pt = this.frac2vox(this.scene.crosshairPos);
113122
+ if (this.opts.penValue < 0 || Object.is(this.opts.penValue, -0)) {
113123
+ this.drawFloodFill(pt, Math.abs(this.opts.penValue));
113124
+ return;
113125
+ }
111950
113126
  if (isNaN(this.drawPenLocation[0])) {
111951
113127
  this.drawPenAxCorSag = axCorSag;
111952
113128
  this.drawPenFillPts = [];
111953
113129
  this.drawPt(...pt, this.opts.penValue);
111954
- this.drawUndoBitmap = this.drawBitmap.slice();
111955
113130
  } else {
111956
113131
  if (pt[0] === this.drawPenLocation[0] && pt[1] === this.drawPenLocation[1] && pt[2] === this.drawPenLocation[2])
111957
113132
  return;
111958
- this.drawLine(pt, this.drawPenLocation, this.opts.penValue);
113133
+ this.drawPenLine(pt, this.drawPenLocation, this.opts.penValue);
111959
113134
  }
111960
113135
  this.drawPenLocation = pt;
111961
113136
  if (this.opts.isFilledPen)
@@ -111963,57 +113138,287 @@ Niivue.prototype.mouseClick = function(x2, y, posChange = 0, isDelta = true) {
111963
113138
  this.refreshDrawing(false);
111964
113139
  }
111965
113140
  this.drawScene();
111966
- this.scene.location$.next({
113141
+ this.opts.onLocationChange({
111967
113142
  mm: this.frac2mm(this.scene.crosshairPos),
111968
113143
  vox: this.frac2vox(this.scene.crosshairPos),
111969
113144
  frac: this.scene.crosshairPos,
113145
+ xy: [x2, y],
111970
113146
  values: this.volumes.map((v) => {
111971
113147
  let mm = this.frac2mm(this.scene.crosshairPos);
111972
113148
  let vox = v.mm2vox(mm);
111973
113149
  let val = v.getValue(...vox);
111974
- return val;
113150
+ return { name: v.name, value: val, id: v.id, mm, vox };
111975
113151
  })
111976
113152
  });
111977
113153
  return;
111978
- } else {
111979
- if (x2 === null && y === null) {
111980
- this.scene.crosshairPos[2 - axCorSag] = posChange;
111981
- this.drawScene();
111982
- return;
111983
- }
111984
113154
  }
111985
113155
  }
111986
113156
  };
111987
- Niivue.prototype.drawSelectionBox = function(leftTopWidthHeight) {
113157
+ Niivue.prototype.drawRuler = function() {
113158
+ let fovMM = [];
113159
+ let ltwh = [];
113160
+ for (let i2 = 0; i2 < this.screenSlices.length; i2++) {
113161
+ if (this.screenSlices[i2].axCorSag === this.sliceTypeRender)
113162
+ continue;
113163
+ if (this.screenSlices[i2].fovMM.length > 1) {
113164
+ ltwh = this.screenSlices[i2].leftTopWidthHeight;
113165
+ fovMM = this.screenSlices[i2].fovMM;
113166
+ break;
113167
+ }
113168
+ }
113169
+ if (ltwh.length < 4)
113170
+ return;
113171
+ let frac10cm = 100 / fovMM[0];
113172
+ let pix10cm = frac10cm * ltwh[2];
113173
+ let pixLeft = ltwh[0] + 0.5 * ltwh[2] - 0.5 * pix10cm;
113174
+ let pixTop = ltwh[1] + ltwh[3] - 2 * this.opts.rulerWidth;
113175
+ let startXYendXY = [pixLeft, pixTop, pixLeft + pix10cm, pixTop];
113176
+ this.drawRuler10cm(startXYendXY);
113177
+ };
113178
+ Niivue.prototype.drawRuler10cm = function(startXYendXY) {
113179
+ this.gl.bindVertexArray(this.genericVAO);
111988
113180
  this.lineShader.use(this.gl);
111989
- this.gl.uniform4fv(this.lineShader.uniforms["lineColor"], this.opts.selectionBoxColor);
113181
+ this.gl.uniform4fv(this.lineShader.uniforms["lineColor"], this.opts.rulerColor);
111990
113182
  this.gl.uniform2fv(this.lineShader.uniforms["canvasWidthHeight"], [
111991
113183
  this.gl.canvas.width,
111992
113184
  this.gl.canvas.height
111993
113185
  ]);
111994
- this.gl.uniform4f(this.lineShader.uniforms["leftTopWidthHeight"], leftTopWidthHeight[0], leftTopWidthHeight[1], leftTopWidthHeight[2], leftTopWidthHeight[3]);
113186
+ this.gl.uniform1f(this.lineShader.uniforms["thickness"], this.opts.rulerWidth);
113187
+ this.gl.uniform4fv(this.lineShader.uniforms["startXYendXY"], startXYendXY);
113188
+ this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
113189
+ let w1cm = -0.1 * (startXYendXY[0] - startXYendXY[2]);
113190
+ let b = startXYendXY[1];
113191
+ let t = b - 2 * this.opts.rulerWidth;
113192
+ let t2 = b - 4 * this.opts.rulerWidth;
113193
+ for (let i2 = 0; i2 < 11; i2++) {
113194
+ let l = startXYendXY[0] + i2 * w1cm;
113195
+ let xyxy = [l, b, l, t];
113196
+ if (i2 % 5 === 0)
113197
+ xyxy[3] = t2;
113198
+ this.gl.uniform4fv(this.lineShader.uniforms["startXYendXY"], xyxy);
113199
+ this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
113200
+ }
113201
+ this.gl.bindVertexArray(this.unusedVAO);
113202
+ };
113203
+ Niivue.prototype.drawMeasurementTool = function(startXYendXY) {
113204
+ let gl = this.gl;
113205
+ gl.bindVertexArray(this.genericVAO);
113206
+ gl.depthFunc(gl.ALWAYS);
113207
+ gl.enable(gl.BLEND);
113208
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
113209
+ this.lineShader.use(this.gl);
113210
+ gl.uniform4fv(this.lineShader.uniforms["lineColor"], this.opts.rulerColor);
113211
+ gl.uniform2fv(this.lineShader.uniforms["canvasWidthHeight"], [
113212
+ gl.canvas.width,
113213
+ gl.canvas.height
113214
+ ]);
113215
+ gl.uniform1f(this.lineShader.uniforms["thickness"], this.opts.rulerWidth);
113216
+ gl.uniform4fv(this.lineShader.uniforms["startXYendXY"], startXYendXY);
113217
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
113218
+ let c2 = this.opts.rulerColor;
113219
+ c2[3] = 1;
113220
+ gl.uniform4fv(this.lineShader.uniforms["lineColor"], c2);
113221
+ let w = this.opts.rulerWidth;
113222
+ gl.uniform1f(this.lineShader.uniforms["thickness"], w * 2);
113223
+ let sXYeXY = [
113224
+ startXYendXY[0],
113225
+ startXYendXY[1] - w,
113226
+ startXYendXY[0],
113227
+ startXYendXY[1] + w
113228
+ ];
113229
+ gl.uniform4fv(this.lineShader.uniforms["startXYendXY"], sXYeXY);
113230
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
113231
+ sXYeXY = [
113232
+ startXYendXY[2],
113233
+ startXYendXY[3] - w,
113234
+ startXYendXY[2],
113235
+ startXYendXY[3] + w
113236
+ ];
113237
+ gl.uniform4fv(this.lineShader.uniforms["startXYendXY"], sXYeXY);
113238
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
113239
+ let startXY = this.canvasPos2frac([startXYendXY[0], startXYendXY[1]]);
113240
+ let endXY = this.canvasPos2frac([startXYendXY[2], startXYendXY[3]]);
113241
+ if (startXY[0] >= 0 && endXY[0] >= 0) {
113242
+ startXY = this.frac2mm(startXY);
113243
+ endXY = this.frac2mm(endXY);
113244
+ let v = create$1();
113245
+ sub(v, startXY, endXY);
113246
+ let lenMM = len(v);
113247
+ let decimals = 2;
113248
+ if (lenMM > 9)
113249
+ decimals = 1;
113250
+ if (lenMM > 99)
113251
+ decimals = 0;
113252
+ let stringMM = lenMM.toFixed(decimals);
113253
+ this.drawTextBetween(startXYendXY, stringMM);
113254
+ }
113255
+ gl.bindVertexArray(this.unusedVAO);
113256
+ };
113257
+ Niivue.prototype.drawBox = function(leftTopWidthHeight, lineColor = [1, 0, 0, 1]) {
113258
+ this.rectShader.use(this.gl);
113259
+ this.gl.enable(this.gl.BLEND);
113260
+ this.gl.uniform4fv(this.rectShader.uniforms["lineColor"], lineColor);
113261
+ this.gl.uniform2fv(this.rectShader.uniforms["canvasWidthHeight"], [
113262
+ this.gl.canvas.width,
113263
+ this.gl.canvas.height
113264
+ ]);
113265
+ this.gl.uniform4f(this.rectShader.uniforms["leftTopWidthHeight"], leftTopWidthHeight[0], leftTopWidthHeight[1], leftTopWidthHeight[2], leftTopWidthHeight[3]);
111995
113266
  this.gl.bindVertexArray(this.genericVAO);
111996
113267
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
111997
113268
  this.gl.bindVertexArray(this.unusedVAO);
111998
113269
  };
111999
- Niivue.prototype.drawColorbar = function(leftTopWidthHeight) {
113270
+ Niivue.prototype.drawSelectionBox = function(leftTopWidthHeight, lineColor = [1, 0, 0, 1]) {
113271
+ this.drawBox(leftTopWidthHeight, this.opts.selectionBoxColor);
113272
+ };
113273
+ function tickSpacingX(tickCount, mn, mx) {
113274
+ let range = Math.abs(mx - mn);
113275
+ if (range === 0)
113276
+ return [0, 0];
113277
+ let unroundedTickSize = range / (tickCount - 1);
113278
+ let x2 = Math.ceil(Math.log10(unroundedTickSize) - 1);
113279
+ let pow10x = Math.pow(10, x2);
113280
+ let spacing = Math.ceil(unroundedTickSize / pow10x) * pow10x;
113281
+ let ticMin = mn;
113282
+ if (ticMin % spacing !== 0 && range % spacing !== 0)
113283
+ ticMin = Math.floor((mn + spacing) / spacing) * spacing;
113284
+ if (mn / spacing % 1 !== 0)
113285
+ ticMin = Math.sign(ticMin) * Math.round(Math.abs(ticMin));
113286
+ return [spacing, ticMin];
113287
+ }
113288
+ function tickSpacing(mn, mx) {
113289
+ let range = Math.abs(mx - mn);
113290
+ let [spacing, ticMin] = tickSpacingX(5, mn, mx);
113291
+ if (range % spacing === 0)
113292
+ return [spacing, ticMin];
113293
+ [spacing, ticMin] = tickSpacingX(4, mn, mx);
113294
+ if (range % spacing === 0)
113295
+ return [spacing, ticMin];
113296
+ [spacing, ticMin] = tickSpacingX(5, mn, mx);
113297
+ return [spacing, ticMin];
113298
+ }
113299
+ Niivue.prototype.effectiveCanvasHeight = function() {
113300
+ return this.gl.canvas.height - this.colorbarHeight;
113301
+ };
113302
+ Niivue.prototype.reserveColorbarPanel = function() {
113303
+ let txtHt = Math.max(this.opts.textHeight, 0.01);
113304
+ txtHt = txtHt * Math.min(this.gl.canvas.height, this.gl.canvas.width);
113305
+ let fullHt = 3 * txtHt;
113306
+ let leftTopWidthHeight = [
113307
+ 0,
113308
+ this.gl.canvas.height - fullHt,
113309
+ this.gl.canvas.width,
113310
+ fullHt
113311
+ ];
113312
+ this.colorbarHeight = leftTopWidthHeight[3] + 1;
113313
+ return leftTopWidthHeight;
113314
+ };
113315
+ Niivue.prototype.drawColorbarCore = function(layer = 0, leftTopWidthHeight = [0, 0, 0, 0], isNegativeColor = false) {
113316
+ if (this.volumes.length < 1)
113317
+ return;
113318
+ if (layer >= this.volumes.length)
113319
+ layer = 0;
112000
113320
  if (leftTopWidthHeight[2] <= 0 || leftTopWidthHeight[3] <= 0)
112001
113321
  return;
113322
+ let txtHt = Math.max(this.opts.textHeight, 0.01);
113323
+ txtHt = txtHt * Math.min(this.gl.canvas.height, this.gl.canvas.width);
113324
+ let margin = txtHt;
113325
+ let fullHt = 3 * txtHt;
113326
+ let barHt = txtHt;
113327
+ if (leftTopWidthHeight[3] < fullHt) {
113328
+ if (leftTopWidthHeight[3] < 3)
113329
+ return;
113330
+ margin = 1;
113331
+ barHt = leftTopWidthHeight[3] - 2;
113332
+ }
113333
+ this.gl.disable(this.gl.DEPTH_TEST);
113334
+ this.colorbarHeight = leftTopWidthHeight[3] + 1;
113335
+ [
113336
+ this.opts.backColor[0],
113337
+ this.opts.backColor[1],
113338
+ this.opts.backColor[2],
113339
+ 0.5
113340
+ ];
113341
+ leftTopWidthHeight[1];
113342
+ let barLTWH = [
113343
+ leftTopWidthHeight[0] + margin,
113344
+ leftTopWidthHeight[1],
113345
+ leftTopWidthHeight[2] - 2 * margin,
113346
+ barHt
113347
+ ];
113348
+ let rimLTWH = [
113349
+ barLTWH[0] - 1,
113350
+ barLTWH[1] - 1,
113351
+ barLTWH[2] + 2,
113352
+ barLTWH[3] + 2
113353
+ ];
113354
+ this.drawBox(rimLTWH, this.opts.crosshairColor);
112002
113355
  this.colorbarShader.use(this.gl);
112003
113356
  this.gl.activeTexture(this.gl.TEXTURE1);
112004
113357
  this.gl.bindTexture(this.gl.TEXTURE_2D, this.colormapTexture);
112005
113358
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
112006
113359
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
113360
+ let lx = layer;
113361
+ if (isNegativeColor)
113362
+ lx += this.volumes.length;
113363
+ this.gl.uniform1f(this.colorbarShader.uniforms["layer"], lx);
112007
113364
  this.gl.uniform2fv(this.colorbarShader.uniforms["canvasWidthHeight"], [
112008
113365
  this.gl.canvas.width,
112009
113366
  this.gl.canvas.height
112010
113367
  ]);
112011
- this.gl.uniform4f(this.colorbarShader.uniforms["leftTopWidthHeight"], leftTopWidthHeight[0], leftTopWidthHeight[1], leftTopWidthHeight[2], leftTopWidthHeight[3]);
113368
+ this.gl.uniform4fv(this.colorbarShader.uniforms["leftTopWidthHeight"], barLTWH);
112012
113369
  this.gl.bindVertexArray(this.genericVAO);
112013
113370
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
112014
113371
  this.gl.bindVertexArray(this.unusedVAO);
112015
113372
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
112016
113373
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
113374
+ let min2 = this.volumes[layer].cal_min;
113375
+ let max2 = this.volumes[layer].cal_max;
113376
+ if (min2 >= max2 || txtHt < 1)
113377
+ return;
113378
+ let range = max2 - min2;
113379
+ let [spacing, ticMin] = tickSpacing(min2, max2);
113380
+ function humanize(x2) {
113381
+ return x2.toFixed(6).replace(/\.?0*$/, "");
113382
+ }
113383
+ let tic = ticMin;
113384
+ let ticLTWH = [0, barLTWH[1] + barLTWH[3] - txtHt * 0.5, 2, txtHt * 0.75];
113385
+ let txtTop = ticLTWH[1] + ticLTWH[3];
113386
+ while (tic <= max2) {
113387
+ ticLTWH[0] = barLTWH[0] + (tic - min2) / range * barLTWH[2];
113388
+ this.drawBox(ticLTWH);
113389
+ let str = humanize(tic);
113390
+ if (isNegativeColor)
113391
+ str = "-" + str;
113392
+ this.drawTextBelow([ticLTWH[0], txtTop], str);
113393
+ tic += spacing;
113394
+ }
113395
+ };
113396
+ Niivue.prototype.drawColorbar = function() {
113397
+ let nlayers = this.volumes.length;
113398
+ if (nlayers < 1)
113399
+ return;
113400
+ let ncolorMapNegative = 0;
113401
+ for (let i2 = 0; i2 < nlayers; i2++)
113402
+ if (this.volumes[i2].colorMapNegative.length > 0)
113403
+ ncolorMapNegative++;
113404
+ let leftTopWidthHeight = this.reserveColorbarPanel();
113405
+ let txtHt = Math.max(this.opts.textHeight, 0.01);
113406
+ txtHt = txtHt * Math.min(this.gl.canvas.height, this.gl.canvas.width);
113407
+ let fullHt = 3 * txtHt;
113408
+ let wid = leftTopWidthHeight[2] / (nlayers + ncolorMapNegative);
113409
+ if (leftTopWidthHeight[2] <= 0 || leftTopWidthHeight[3] <= 0) {
113410
+ wid = this.gl.canvas.width / nlayers;
113411
+ leftTopWidthHeight = [0, this.gl.canvas.height - fullHt, wid, fullHt];
113412
+ }
113413
+ leftTopWidthHeight[2] = wid;
113414
+ for (let i2 = 0; i2 < nlayers; i2++) {
113415
+ this.drawColorbarCore(i2, leftTopWidthHeight);
113416
+ leftTopWidthHeight[0] += wid;
113417
+ if (this.volumes[i2].colorMapNegative.length === 0)
113418
+ continue;
113419
+ this.drawColorbarCore(i2, leftTopWidthHeight, true);
113420
+ leftTopWidthHeight[0] += wid;
113421
+ }
112017
113422
  };
112018
113423
  Niivue.prototype.textWidth = function(scale2, str) {
112019
113424
  let w = 0;
@@ -112074,6 +113479,34 @@ Niivue.prototype.drawTextLeft = function(xy, str, scale2 = 1, color = null) {
112074
113479
  xy[1] -= 0.5 * size;
112075
113480
  this.drawText(xy, str, scale2, color);
112076
113481
  };
113482
+ Niivue.prototype.drawTextRightBelow = function(xy, str, scale2 = 1, color = null) {
113483
+ if (this.opts.textHeight <= 0)
113484
+ return;
113485
+ this.opts.textHeight * this.gl.canvas.height * scale2;
113486
+ this.drawText(xy, str, scale2, color);
113487
+ };
113488
+ Niivue.prototype.drawTextBetween = function(startXYendXY, str, scale2 = 1, color = null) {
113489
+ if (this.opts.textHeight <= 0)
113490
+ return;
113491
+ let xy = [
113492
+ (startXYendXY[0] + startXYendXY[2]) * 0.5,
113493
+ (startXYendXY[1] + startXYendXY[3]) * 0.5
113494
+ ];
113495
+ let size = this.opts.textHeight * this.gl.canvas.height * scale2;
113496
+ let w = this.textWidth(size, str);
113497
+ xy[0] -= 0.5 * w;
113498
+ xy[1] -= 0.5 * size;
113499
+ let LTWH = [xy[0] - 1, xy[1] - 1, w + 2, size + 2];
113500
+ let clr = color;
113501
+ if (clr === null)
113502
+ clr = this.opts.crosshairColor;
113503
+ if (clr[0] + clr[1] + clr[2] > 0.8)
113504
+ clr = [0, 0, 0, 0.5];
113505
+ else
113506
+ clr = [1, 1, 1, 0.5];
113507
+ this.drawRect(LTWH, clr);
113508
+ this.drawText(xy, str, scale2, color);
113509
+ };
112077
113510
  Niivue.prototype.drawTextBelow = function(xy, str, scale2 = 1, color = null) {
112078
113511
  if (this.opts.textHeight <= 0)
112079
113512
  return;
@@ -112107,114 +113540,271 @@ Niivue.prototype.setInterpolation = function(isNearest) {
112107
113540
  this.updateInterpolation(1);
112108
113541
  this.drawScene();
112109
113542
  };
112110
- Niivue.prototype.calculateMvpMatrixX = function() {
112111
- let whratio = this.gl.canvas.clientWidth / this.gl.canvas.clientHeight;
112112
- let scale2 = this.furthestFromPivot;
112113
- let origin = [0, 0, 0];
113543
+ function deg2rad(deg) {
113544
+ return deg * (Math.PI / 180);
113545
+ }
113546
+ Niivue.prototype.calculateMvpMatrix2D = function(leftTopWidthHeight, mn, mx, clipTolerance = Infinity, clipDepth = 0, azimuth = null, elevation = null, isRadiolgical) {
113547
+ let gl = this.gl;
113548
+ gl.viewport(leftTopWidthHeight[0], this.gl.canvas.clientHeight - (leftTopWidthHeight[1] + leftTopWidthHeight[3]), leftTopWidthHeight[2], leftTopWidthHeight[3]);
113549
+ let left = mn[0];
113550
+ let right = mx[0];
113551
+ let leftTopMM = [left, mn[1]];
113552
+ let fovMM = [right - left, mx[1] - mn[1]];
113553
+ if (isRadiolgical) {
113554
+ leftTopMM = [mx[0], mn[1]];
113555
+ fovMM = [mn[0] - mx[0], mx[1] - mn[1]];
113556
+ left = -mx[0];
113557
+ right = -mn[0];
113558
+ }
113559
+ let scale2 = 2 * Math.max(Math.abs(mn[2]), Math.abs(mx[2]));
112114
113560
  let projectionMatrix = create$2();
112115
- if (whratio < 1)
112116
- ortho(projectionMatrix, -scale2, scale2, -scale2 / whratio, scale2 / whratio, 0.01, scale2 * 8);
112117
- else
112118
- ortho(projectionMatrix, -scale2 * whratio, scale2 * whratio, -scale2, scale2, 0.01, scale2 * 8);
113561
+ let near = 0.01;
113562
+ let far = scale2 * 8;
113563
+ if (clipTolerance !== Infinity) {
113564
+ let r = isRadiolgical;
113565
+ if (elevation === 0 && (azimuth === 0 || azimuth === 180))
113566
+ r = !r;
113567
+ let dx = scale2 * 1.8 - clipDepth;
113568
+ if (!r)
113569
+ dx = scale2 * 1.8 + clipDepth;
113570
+ near = dx - clipTolerance;
113571
+ far = dx + clipTolerance;
113572
+ }
113573
+ ortho(projectionMatrix, left, right, mn[1], mx[1], near, far);
112119
113574
  const modelMatrix = create$2();
112120
113575
  modelMatrix[0] = -1;
112121
113576
  let translateVec3 = fromValues$1(0, 0, -scale2 * 1.8);
112122
113577
  translate(modelMatrix, modelMatrix, translateVec3);
112123
- if (this.position)
112124
- translate(modelMatrix, modelMatrix, this.position);
112125
- translate(modelMatrix, modelMatrix, [
112126
- -origin[0],
112127
- -origin[1],
112128
- -origin[2]
112129
- ]);
113578
+ rotateX(modelMatrix, modelMatrix, deg2rad(270 - elevation));
113579
+ rotateZ(modelMatrix, modelMatrix, deg2rad(azimuth - 180));
112130
113580
  let iModelMatrix = create$2();
112131
113581
  invert(iModelMatrix, modelMatrix);
112132
113582
  let normalMatrix = create$2();
112133
113583
  transpose(normalMatrix, iModelMatrix);
112134
113584
  let modelViewProjectionMatrix = create$2();
112135
- multiply$1(modelViewProjectionMatrix, projectionMatrix, modelMatrix);
112136
- return [modelViewProjectionMatrix, modelMatrix, normalMatrix];
113585
+ multiply(modelViewProjectionMatrix, projectionMatrix, modelMatrix);
113586
+ return {
113587
+ modelViewProjectionMatrix,
113588
+ modelMatrix,
113589
+ normalMatrix,
113590
+ leftTopMM,
113591
+ fovMM
113592
+ };
112137
113593
  };
112138
- Niivue.prototype.drawMesh3DX = function(isDepthTest = true, alpha = 1, leftTopWidthHeight) {
112139
- if (this.meshes.length < 1)
113594
+ Niivue.prototype.swizzleVec3MM = function(v3, axCorSag) {
113595
+ if (axCorSag === this.sliceTypeCoronal) {
113596
+ v3 = swizzleVec3(v3, [0, 2, 1]);
113597
+ } else if (axCorSag === this.sliceTypeSagittal) {
113598
+ v3 = swizzleVec3(v3, [1, 2, 0]);
113599
+ }
113600
+ return v3;
113601
+ };
113602
+ Niivue.prototype.screenFieldOfViewMM = function(axCorSag = 0, forceSliceMM = false) {
113603
+ if (!forceSliceMM && !this.opts.isSliceMM) {
113604
+ let fov = this.volumeObject3D.fieldOfViewDeObliqueMM.slice();
113605
+ return this.swizzleVec3MM(fov, axCorSag);
113606
+ }
113607
+ let mnMM = this.volumeObject3D.extentsMin.slice();
113608
+ let mxMM = this.volumeObject3D.extentsMax.slice();
113609
+ create$2();
113610
+ mnMM = this.swizzleVec3MM(mnMM, axCorSag);
113611
+ mxMM = this.swizzleVec3MM(mxMM, axCorSag);
113612
+ let fovMM = create$1();
113613
+ subtract(fovMM, mxMM, mnMM);
113614
+ return fovMM;
113615
+ };
113616
+ Niivue.prototype.screenFieldOfViewExtendedMM = function(axCorSag = 0) {
113617
+ let mnMM = this.volumeObject3D.extentsMin.slice();
113618
+ let mxMM = this.volumeObject3D.extentsMax.slice();
113619
+ let rotation = create$2();
113620
+ mnMM = this.swizzleVec3MM(mnMM, axCorSag);
113621
+ mxMM = this.swizzleVec3MM(mxMM, axCorSag);
113622
+ let fovMM = create$1();
113623
+ subtract(fovMM, mxMM, mnMM);
113624
+ return { mnMM, mxMM, rotation, fovMM };
113625
+ };
113626
+ Niivue.prototype.drawSliceOrientationText = function(leftTopWidthHeight, axCorSag) {
113627
+ this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
113628
+ let topText = "S";
113629
+ if (axCorSag === this.sliceTypeAxial)
113630
+ topText = "A";
113631
+ let leftText = this.opts.isRadiologicalConvention ? "R" : "L";
113632
+ if (axCorSag === this.sliceTypeSagittal)
113633
+ leftText = this.opts.sagittalNoseLeft ? "A" : "P";
113634
+ if (this.opts.isCornerOrientationText) {
113635
+ this.drawTextRightBelow([leftTopWidthHeight[0], leftTopWidthHeight[1]], leftText + topText);
112140
113636
  return;
113637
+ }
113638
+ this.drawTextBelow([
113639
+ leftTopWidthHeight[0] + leftTopWidthHeight[2] * 0.5,
113640
+ leftTopWidthHeight[1]
113641
+ ], topText);
113642
+ this.drawTextRight([
113643
+ leftTopWidthHeight[0],
113644
+ leftTopWidthHeight[1] + leftTopWidthHeight[3] * 0.5
113645
+ ], leftText);
113646
+ };
113647
+ Niivue.prototype.xyMM2xyzMM = function(axCorSag, sliceFrac) {
113648
+ let sliceDim = 2;
113649
+ if (axCorSag === this.sliceTypeCoronal)
113650
+ sliceDim = 1;
113651
+ if (axCorSag === this.sliceTypeSagittal)
113652
+ sliceDim = 0;
113653
+ let a = [0, 0, 0];
113654
+ let b = [1, 1, 0];
113655
+ let c2 = [1, 0, 1];
113656
+ a[sliceDim] = sliceFrac;
113657
+ b[sliceDim] = sliceFrac;
113658
+ c2[sliceDim] = sliceFrac;
113659
+ a = this.frac2mm(a);
113660
+ b = this.frac2mm(b);
113661
+ c2 = this.frac2mm(c2);
113662
+ a = this.swizzleVec3MM(a, axCorSag);
113663
+ b = this.swizzleVec3MM(b, axCorSag);
113664
+ c2 = this.swizzleVec3MM(c2, axCorSag);
113665
+ let denom = (b[0] - a[0]) * (c2[1] - a[1]) - (c2[0] - a[0]) * (b[1] - a[1]);
113666
+ let yMult = (b[0] - a[0]) * (c2[2] - a[2]) - (c2[0] - a[0]) * (b[2] - a[2]);
113667
+ yMult /= denom;
113668
+ let xMult = (b[1] - a[1]) * (c2[2] - a[2]) - (c2[1] - a[1]) * (b[2] - a[2]);
113669
+ xMult /= denom;
113670
+ let AxyzMxy = [0, 0, 0, 0, 0];
113671
+ AxyzMxy[0] = a[0];
113672
+ AxyzMxy[1] = a[1];
113673
+ AxyzMxy[2] = a[2];
113674
+ AxyzMxy[3] = xMult;
113675
+ AxyzMxy[4] = yMult;
113676
+ return AxyzMxy;
113677
+ };
113678
+ Niivue.prototype.draw2DMM = function(leftTopWidthHeight, axCorSag, customMM = NaN) {
113679
+ let screen2 = this.screenFieldOfViewExtendedMM(axCorSag);
113680
+ let isRadiolgical = this.opts.isRadiologicalConvention && axCorSag < this.sliceTypeSagittal;
113681
+ if (customMM === Infinity || customMM === -Infinity) {
113682
+ isRadiolgical = customMM !== Infinity;
113683
+ if (axCorSag === this.sliceTypeCoronal)
113684
+ isRadiolgical = !isRadiolgical;
113685
+ } else if (this.opts.sagittalNoseLeft && axCorSag === this.sliceTypeSagittal)
113686
+ isRadiolgical = !isRadiolgical;
113687
+ let elevation = 0;
113688
+ let azimuth = 0;
113689
+ if (axCorSag === this.sliceTypeSagittal) {
113690
+ azimuth = isRadiolgical ? 90 : -90;
113691
+ } else if (axCorSag === this.sliceTypeCoronal) {
113692
+ azimuth = isRadiolgical ? 180 : 0;
113693
+ } else {
113694
+ azimuth = isRadiolgical ? 180 : 0;
113695
+ elevation = isRadiolgical ? -90 : 90;
113696
+ }
113697
+ if (leftTopWidthHeight[2] === 0 || leftTopWidthHeight[3] === 0)
113698
+ leftTopWidthHeight = this.scaleSlice(screen2.fovMM[0], screen2.fovMM[1]);
113699
+ if (isNaN(customMM)) {
113700
+ let panXY = this.swizzleVec3MM(this.scene.pan2Dxyzmm, axCorSag);
113701
+ screen2.mnMM[0] -= panXY[0];
113702
+ screen2.mxMM[0] -= panXY[0];
113703
+ screen2.mnMM[1] -= panXY[1];
113704
+ screen2.mxMM[1] -= panXY[1];
113705
+ screen2.mnMM[0] /= this.scene.pan2Dxyzmm[3];
113706
+ screen2.mxMM[0] /= this.scene.pan2Dxyzmm[3];
113707
+ screen2.mnMM[1] /= this.scene.pan2Dxyzmm[3];
113708
+ screen2.mxMM[1] /= this.scene.pan2Dxyzmm[3];
113709
+ }
113710
+ let sliceDim = 2;
113711
+ if (axCorSag === this.sliceTypeCoronal)
113712
+ sliceDim = 1;
113713
+ if (axCorSag === this.sliceTypeSagittal)
113714
+ sliceDim = 0;
113715
+ let sliceFrac = this.scene.crosshairPos[sliceDim];
113716
+ let mm = this.frac2mm(this.scene.crosshairPos);
113717
+ if (!isNaN(customMM) && customMM !== Infinity && customMM !== -Infinity) {
113718
+ mm = this.frac2mm([0.5, 0.5, 0.5]);
113719
+ mm[sliceDim] = customMM;
113720
+ let frac = this.mm2frac(mm);
113721
+ sliceFrac = frac[sliceDim];
113722
+ }
113723
+ let sliceMM = mm[sliceDim];
112141
113724
  let gl = this.gl;
112142
- let m, modelMtx, normMtx;
112143
- [m, modelMtx, normMtx] = this.calculateMvpMatrixX(this.volumeObject3D, leftTopWidthHeight);
113725
+ gl.clear(gl.DEPTH_BUFFER_BIT);
113726
+ let obj = this.calculateMvpMatrix2D(leftTopWidthHeight, screen2.mnMM, screen2.mxMM, Infinity, 0, azimuth, elevation, isRadiolgical);
113727
+ if (customMM === Infinity || customMM === -Infinity) {
113728
+ this.draw3D(leftTopWidthHeight, obj.modelViewProjectionMatrix, obj.modelMatrix, obj.normalMatrix, azimuth, elevation);
113729
+ return;
113730
+ }
112144
113731
  gl.enable(gl.DEPTH_TEST);
112145
113732
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
112146
113733
  gl.disable(gl.BLEND);
112147
113734
  gl.depthFunc(gl.GREATER);
112148
- if (isDepthTest) {
112149
- gl.disable(gl.BLEND);
112150
- gl.depthFunc(gl.GREATER);
112151
- } else {
112152
- gl.enable(gl.BLEND);
112153
- gl.depthFunc(gl.ALWAYS);
112154
- }
112155
113735
  gl.disable(gl.CULL_FACE);
112156
- this.meshShader.use(this.gl);
112157
- gl.uniformMatrix4fv(this.meshShader.uniforms["mvpMtx"], false, m);
112158
- gl.uniformMatrix4fv(this.meshShader.uniforms["modelMtx"], false, modelMtx);
112159
- gl.uniformMatrix4fv(this.meshShader.uniforms["normMtx"], false, normMtx);
112160
- gl.uniform1f(this.meshShader.uniforms["opacity"], alpha);
112161
- let hasFibers = false;
112162
- for (let i2 = 0; i2 < this.meshes.length; i2++) {
112163
- if (this.meshes[i2].indexCount < 3)
112164
- continue;
112165
- if (this.meshes[i2].offsetPt0) {
112166
- hasFibers = true;
112167
- continue;
112168
- }
112169
- gl.bindVertexArray(this.meshes[i2].vao);
112170
- gl.drawElements(gl.TRIANGLES, this.meshes[i2].indexCount, gl.UNSIGNED_INT, 0);
112171
- gl.bindVertexArray(this.unusedVAO);
112172
- }
112173
- if (!hasFibers) {
112174
- gl.enable(gl.BLEND);
112175
- gl.depthFunc(gl.ALWAYS);
112176
- return;
113736
+ this.sliceMMShader.use(this.gl);
113737
+ this.gl.uniform1i(this.sliceMMShader.uniforms["backgroundMasksOverlays"], this.backgroundMasksOverlays);
113738
+ this.gl.uniform1f(this.sliceMMShader.uniforms["opacity"], this.volumes[0].opacity);
113739
+ this.gl.uniform1i(this.sliceMMShader.uniforms["axCorSag"], axCorSag);
113740
+ this.gl.uniform1f(this.sliceMMShader.uniforms["slice"], sliceFrac);
113741
+ this.gl.uniformMatrix4fv(this.sliceMMShader.uniforms["frac2mm"], false, this.volumes[0].frac2mm);
113742
+ this.gl.uniformMatrix4fv(this.sliceMMShader.uniforms["mvpMtx"], false, obj.modelViewProjectionMatrix.slice());
113743
+ this.gl.bindVertexArray(this.genericVAO);
113744
+ this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
113745
+ this.gl.bindVertexArray(this.unusedVAO);
113746
+ this.screenSlices.push({
113747
+ leftTopWidthHeight,
113748
+ axCorSag,
113749
+ sliceFrac,
113750
+ AxyzMxy: this.xyMM2xyzMM(axCorSag, sliceFrac),
113751
+ leftTopMM: obj.leftTopMM,
113752
+ fovMM: obj.fovMM
113753
+ });
113754
+ if (isNaN(customMM)) {
113755
+ this.drawCrosshairs3D(true, 1, obj.modelViewProjectionMatrix);
112177
113756
  }
112178
- let shader = this.fiberShader;
112179
- shader.use(this.gl);
112180
- gl.uniformMatrix4fv(shader.uniforms["mvpMtx"], false, m);
112181
- gl.uniform1f(shader.uniforms["opacity"], alpha);
112182
- for (let i2 = 0; i2 < this.meshes.length; i2++) {
112183
- if (this.meshes[i2].indexCount < 3)
112184
- continue;
112185
- if (!this.meshes[i2].offsetPt0)
112186
- continue;
112187
- gl.bindVertexArray(this.meshes[i2].vao);
112188
- gl.drawElements(gl.LINE_STRIP, this.meshes[i2].indexCount, gl.UNSIGNED_INT, 0);
112189
- gl.bindVertexArray(this.unusedVAO);
113757
+ if (this.opts.meshThicknessOn2D > 0) {
113758
+ if (this.opts.meshThicknessOn2D !== Infinity)
113759
+ obj = this.calculateMvpMatrix2D(leftTopWidthHeight, screen2.mnMM, screen2.mxMM, this.opts.meshThicknessOn2D, sliceMM, azimuth, elevation, isRadiolgical);
113760
+ this.drawMesh3D(true, 1, obj.modelViewProjectionMatrix, obj.modelMatrix, obj.normalMatrix);
112190
113761
  }
112191
- gl.enable(gl.BLEND);
112192
- gl.depthFunc(gl.ALWAYS);
113762
+ if (isNaN(customMM))
113763
+ this.drawCrosshairs3D(false, 0.15, obj.modelViewProjectionMatrix);
113764
+ this.drawSliceOrientationText(leftTopWidthHeight, axCorSag);
112193
113765
  this.readyForSync = true;
112194
113766
  };
112195
- Niivue.prototype.draw2D = function(leftTopWidthHeight, axCorSag) {
113767
+ Niivue.prototype.draw2DVox = function(leftTopWidthHeight, axCorSag, customMM = NaN) {
113768
+ let fovMM = this.screenFieldOfViewMM(axCorSag);
113769
+ if (leftTopWidthHeight[2] === 0 || leftTopWidthHeight[3] === 0) {
113770
+ leftTopWidthHeight = this.scaleSlice(fovMM[0], fovMM[1]);
113771
+ }
112196
113772
  this.gl.cullFace(this.gl.FRONT);
113773
+ let ltwh = leftTopWidthHeight.slice();
112197
113774
  let gl = this.gl;
112198
113775
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
112199
113776
  gl.disable(gl.DEPTH_TEST);
112200
- var crossXYZ = [
113777
+ let sliceDim = 2;
113778
+ let crossXYZ = [
112201
113779
  this.scene.crosshairPos[0],
112202
113780
  this.scene.crosshairPos[1],
112203
113781
  this.scene.crosshairPos[2]
112204
113782
  ];
112205
- if (axCorSag === this.sliceTypeCoronal)
113783
+ if (axCorSag === this.sliceTypeCoronal) {
113784
+ sliceDim = 1;
112206
113785
  crossXYZ = [
112207
113786
  this.scene.crosshairPos[0],
112208
113787
  this.scene.crosshairPos[2],
112209
113788
  this.scene.crosshairPos[1]
112210
113789
  ];
112211
- if (axCorSag === this.sliceTypeSagittal)
113790
+ }
113791
+ if (axCorSag === this.sliceTypeSagittal) {
113792
+ sliceDim = 0;
112212
113793
  crossXYZ = [
112213
113794
  this.scene.crosshairPos[1],
112214
113795
  this.scene.crosshairPos[2],
112215
113796
  this.scene.crosshairPos[0]
112216
113797
  ];
113798
+ }
113799
+ if (!isNaN(customMM)) {
113800
+ let mm = this.frac2mm([0.5, 0.5, 0.5]);
113801
+ mm[sliceDim] = customMM;
113802
+ let frac = this.mm2frac(mm);
113803
+ crossXYZ[2] = frac[sliceDim];
113804
+ }
112217
113805
  let isMirrorLR = this.opts.isRadiologicalConvention && axCorSag < this.sliceTypeSagittal;
113806
+ if (this.opts.sagittalNoseLeft && axCorSag === this.sliceTypeSagittal)
113807
+ isMirrorLR = !isMirrorLR;
112218
113808
  this.sliceShader.use(this.gl);
112219
113809
  this.gl.uniform1f(this.sliceShader.uniforms["opacity"], this.volumes[0].opacity);
112220
113810
  this.gl.uniform1i(this.sliceShader.uniforms["backgroundMasksOverlays"], this.backgroundMasksOverlays);
@@ -112232,59 +113822,50 @@ Niivue.prototype.draw2D = function(leftTopWidthHeight, axCorSag) {
112232
113822
  this.gl.uniform4f(this.sliceShader.uniforms["leftTopWidthHeight"], leftTopWidthHeight[0], leftTopWidthHeight[1], leftTopWidthHeight[2], leftTopWidthHeight[3]);
112233
113823
  this.gl.bindVertexArray(this.genericVAO);
112234
113824
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
112235
- this.screenSlices[this.numScreenSlices].leftTopWidthHeight = leftTopWidthHeight;
112236
- this.screenSlices[this.numScreenSlices].axCorSag = axCorSag;
112237
- this.numScreenSlices += 1;
113825
+ this.screenSlices.push({
113826
+ leftTopWidthHeight,
113827
+ axCorSag,
113828
+ sliceFrac: crossXYZ[2],
113829
+ AxyzMxy: [],
113830
+ leftTopMM: [],
113831
+ fovMM: [fovMM[0], fovMM[1]]
113832
+ });
113833
+ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
113834
+ this.drawSliceOrientationText(ltwh, axCorSag);
113835
+ if (!isNaN(customMM))
113836
+ return;
112238
113837
  if (this.opts.crosshairWidth <= 0)
112239
113838
  return;
112240
- this.lineShader.use(this.gl);
112241
- this.gl.uniform4fv(this.lineShader.uniforms["lineColor"], this.opts.crosshairColor);
112242
- this.gl.uniform2fv(this.lineShader.uniforms["canvasWidthHeight"], [
113839
+ this.gl.bindVertexArray(this.genericVAO);
113840
+ this.rectShader.use(this.gl);
113841
+ this.gl.uniform4fv(this.rectShader.uniforms["lineColor"], this.opts.crosshairColor);
113842
+ this.gl.uniform2fv(this.rectShader.uniforms["canvasWidthHeight"], [
112243
113843
  this.gl.canvas.width,
112244
113844
  this.gl.canvas.height
112245
113845
  ]);
112246
113846
  var xleft = leftTopWidthHeight[0] + leftTopWidthHeight[2] * crossXYZ[0];
112247
- this.gl.uniform4f(this.lineShader.uniforms["leftTopWidthHeight"], xleft - 0.5 * this.opts.crosshairWidth, leftTopWidthHeight[1], this.opts.crosshairWidth, leftTopWidthHeight[3]);
113847
+ this.gl.uniform4f(this.rectShader.uniforms["leftTopWidthHeight"], xleft - 0.5 * this.opts.crosshairWidth, leftTopWidthHeight[1], this.opts.crosshairWidth, leftTopWidthHeight[3]);
112248
113848
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
112249
113849
  var xtop = leftTopWidthHeight[1] + leftTopWidthHeight[3] * (1 - crossXYZ[1]);
112250
- this.gl.uniform4f(this.lineShader.uniforms["leftTopWidthHeight"], leftTopWidthHeight[0], xtop - 0.5 * this.opts.crosshairWidth, leftTopWidthHeight[2], this.opts.crosshairWidth);
113850
+ this.gl.uniform4f(this.rectShader.uniforms["leftTopWidthHeight"], leftTopWidthHeight[0], xtop - 0.5 * this.opts.crosshairWidth, leftTopWidthHeight[2], this.opts.crosshairWidth);
112251
113851
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
112252
113852
  this.gl.bindVertexArray(this.unusedVAO);
112253
113853
  this.gl.enable(this.gl.CULL_FACE);
112254
- if (isMirrorLR)
112255
- this.drawTextRight([
112256
- leftTopWidthHeight[0] + leftTopWidthHeight[2] + 1,
112257
- leftTopWidthHeight[1] + 0.5 * leftTopWidthHeight[3]
112258
- ], "R");
112259
- else if (axCorSag < this.sliceTypeSagittal)
112260
- this.drawTextRight([
112261
- leftTopWidthHeight[0] + 1,
112262
- leftTopWidthHeight[1] + 0.5 * leftTopWidthHeight[3]
112263
- ], "L");
112264
- if (axCorSag === this.sliceTypeAxial)
112265
- this.drawTextBelow([
112266
- leftTopWidthHeight[0] + 0.5 * leftTopWidthHeight[2],
112267
- leftTopWidthHeight[1] + 1
112268
- ], "A");
112269
- if (axCorSag > this.sliceTypeAxial)
112270
- this.drawTextBelow([
112271
- leftTopWidthHeight[0] + 0.5 * leftTopWidthHeight[2],
112272
- leftTopWidthHeight[1] + 1
112273
- ], "S");
112274
- if (axCorSag !== this.sliceTypeAxial)
112275
- return;
112276
- this.setPivot3D();
112277
- gl.viewport(leftTopWidthHeight[0], leftTopWidthHeight[1], leftTopWidthHeight[2], leftTopWidthHeight[3]);
112278
113854
  gl.enable(gl.DEPTH_TEST);
112279
113855
  gl.depthFunc(gl.ALWAYS);
112280
113856
  gl.clearDepth(0);
112281
113857
  this.sync();
112282
113858
  };
112283
- Niivue.prototype.calculateMvpMatrix = function() {
112284
- function deg2rad(deg) {
112285
- return deg * (Math.PI / 180);
112286
- }
112287
- let whratio = this.gl.canvas.clientWidth / this.gl.canvas.clientHeight;
113859
+ Niivue.prototype.draw2D = function(leftTopWidthHeight, axCorSag, customMM = NaN) {
113860
+ if (this.opts.isSliceMM)
113861
+ this.draw2DMM(leftTopWidthHeight, axCorSag, customMM);
113862
+ else
113863
+ this.draw2DVox(leftTopWidthHeight, axCorSag, customMM);
113864
+ };
113865
+ Niivue.prototype.calculateMvpMatrix = function(unused, leftTopWidthHeight = [0, 0, 0, 0], azimuth, elevation) {
113866
+ if (leftTopWidthHeight[2] === 0 || leftTopWidthHeight[3] === 0)
113867
+ leftTopWidthHeight = [0, 0, this.gl.canvas.width, this.gl.canvas.height];
113868
+ let whratio = leftTopWidthHeight[2] / leftTopWidthHeight[3];
112288
113869
  let scale2 = this.furthestFromPivot;
112289
113870
  let origin = this.pivot3D;
112290
113871
  let projectionMatrix = create$2();
@@ -112299,8 +113880,8 @@ Niivue.prototype.calculateMvpMatrix = function() {
112299
113880
  translate(modelMatrix, modelMatrix, translateVec3);
112300
113881
  if (this.position)
112301
113882
  translate(modelMatrix, modelMatrix, this.position);
112302
- rotateX(modelMatrix, modelMatrix, deg2rad(270 - this.scene.renderElevation));
112303
- rotateZ(modelMatrix, modelMatrix, deg2rad(this.scene.renderAzimuth - 180));
113883
+ rotateX(modelMatrix, modelMatrix, deg2rad(270 - elevation));
113884
+ rotateZ(modelMatrix, modelMatrix, deg2rad(azimuth - 180));
112304
113885
  translate(modelMatrix, modelMatrix, [
112305
113886
  -origin[0],
112306
113887
  -origin[1],
@@ -112311,24 +113892,24 @@ Niivue.prototype.calculateMvpMatrix = function() {
112311
113892
  let normalMatrix = create$2();
112312
113893
  transpose(normalMatrix, iModelMatrix);
112313
113894
  let modelViewProjectionMatrix = create$2();
112314
- multiply$1(modelViewProjectionMatrix, projectionMatrix, modelMatrix);
113895
+ multiply(modelViewProjectionMatrix, projectionMatrix, modelMatrix);
112315
113896
  return [modelViewProjectionMatrix, modelMatrix, normalMatrix];
112316
113897
  };
112317
- Niivue.prototype.calculateRayDirection = function() {
112318
- function deg2rad(deg) {
113898
+ Niivue.prototype.calculateRayDirection = function(azimuth, elevation) {
113899
+ function deg2rad2(deg) {
112319
113900
  return deg * (Math.PI / 180);
112320
113901
  }
112321
113902
  const modelMatrix = create$2();
112322
113903
  modelMatrix[0] = -1;
112323
- rotateX(modelMatrix, modelMatrix, deg2rad(270 - this.scene.renderElevation));
112324
- rotateZ(modelMatrix, modelMatrix, deg2rad(this.scene.renderAzimuth - 180));
113904
+ rotateX(modelMatrix, modelMatrix, deg2rad2(270 - elevation));
113905
+ rotateZ(modelMatrix, modelMatrix, deg2rad2(azimuth - 180));
112325
113906
  if (this.back.obliqueRAS) {
112326
113907
  let oblique = clone$1(this.back.obliqueRAS);
112327
- multiply$1(modelMatrix, modelMatrix, oblique);
113908
+ multiply(modelMatrix, modelMatrix, oblique);
112328
113909
  }
112329
113910
  let projectionMatrix = fromValues$2(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1);
112330
113911
  let mvpMatrix = create$2();
112331
- multiply$1(mvpMatrix, projectionMatrix, modelMatrix);
113912
+ multiply(mvpMatrix, projectionMatrix, modelMatrix);
112332
113913
  var inv = create$2();
112333
113914
  invert(inv, mvpMatrix);
112334
113915
  var rayDir4 = fromValues(0, 0, -1, 1);
@@ -112367,6 +113948,8 @@ Niivue.prototype.setPivot3D = function() {
112367
113948
  scale(pivot, pivot, 0.5);
112368
113949
  this.pivot3D = [pivot[0], pivot[1], pivot[2]];
112369
113950
  subtract(pivot, mx, mn);
113951
+ this.extentsMin = mn;
113952
+ this.extentsMax = mx;
112370
113953
  this.furthestFromPivot = length$1(pivot) * 0.5;
112371
113954
  };
112372
113955
  Niivue.prototype.drawGraphLine = function(LTRB, color = [1, 0, 0, 0.5], thickness = 2) {
@@ -112383,35 +113966,22 @@ Niivue.prototype.drawGraphLine = function(LTRB, color = [1, 0, 0, 0.5], thicknes
112383
113966
  this.gl.bindVertexArray(this.unusedVAO);
112384
113967
  };
112385
113968
  Niivue.prototype.drawRect = function(LTWH, color = [1, 0, 0, 0.5]) {
112386
- this.lineShader.use(this.gl);
112387
- this.gl.uniform4fv(this.lineShader.uniforms["lineColor"], color);
112388
- this.gl.uniform2fv(this.lineShader.uniforms["canvasWidthHeight"], [
113969
+ this.rectShader.use(this.gl);
113970
+ this.gl.uniform4fv(this.rectShader.uniforms["lineColor"], color);
113971
+ this.gl.uniform2fv(this.rectShader.uniforms["canvasWidthHeight"], [
112389
113972
  this.gl.canvas.width,
112390
113973
  this.gl.canvas.height
112391
113974
  ]);
112392
- this.gl.uniform4f(this.lineShader.uniforms["leftTopWidthHeight"], LTWH[0], LTWH[1], LTWH[2], LTWH[3]);
113975
+ this.gl.uniform4f(this.rectShader.uniforms["leftTopWidthHeight"], LTWH[0], LTWH[1], LTWH[2], LTWH[3]);
112393
113976
  this.gl.bindVertexArray(this.genericVAO);
112394
113977
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
112395
113978
  this.gl.bindVertexArray(this.unusedVAO);
112396
113979
  };
112397
- function tickSpacing(tickCount, mn, mx) {
112398
- let range = Math.abs(mx - mn);
112399
- if (range === 0)
112400
- return [0, 0];
112401
- let unroundedTickSize = range / (tickCount - 1);
112402
- let x2 = Math.ceil(Math.log10(unroundedTickSize) - 1);
112403
- let pow10x = Math.pow(10, x2);
112404
- let spacing = Math.ceil(unroundedTickSize / pow10x) * pow10x;
112405
- let ticMin = mn;
112406
- if (mn / spacing % 1 !== 0)
112407
- ticMin = Math.sign(ticMin) * Math.round(Math.abs(ticMin));
112408
- return [spacing, ticMin];
112409
- }
112410
113980
  Niivue.prototype.drawGraph = function() {
112411
113981
  this.gl;
112412
113982
  let graph = this.graph;
112413
113983
  if (this.graph.autoSizeMultiplanar && this.sliceType === this.sliceTypeMultiplanar) {
112414
- for (let i2 = 0; i2 < this.numScreenSlices; i2++) {
113984
+ for (let i2 = 0; i2 < this.screenSlices.length; i2++) {
112415
113985
  var axCorSag = this.screenSlices[i2].axCorSag;
112416
113986
  if (axCorSag !== this.sliceTypeSagittal)
112417
113987
  continue;
@@ -112504,7 +114074,7 @@ Niivue.prototype.drawGraph = function() {
112504
114074
  graph.backColor[3]
112505
114075
  ];
112506
114076
  this.drawRect(graph.LTWH, borderColor);
112507
- let [spacing, ticMin] = tickSpacing(5, mn, mx);
114077
+ let [spacing, ticMin] = tickSpacing(mn, mx);
112508
114078
  function humanize(x2) {
112509
114079
  return x2.toFixed(6).replace(/\.?0*$/, "");
112510
114080
  }
@@ -112605,21 +114175,54 @@ Niivue.prototype.drawGraph = function() {
112605
114175
  this.drawGraphLine([x2, plotLTWH[1], x2, plotLTWH[1] + plotLTWH[3]], [graph.lineRGB[3][0], graph.lineRGB[3][1], graph.lineRGB[3][2], 1], graph.lineThickness);
112606
114176
  }
112607
114177
  };
112608
- Niivue.prototype.draw3D = function() {
114178
+ function isRadiological(mtx) {
114179
+ let vRight = fromValues(1, 0, 0, 0);
114180
+ let vRotated = create();
114181
+ transformMat4(vRotated, vRight, mtx);
114182
+ return vRotated[0];
114183
+ }
114184
+ Niivue.prototype.draw3D = function(leftTopWidthHeight = [0, 0, 0, 0], mvpMatrix = null, modelMatrix, normalMatrix, azimuth = null, elevation) {
114185
+ let isMosaic = azimuth !== null;
112609
114186
  this.setPivot3D();
114187
+ if (!isMosaic) {
114188
+ azimuth = this.scene.renderAzimuth;
114189
+ elevation = this.scene.renderElevation;
114190
+ }
112610
114191
  let gl = this.gl;
112611
- gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
114192
+ if (mvpMatrix === null)
114193
+ [mvpMatrix, modelMatrix, normalMatrix] = this.calculateMvpMatrix(null, leftTopWidthHeight, azimuth, elevation);
114194
+ if (leftTopWidthHeight[2] === 0 || leftTopWidthHeight[3] === 0) {
114195
+ leftTopWidthHeight = [0, 0, gl.canvas.width, gl.canvas.height];
114196
+ this.screenSlices.push({
114197
+ leftTopWidthHeight,
114198
+ axCorSag: this.sliceTypeRender,
114199
+ sliceFrac: 0,
114200
+ AxyzMxy: [],
114201
+ leftTopMM: [],
114202
+ fovMM: [isRadiological(modelMatrix), 0]
114203
+ });
114204
+ } else {
114205
+ this.screenSlices.push({
114206
+ leftTopWidthHeight: leftTopWidthHeight.slice(),
114207
+ axCorSag: this.sliceTypeRender,
114208
+ sliceFrac: 0,
114209
+ AxyzMxy: [],
114210
+ leftTopMM: [],
114211
+ fovMM: [isRadiological(modelMatrix), 0]
114212
+ });
114213
+ leftTopWidthHeight[1] = gl.canvas.height - leftTopWidthHeight[3] - leftTopWidthHeight[1];
114214
+ }
114215
+ gl.viewport(leftTopWidthHeight[0], leftTopWidthHeight[1], leftTopWidthHeight[2], leftTopWidthHeight[3]);
112612
114216
  gl.enable(gl.DEPTH_TEST);
112613
114217
  gl.depthFunc(gl.ALWAYS);
112614
114218
  gl.clearDepth(0);
112615
114219
  if (this.volumes.length === 0) {
112616
114220
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
112617
- this.drawMesh3D(true, 1);
114221
+ this.drawMesh3D(true, 1, mvpMatrix, modelMatrix, normalMatrix);
114222
+ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
112618
114223
  return;
112619
114224
  }
112620
- let mvpMatrix, modelMatrix, normalMatrix;
112621
- [mvpMatrix, modelMatrix, normalMatrix] = this.calculateMvpMatrix(this.volumeObject3D);
112622
- const rayDir = this.calculateRayDirection();
114225
+ const rayDir = this.calculateRayDirection(azimuth, elevation);
112623
114226
  let object3D = this.volumeObject3D;
112624
114227
  if (this.scene.mouseDepthPicker) {
112625
114228
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
@@ -112637,8 +114240,8 @@ Niivue.prototype.draw3D = function() {
112637
114240
  gl.drawElements(object3D.mode, object3D.indexCount, gl.UNSIGNED_SHORT, 0);
112638
114241
  gl.bindVertexArray(this.unusedVAO);
112639
114242
  }
112640
- const pixelX = this.mousePos[0] * gl.canvas.width / gl.canvas.clientWidth;
112641
- const pixelY = gl.canvas.height - this.mousePos[1] * gl.canvas.height / gl.canvas.clientHeight - 1;
114243
+ const pixelX = this.mousePos[0] * leftTopWidthHeight[2] / leftTopWidthHeight[2];
114244
+ const pixelY = gl.canvas.height - this.mousePos[1] * leftTopWidthHeight[3] / leftTopWidthHeight[3] - 1;
112642
114245
  const rgbaPixel = new Uint8Array(4);
112643
114246
  gl.readPixels(pixelX, pixelY, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, rgbaPixel);
112644
114247
  this.selectedObjectId = rgbaPixel[3];
@@ -112662,22 +114265,25 @@ Niivue.prototype.draw3D = function() {
112662
114265
  gl.drawElements(object3D.mode, object3D.indexCount, gl.UNSIGNED_SHORT, 0);
112663
114266
  gl.bindVertexArray(this.unusedVAO);
112664
114267
  }
112665
- this.drawCrosshairs3D(true, 1);
112666
- this.drawMesh3D(true, 1);
112667
- this.drawMesh3D(false, 0.02);
112668
- this.drawCrosshairs3D(false, 0.15);
114268
+ if (!isMosaic)
114269
+ this.drawCrosshairs3D(true, 1, mvpMatrix);
114270
+ this.drawMesh3D(true, 1, mvpMatrix, modelMatrix, normalMatrix);
114271
+ this.drawMesh3D(false, 0.02, mvpMatrix, modelMatrix, normalMatrix);
114272
+ if (!isMosaic)
114273
+ this.drawCrosshairs3D(false, 0.15, mvpMatrix);
114274
+ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
112669
114275
  let posString = "azimuth: " + this.scene.renderAzimuth.toFixed(0) + " elevation: " + this.scene.renderElevation.toFixed(0);
112670
114276
  this.drawGraph();
112671
114277
  this.readyForSync = true;
112672
114278
  this.sync();
112673
114279
  return posString;
112674
114280
  };
112675
- Niivue.prototype.drawMesh3D = function(isDepthTest = true, alpha = 1) {
114281
+ Niivue.prototype.drawMesh3D = function(isDepthTest = true, alpha = 1, m, modelMtx, normMtx) {
112676
114282
  if (this.meshes.length < 1)
112677
114283
  return;
112678
114284
  let gl = this.gl;
112679
- let m, modelMtx, normMtx;
112680
- [m, modelMtx, normMtx] = this.calculateMvpMatrix(this.volumeObject3D);
114285
+ if (!m)
114286
+ [m, modelMtx, normMtx] = this.calculateMvpMatrix(this.volumeObject3D, this.scene.renderAzimuth, this.scene.renderElevation);
112681
114287
  gl.enable(gl.DEPTH_TEST);
112682
114288
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
112683
114289
  gl.disable(gl.BLEND);
@@ -112729,7 +114335,7 @@ Niivue.prototype.drawMesh3D = function(isDepthTest = true, alpha = 1) {
112729
114335
  gl.depthFunc(gl.ALWAYS);
112730
114336
  this.readyForSync = true;
112731
114337
  };
112732
- Niivue.prototype.drawCrosshairs3D = function(isDepthTest = true, alpha = 1) {
114338
+ Niivue.prototype.drawCrosshairs3D = function(isDepthTest = true, alpha = 1, mvpMtx = null) {
112733
114339
  if (!this.opts.show3Dcrosshair)
112734
114340
  return;
112735
114341
  let gl = this.gl;
@@ -112739,15 +114345,16 @@ Niivue.prototype.drawCrosshairs3D = function(isDepthTest = true, alpha = 1) {
112739
114345
  gl.deleteBuffer(this.crosshairs3D.indexBuffer);
112740
114346
  gl.deleteBuffer(this.crosshairs3D.vertexBuffer);
112741
114347
  }
112742
- let radius = Math.min(Math.min(this.back.pixDims[1], this.back.pixDims[2]), this.back.pixDims[3]);
114348
+ let radius = 0.5 * Math.min(Math.min(this.back.pixDims[1], this.back.pixDims[2]), this.back.pixDims[3]);
112743
114349
  this.crosshairs3D = NiivueObject3D.generateCrosshairs(this.gl, 1, mm, this.volumeObject3D.extentsMin, this.volumeObject3D.extentsMax, radius);
112744
114350
  this.crosshairs3D.mm = mm;
112745
114351
  }
112746
114352
  let crosshairsShader = this.surfaceShader;
112747
114353
  crosshairsShader.use(this.gl);
112748
- let m, modelMtx, normMtx;
112749
- [m, modelMtx, normMtx] = this.calculateMvpMatrix(this.crosshairs3D);
112750
- gl.uniformMatrix4fv(crosshairsShader.uniforms["mvpMtx"], false, m);
114354
+ let modelMtx, normMtx;
114355
+ if (mvpMtx == null)
114356
+ [mvpMtx, modelMtx, normMtx] = this.calculateMvpMatrix(this.crosshairs3D, this.scene.renderAzimuth, this.scene.renderElevation);
114357
+ gl.uniformMatrix4fv(crosshairsShader.uniforms["mvpMtx"], false, mvpMtx);
112751
114358
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.crosshairs3D.indexBuffer);
112752
114359
  gl.enable(gl.DEPTH_TEST);
112753
114360
  let color = [...this.opts.crosshairColor];
@@ -112809,69 +114416,77 @@ Niivue.prototype.moveCrosshairInVox = function(x2, y, z) {
112809
114416
  };
112810
114417
  Niivue.prototype.frac2mm = function(frac, volIdx = 0) {
112811
114418
  let pos = fromValues(frac[0], frac[1], frac[2], 1);
112812
- let dim = fromValues(this.volumes[volIdx].dimsRAS[1], this.volumes[volIdx].dimsRAS[2], this.volumes[volIdx].dimsRAS[3], 1);
112813
- let sform = clone$1(this.volumes[volIdx].matRAS);
112814
- transpose(sform, sform);
112815
- mul(pos, pos, dim);
112816
- let shim = fromValues(-0.5, -0.5, -0.5, 0);
112817
- add(pos, pos, shim);
112818
- transformMat4(pos, pos, sform);
114419
+ transformMat4(pos, pos, this.volumes[volIdx].frac2mm);
112819
114420
  return pos;
112820
114421
  };
112821
- Niivue.prototype.canvasPos2frac = function(canvasPos) {
112822
- let x2, y, z;
112823
- for (let i2 = 0; i2 < this.numScreenSlices; i2++) {
112824
- var axCorSag = this.screenSlices[i2].axCorSag;
112825
- if (axCorSag > this.sliceTypeSagittal)
112826
- continue;
112827
- var ltwh = this.screenSlices[i2].leftTopWidthHeight;
112828
- let isMirror = false;
112829
- if (ltwh[2] < 0) {
112830
- isMirror = true;
112831
- ltwh[0] += ltwh[2];
112832
- ltwh[2] = -ltwh[2];
112833
- }
112834
- var fracX = (canvasPos[0] - ltwh[0]) / ltwh[2];
112835
- if (isMirror) {
112836
- fracX = 1 - fracX;
112837
- }
112838
- var fracY = 1 - (canvasPos[1] - ltwh[1]) / ltwh[3];
112839
- if (fracX >= 0 && fracX < 1 && fracY >= 0 && fracY < 1) {
112840
- switch (axCorSag) {
112841
- case this.sliceTypeAxial:
112842
- break;
112843
- case this.sliceTypeCoronal:
112844
- break;
112845
- }
112846
- if (axCorSag === this.sliceTypeAxial) {
112847
- x2 = fracX;
112848
- y = fracY;
112849
- z = this.scene.crosshairPos[2];
112850
- }
112851
- if (axCorSag === this.sliceTypeCoronal) {
112852
- x2 = fracX;
112853
- y = this.scene.crosshairPos[1];
112854
- z = fracY;
112855
- }
112856
- if (axCorSag === this.sliceTypeSagittal) {
112857
- x2 = this.scene.crosshairPos[0];
112858
- y = fracX;
112859
- z = fracY;
112860
- }
112861
- break;
114422
+ Niivue.prototype.screenXY2TextureFrac = function(x2, y, i2) {
114423
+ let texFrac = [-1, -1, -1];
114424
+ var axCorSag = this.screenSlices[i2].axCorSag;
114425
+ if (axCorSag > this.sliceTypeSagittal)
114426
+ return texFrac;
114427
+ var ltwh = this.screenSlices[i2].leftTopWidthHeight.slice();
114428
+ let isMirror = false;
114429
+ if (ltwh[2] < 0) {
114430
+ isMirror = true;
114431
+ ltwh[0] += ltwh[2];
114432
+ ltwh[2] = -ltwh[2];
114433
+ }
114434
+ var fracX = (x2 - ltwh[0]) / ltwh[2];
114435
+ if (isMirror)
114436
+ fracX = 1 - fracX;
114437
+ var fracY = 1 - (y - ltwh[1]) / ltwh[3];
114438
+ if (fracX < 0 || fracX > 1 || fracY < 0 || fracY > 1)
114439
+ return texFrac;
114440
+ if (this.screenSlices[i2].AxyzMxy.length > 4) {
114441
+ let xyzMM = [0, 0, 0];
114442
+ xyzMM[0] = this.screenSlices[i2].leftTopMM[0] + fracX * this.screenSlices[i2].fovMM[0];
114443
+ xyzMM[1] = this.screenSlices[i2].leftTopMM[1] + fracY * this.screenSlices[i2].fovMM[1];
114444
+ let v = this.screenSlices[i2].AxyzMxy;
114445
+ xyzMM[2] = v[2] + v[4] * (xyzMM[1] - v[1]) - v[3] * (xyzMM[0] - v[0]);
114446
+ if (axCorSag === this.sliceTypeCoronal)
114447
+ xyzMM = swizzleVec3(xyzMM, [0, 2, 1]);
114448
+ if (axCorSag === this.sliceTypeSagittal)
114449
+ xyzMM = swizzleVec3(xyzMM, [2, 0, 1]);
114450
+ let xyz = this.mm2frac(xyzMM);
114451
+ if (xyz[0] < 0 || xyz[0] > 1 || xyz[1] < 0 || xyz[1] > 1 || xyz[2] < 0 || xyz[2] > 1)
114452
+ return texFrac;
114453
+ texFrac[0] = xyz[0];
114454
+ texFrac[1] = xyz[1];
114455
+ texFrac[2] = xyz[2];
114456
+ } else {
114457
+ texFrac = this.scene.crosshairPos.slice();
114458
+ if (axCorSag === this.sliceTypeAxial) {
114459
+ texFrac[0] = fracX;
114460
+ texFrac[1] = fracY;
114461
+ }
114462
+ if (axCorSag === this.sliceTypeCoronal) {
114463
+ texFrac[0] = fracX;
114464
+ texFrac[2] = fracY;
114465
+ }
114466
+ if (axCorSag === this.sliceTypeSagittal) {
114467
+ texFrac[1] = fracX;
114468
+ texFrac[2] = fracY;
112862
114469
  }
112863
114470
  }
112864
- return [x2, y, z];
114471
+ return texFrac;
114472
+ };
114473
+ Niivue.prototype.canvasPos2frac = function(canvasPos) {
114474
+ for (let i2 = 0; i2 < this.screenSlices.length; i2++) {
114475
+ let texFrac = this.screenXY2TextureFrac(canvasPos[0], canvasPos[1], i2);
114476
+ if (texFrac[0] >= 0)
114477
+ return texFrac;
114478
+ }
114479
+ return [-1, -1, -1];
112865
114480
  };
112866
114481
  Niivue.prototype.scaleSlice = function(w, h) {
112867
114482
  let scalePix = this.gl.canvas.clientWidth / w;
112868
- if (h * scalePix > this.gl.canvas.clientHeight)
112869
- scalePix = this.gl.canvas.clientHeight / h;
114483
+ if (h * scalePix > this.effectiveCanvasHeight())
114484
+ scalePix = this.effectiveCanvasHeight() / h;
112870
114485
  let wPix = w * scalePix;
112871
114486
  let hPix = h * scalePix;
112872
114487
  let leftTopWidthHeight = [
112873
114488
  (this.gl.canvas.clientWidth - wPix) * 0.5,
112874
- (this.gl.canvas.clientHeight - hPix) * 0.5,
114489
+ (this.effectiveCanvasHeight() - hPix) * 0.5,
112875
114490
  wPix,
112876
114491
  hPix
112877
114492
  ];
@@ -112891,10 +114506,324 @@ Niivue.prototype.drawThumbnail = function() {
112891
114506
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
112892
114507
  this.gl.bindVertexArray(this.unusedVAO);
112893
114508
  };
114509
+ Niivue.prototype.drawLine = function(leftTopWidthHeight) {
114510
+ this.gl.enable(this.gl.BLEND);
114511
+ this.rectShader.use(this.gl);
114512
+ this.gl.uniform4fv(this.rectShader.uniforms["lineColor"], this.opts.crosshairColor);
114513
+ this.gl.uniform2fv(this.rectShader.uniforms["canvasWidthHeight"], [
114514
+ this.gl.canvas.width,
114515
+ this.gl.canvas.height
114516
+ ]);
114517
+ this.gl.uniform4f(this.rectShader.uniforms["leftTopWidthHeight"], leftTopWidthHeight[0], leftTopWidthHeight[1], leftTopWidthHeight[2], leftTopWidthHeight[3]);
114518
+ this.gl.bindVertexArray(this.genericVAO);
114519
+ this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
114520
+ this.gl.bindVertexArray(this.unusedVAO);
114521
+ };
114522
+ Niivue.prototype.drawLine2DX = function(startXYendXY, thickness = 1) {
114523
+ this.gl.bindVertexArray(this.genericVAO);
114524
+ this.lineShader.use(this.gl);
114525
+ this.gl.uniform4fv(this.lineShader.uniforms["lineColor"], this.opts.crosshairColor);
114526
+ this.gl.uniform2fv(this.lineShader.uniforms["canvasWidthHeight"], [
114527
+ this.gl.canvas.width,
114528
+ this.gl.canvas.height
114529
+ ]);
114530
+ this.gl.uniform1f(this.lineShader.uniforms["thickness"], thickness);
114531
+ this.gl.uniform4fv(this.lineShader.uniforms["startXYendXY"], startXYendXY);
114532
+ this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
114533
+ this.gl.bindVertexArray(this.unusedVAO);
114534
+ };
114535
+ Niivue.prototype.drawCrossLinesMM = function(sliceIndex, axCorSag, axiMM, corMM, sagMM) {
114536
+ if (sliceIndex < 0 || this.screenSlices.length <= sliceIndex)
114537
+ return;
114538
+ let tile = this.screenSlices[sliceIndex];
114539
+ let linesH = corMM.slice();
114540
+ let linesV = sagMM.slice();
114541
+ let thick = Math.max(1, this.opts.crosshairWidth);
114542
+ if (axCorSag === this.sliceTypeCoronal)
114543
+ linesH = axiMM.slice();
114544
+ if (axCorSag === this.sliceTypeSagittal) {
114545
+ linesH = axiMM.slice();
114546
+ linesV = corMM.slice();
114547
+ }
114548
+ function mm2screen(mm) {
114549
+ let screenXY = [0, 0];
114550
+ screenXY[0] = tile.leftTopWidthHeight[0] + (mm[0] - tile.leftTopMM[0]) / tile.fovMM[0] * tile.leftTopWidthHeight[2];
114551
+ screenXY[1] = tile.leftTopWidthHeight[1] + tile.leftTopWidthHeight[3] - (mm[1] - tile.leftTopMM[1]) / tile.fovMM[1] * tile.leftTopWidthHeight[3];
114552
+ return screenXY;
114553
+ }
114554
+ if (linesH.length > 0 && axCorSag === 0) {
114555
+ let fracZ = tile.sliceFrac;
114556
+ let dimV = 1;
114557
+ for (let i2 = 0; i2 < linesH.length; i2++) {
114558
+ let mmV = this.frac2mm([0.5, 0.5, 0.5]);
114559
+ mmV[dimV] = linesH[i2];
114560
+ let fracY = this.mm2frac(mmV);
114561
+ fracY = fracY[dimV];
114562
+ let left = this.frac2mm([0, fracY, fracZ]);
114563
+ left = swizzleVec3(left, [0, 1, 2]);
114564
+ let right = this.frac2mm([1, fracY, fracZ]);
114565
+ right = swizzleVec3(right, [0, 1, 2]);
114566
+ left = mm2screen(left);
114567
+ right = mm2screen(right);
114568
+ this.drawLine2DX([left[0], left[1], right[0], right[1]], thick);
114569
+ }
114570
+ }
114571
+ if (linesH.length > 0 && axCorSag === 1) {
114572
+ let fracH = tile.sliceFrac;
114573
+ this.frac2mm([0.5, fracH, 0.5]);
114574
+ let dimV = 2;
114575
+ for (let i2 = 0; i2 < linesH.length; i2++) {
114576
+ let mmV = this.frac2mm([0.5, 0.5, 0.5]);
114577
+ mmV[dimV] = linesH[i2];
114578
+ let fracV = this.mm2frac(mmV);
114579
+ fracV = fracV[dimV];
114580
+ let left = this.frac2mm([0, fracH, fracV]);
114581
+ left = swizzleVec3(left, [0, 2, 1]);
114582
+ let right = this.frac2mm([1, fracH, fracV]);
114583
+ right = swizzleVec3(right, [0, 2, 1]);
114584
+ left = mm2screen(left);
114585
+ right = mm2screen(right);
114586
+ this.drawLine2DX([left[0], left[1], right[0], right[1]], thick);
114587
+ }
114588
+ }
114589
+ if (linesH.length > 0 && axCorSag === 2) {
114590
+ let fracX = tile.sliceFrac;
114591
+ let dimV = 2;
114592
+ for (let i2 = 0; i2 < linesH.length; i2++) {
114593
+ let mmV = this.frac2mm([0.5, 0.5, 0.5]);
114594
+ mmV[dimV] = linesH[i2];
114595
+ let fracZ = this.mm2frac(mmV);
114596
+ fracZ = fracZ[dimV];
114597
+ let left = this.frac2mm([fracX, 0, fracZ]);
114598
+ left = swizzleVec3(left, [1, 2, 0]);
114599
+ let right = this.frac2mm([fracX, 1, fracZ]);
114600
+ right = swizzleVec3(right, [1, 2, 0]);
114601
+ left = mm2screen(left);
114602
+ right = mm2screen(right);
114603
+ this.drawLine2DX([left[0], left[1], right[0], right[1]], thick);
114604
+ }
114605
+ }
114606
+ if (linesV.length > 0 && axCorSag === 0) {
114607
+ let fracZ = tile.sliceFrac;
114608
+ let dimH = 0;
114609
+ for (let i2 = 0; i2 < linesV.length; i2++) {
114610
+ let mm = this.frac2mm([0.5, 0.5, 0.5]);
114611
+ mm[dimH] = linesV[i2];
114612
+ let frac = this.mm2frac(mm);
114613
+ frac = frac[dimH];
114614
+ let left = this.frac2mm([frac, 0, fracZ]);
114615
+ left = swizzleVec3(left, [0, 1, 2]);
114616
+ let right = this.frac2mm([frac, 1, fracZ]);
114617
+ right = swizzleVec3(right, [0, 1, 2]);
114618
+ left = mm2screen(left);
114619
+ right = mm2screen(right);
114620
+ this.drawLine2DX([left[0], left[1], right[0], right[1]], thick);
114621
+ }
114622
+ }
114623
+ if (linesV.length > 0 && axCorSag === 1) {
114624
+ let fracY = tile.sliceFrac;
114625
+ let dimH = 0;
114626
+ for (let i2 = 0; i2 < linesV.length; i2++) {
114627
+ let mm = this.frac2mm([0.5, 0.5, 0.5]);
114628
+ mm[dimH] = linesV[i2];
114629
+ let frac = this.mm2frac(mm);
114630
+ frac = frac[dimH];
114631
+ let left = this.frac2mm([frac, fracY, 0]);
114632
+ left = swizzleVec3(left, [0, 2, 1]);
114633
+ let right = this.frac2mm([frac, fracY, 1]);
114634
+ right = swizzleVec3(right, [0, 2, 1]);
114635
+ left = mm2screen(left);
114636
+ right = mm2screen(right);
114637
+ this.drawLine2DX([left[0], left[1], right[0], right[1]], thick);
114638
+ }
114639
+ }
114640
+ if (linesV.length > 0 && axCorSag === 2) {
114641
+ let fracX = tile.sliceFrac;
114642
+ let dimH = 1;
114643
+ for (let i2 = 0; i2 < linesV.length; i2++) {
114644
+ let mm = this.frac2mm([0.5, 0.5, 0.5]);
114645
+ mm[dimH] = linesV[i2];
114646
+ let frac = this.mm2frac(mm);
114647
+ frac = frac[dimH];
114648
+ let left = this.frac2mm([fracX, frac, 0]);
114649
+ left = swizzleVec3(left, [1, 2, 0]);
114650
+ let right = this.frac2mm([fracX, frac, 1]);
114651
+ right = swizzleVec3(right, [1, 2, 0]);
114652
+ left = mm2screen(left);
114653
+ right = mm2screen(right);
114654
+ this.drawLine2DX([left[0], left[1], right[0], right[1]], thick);
114655
+ }
114656
+ }
114657
+ };
114658
+ Niivue.prototype.drawCrossLines = function(sliceIndex, axCorSag, axiMM, corMM, sagMM) {
114659
+ if (sliceIndex < 0 || this.screenSlices.length <= sliceIndex)
114660
+ return;
114661
+ if (this.opts.isSliceMM)
114662
+ return this.drawCrossLinesMM(sliceIndex, axCorSag, axiMM, corMM, sagMM);
114663
+ let tile = this.screenSlices[sliceIndex];
114664
+ let linesH = corMM.slice();
114665
+ let linesV = sagMM.slice();
114666
+ Math.max(1, this.opts.crosshairWidth);
114667
+ if (axCorSag === this.sliceTypeCoronal)
114668
+ linesH = axiMM.slice();
114669
+ if (axCorSag === this.sliceTypeSagittal) {
114670
+ linesH = axiMM.slice();
114671
+ linesV = corMM.slice();
114672
+ }
114673
+ if (linesH.length > 0) {
114674
+ let LTWH = tile.leftTopWidthHeight.slice();
114675
+ let sliceDim = 2;
114676
+ let mm = this.frac2mm([0.5, 0.5, 0.5]);
114677
+ for (let i2 = 0; i2 < linesH.length; i2++) {
114678
+ mm[sliceDim] = linesH[i2];
114679
+ let frac = this.mm2frac(mm);
114680
+ this.drawLine([
114681
+ LTWH[0],
114682
+ LTWH[1] + LTWH[3] - frac[sliceDim] * LTWH[3],
114683
+ LTWH[2],
114684
+ 1
114685
+ ]);
114686
+ }
114687
+ }
114688
+ if (linesV.length > 0) {
114689
+ let LTWH = tile.leftTopWidthHeight.slice();
114690
+ let isRadiolgical = tile.fovMM[0] < 0;
114691
+ let sliceDim = 0;
114692
+ if (axCorSag === this.sliceTypeSagittal)
114693
+ sliceDim = 1;
114694
+ let mm = this.frac2mm([0.5, 0.5, 0.5]);
114695
+ for (let i2 = 0; i2 < linesV.length; i2++) {
114696
+ mm[sliceDim] = linesV[i2];
114697
+ let frac = this.mm2frac(mm);
114698
+ if (isRadiolgical)
114699
+ this.drawLine([
114700
+ LTWH[0] + (LTWH[2] - frac[sliceDim] * LTWH[2]),
114701
+ LTWH[1],
114702
+ 1,
114703
+ LTWH[3]
114704
+ ]);
114705
+ else
114706
+ this.drawLine([
114707
+ LTWH[0] + frac[sliceDim] * LTWH[2],
114708
+ LTWH[1],
114709
+ 1,
114710
+ LTWH[3]
114711
+ ]);
114712
+ }
114713
+ }
114714
+ };
114715
+ Niivue.prototype.drawMosaic = function(mosaicStr) {
114716
+ if (this.volumes.length === 0) {
114717
+ console.log("Unable to draw mosaic until voxel-based image is loaded");
114718
+ return;
114719
+ }
114720
+ this.screenSlices = [];
114721
+ let fovRenderMM = this.screenFieldOfViewMM(this.sliceTypeAxial, true);
114722
+ let fovSliceMM = this.screenFieldOfViewMM(this.sliceTypeAxial);
114723
+ mosaicStr = mosaicStr.replaceAll(";", " ;").trim();
114724
+ let axiMM = [];
114725
+ let corMM = [];
114726
+ let sagMM = [];
114727
+ let items = mosaicStr.split(/\s+/);
114728
+ let scale2 = 1;
114729
+ let labelSize = this.opts.textHeight;
114730
+ for (let pass = 0; pass < 2; pass++) {
114731
+ let isRender = false;
114732
+ let isCrossLines = false;
114733
+ isRender = false;
114734
+ let rowHt = 0;
114735
+ let left = 0;
114736
+ let top = 0;
114737
+ let mxRowWid = 0;
114738
+ let isLabel = false;
114739
+ let axCorSag = this.sliceTypeAxial;
114740
+ for (let i2 = 0; i2 < items.length; i2++) {
114741
+ let item = items[i2];
114742
+ if (item.includes("X")) {
114743
+ isCrossLines = true;
114744
+ continue;
114745
+ }
114746
+ if (item.includes("L")) {
114747
+ isLabel = !item.includes("-");
114748
+ continue;
114749
+ }
114750
+ if (item.includes("V") || item.includes("H")) {
114751
+ i2++;
114752
+ continue;
114753
+ }
114754
+ if (item.includes("A"))
114755
+ axCorSag = this.sliceTypeAxial;
114756
+ if (item.includes("C"))
114757
+ axCorSag = this.sliceTypeCoronal;
114758
+ if (item.includes("S"))
114759
+ axCorSag = this.sliceTypeSagittal;
114760
+ if (item.includes("R"))
114761
+ isRender = true;
114762
+ if (item.includes(";")) {
114763
+ top += rowHt;
114764
+ mxRowWid = Math.max(mxRowWid, left);
114765
+ rowHt = 0;
114766
+ left = 0;
114767
+ }
114768
+ let sliceMM = parseFloat(item, NaN);
114769
+ if (isNaN(sliceMM))
114770
+ continue;
114771
+ let w = 0;
114772
+ let h = 0;
114773
+ let fov = fovSliceMM;
114774
+ if (isRender)
114775
+ fov = fovRenderMM;
114776
+ if (axCorSag === this.sliceTypeSagittal)
114777
+ w = fov[1];
114778
+ else
114779
+ w = fov[0];
114780
+ if (axCorSag === this.sliceTypeAxial)
114781
+ h = fov[1];
114782
+ else
114783
+ h = fov[2];
114784
+ if (pass === 0) {
114785
+ if (!isRender) {
114786
+ if (axCorSag === this.sliceTypeAxial)
114787
+ axiMM.push(sliceMM);
114788
+ if (axCorSag === this.sliceTypeCoronal)
114789
+ corMM.push(sliceMM);
114790
+ if (axCorSag === this.sliceTypeSagittal)
114791
+ sagMM.push(sliceMM);
114792
+ }
114793
+ } else {
114794
+ let ltwh = [scale2 * left, scale2 * top, scale2 * w, scale2 * h];
114795
+ this.opts.textHeight = isLabel ? labelSize : 0;
114796
+ if (isRender) {
114797
+ let inf = sliceMM < 0 ? -Infinity : Infinity;
114798
+ if (Object.is(sliceMM, -0))
114799
+ inf = -Infinity;
114800
+ this.draw2DMM(ltwh, axCorSag, inf, isLabel);
114801
+ } else
114802
+ this.draw2D(ltwh, axCorSag, sliceMM, isLabel);
114803
+ if (isCrossLines) {
114804
+ this.drawCrossLines(this.screenSlices.length - 1, axCorSag, axiMM, corMM, sagMM);
114805
+ }
114806
+ isRender = false;
114807
+ isCrossLines = false;
114808
+ }
114809
+ left += w;
114810
+ rowHt = Math.max(rowHt, h);
114811
+ }
114812
+ top += rowHt;
114813
+ mxRowWid = Math.max(mxRowWid, left);
114814
+ if (mxRowWid <= 0 || top <= 0)
114815
+ break;
114816
+ let scaleW = this.gl.canvas.width / mxRowWid;
114817
+ let scaleH = this.effectiveCanvasHeight() / top;
114818
+ scale2 = Math.min(scaleW, scaleH);
114819
+ }
114820
+ this.opts.textHeight = labelSize;
114821
+ };
112894
114822
  Niivue.prototype.drawScene = function() {
112895
114823
  if (!this.initialized) {
112896
114824
  return;
112897
114825
  }
114826
+ this.colorbarHeight = 0;
112898
114827
  this.gl.clearColor(this.opts.backColor[0], this.opts.backColor[1], this.opts.backColor[2], this.opts.backColor[3]);
112899
114828
  this.gl.clear(this.gl.COLOR_BUFFER_BIT);
112900
114829
  if (this.bmpTexture) {
@@ -112904,6 +114833,7 @@ Niivue.prototype.drawScene = function() {
112904
114833
  let posString = "";
112905
114834
  if (this.volumes.length === 0 || typeof this.volumes[0].dims === "undefined") {
112906
114835
  if (this.meshes.length > 0) {
114836
+ this.screenSlices = [];
112907
114837
  this.sliceType = this.sliceTypeRender;
112908
114838
  return this.draw3D();
112909
114839
  }
@@ -112912,64 +114842,90 @@ Niivue.prototype.drawScene = function() {
112912
114842
  }
112913
114843
  if (!this.back.hasOwnProperty("dims"))
112914
114844
  return;
112915
- if (this.sliceType === this.sliceTypeRender) {
112916
- if (this.isDragging && this.scene.clipPlaneDepthAziElev[0] < 1.8) {
112917
- let x2 = this.dragStart[0] - this.dragEnd[0];
112918
- let y = this.dragStart[1] - this.dragEnd[1];
112919
- let depthAziElev = this.dragClipPlaneStartDepthAziElev.slice();
112920
- depthAziElev[1] -= x2;
112921
- depthAziElev[1] = depthAziElev[1] % 360;
112922
- depthAziElev[2] += y;
112923
- if (depthAziElev[1] !== this.scene.clipPlaneDepthAziElev[1] || depthAziElev[2] !== this.scene.clipPlaneDepthAziElev[2]) {
112924
- this.scene.clipPlaneDepthAziElev = depthAziElev;
112925
- return this.setClipPlane(this.scene.clipPlaneDepthAziElev);
112926
- }
112927
- }
112928
- return this.draw3D();
114845
+ if (this.isDragging && this.scene.clipPlaneDepthAziElev[0] < 1.8 && this.inRenderTile(this.dragStart[0], this.dragStart[1]) >= 0) {
114846
+ let x2 = this.dragStart[0] - this.dragEnd[0];
114847
+ let y = this.dragStart[1] - this.dragEnd[1];
114848
+ let depthAziElev = this.dragClipPlaneStartDepthAziElev.slice();
114849
+ depthAziElev[1] -= x2;
114850
+ depthAziElev[1] = depthAziElev[1] % 360;
114851
+ depthAziElev[2] += y;
114852
+ if (depthAziElev[1] !== this.scene.clipPlaneDepthAziElev[1] || depthAziElev[2] !== this.scene.clipPlaneDepthAziElev[2]) {
114853
+ this.scene.clipPlaneDepthAziElev = depthAziElev;
114854
+ return this.setClipPlane(this.scene.clipPlaneDepthAziElev);
114855
+ }
114856
+ }
114857
+ if (this.sliceMosaicString.length < 1 && this.sliceType === this.sliceTypeRender) {
114858
+ this.screenSlices = [];
114859
+ this.draw3D();
114860
+ if (this.opts.isColorbar)
114861
+ this.drawColorbar();
114862
+ return;
112929
114863
  }
112930
- let { volScale } = this.sliceScale();
112931
- this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
112932
- this.numScreenSlices = 0;
112933
- if (this.sliceType === this.sliceTypeAxial) {
112934
- let leftTopWidthHeight = this.scaleSlice(volScale[0], volScale[1]);
112935
- this.draw2D(leftTopWidthHeight, 0);
112936
- } else if (this.sliceType === this.sliceTypeCoronal) {
112937
- let leftTopWidthHeight = this.scaleSlice(volScale[0], volScale[2]);
112938
- this.draw2D(leftTopWidthHeight, 1);
112939
- } else if (this.sliceType === this.sliceTypeSagittal) {
112940
- let leftTopWidthHeight = this.scaleSlice(volScale[1], volScale[2]);
112941
- this.draw2D(leftTopWidthHeight, 2);
114864
+ if (this.opts.isColorbar)
114865
+ this.reserveColorbarPanel();
114866
+ if (this.sliceMosaicString.length > 0) {
114867
+ this.drawMosaic(this.sliceMosaicString);
112942
114868
  } else {
112943
- let ltwh = this.scaleSlice(volScale[0] + volScale[1], volScale[1] + volScale[2]);
112944
- let wX = ltwh[2] * volScale[0] / (volScale[0] + volScale[1]);
112945
- let ltwh3x1 = this.scaleSlice(volScale[0] + volScale[0] + volScale[1], Math.max(volScale[1], volScale[2]));
112946
- let wX1 = ltwh3x1[2] * volScale[0] / (volScale[0] + volScale[0] + volScale[1]);
112947
- if (wX1 > wX && !this.graph.autoSizeMultiplanar) {
112948
- let pixScale = wX1 / volScale[0];
112949
- let hY1 = volScale[1] * pixScale;
112950
- let hZ1 = volScale[2] * pixScale;
112951
- this.draw2D([ltwh3x1[0], ltwh3x1[1], wX1, hY1], 0);
112952
- this.draw2D([ltwh3x1[0] + wX1, ltwh3x1[1], wX1, hZ1], 1);
112953
- this.draw2D([ltwh3x1[0] + wX1 + wX1, ltwh3x1[1], hY1, hZ1], 2);
114869
+ this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
114870
+ this.screenSlices = [];
114871
+ if (this.sliceType === this.sliceTypeAxial) {
114872
+ this.draw2D([0, 0, 0, 0], 0);
114873
+ } else if (this.sliceType === this.sliceTypeCoronal) {
114874
+ this.draw2D([0, 0, 0, 0], 1);
114875
+ } else if (this.sliceType === this.sliceTypeSagittal) {
114876
+ this.draw2D([0, 0, 0, 0], 2);
112954
114877
  } else {
112955
- let wY = ltwh[2] - wX;
112956
- let hY = ltwh[3] * volScale[1] / (volScale[1] + volScale[2]);
112957
- let hZ = ltwh[3] - hY;
112958
- this.draw2D([ltwh[0], ltwh[1] + hZ, wX, hY], 0);
112959
- this.draw2D([ltwh[0], ltwh[1], wX, hZ], 1);
112960
- this.draw2D([ltwh[0] + wX, ltwh[1], wY, hZ], 2);
112961
- var margin = this.opts.colorBarMargin * hY;
112962
- if (!this.graph.autoSizeMultiplanar) {
112963
- this.drawColorbar([
112964
- ltwh[0] + wX + margin,
112965
- ltwh[1] + hZ + margin,
112966
- wY - margin - margin,
112967
- hY * this.opts.colorbarHeight
112968
- ]);
114878
+ let { volScale, vox, longestAxis } = this.sliceScale();
114879
+ let ltwh = this.scaleSlice(volScale[0] + volScale[1], volScale[1] + volScale[2]);
114880
+ let wX = ltwh[2] * volScale[0] / (volScale[0] + volScale[1]);
114881
+ let ltwh3x1 = this.scaleSlice(volScale[0] + volScale[0] + volScale[1], Math.max(volScale[1], volScale[2]));
114882
+ let mx = Math.max(Math.max(volScale[1], volScale[2]), volScale[0]);
114883
+ let ltwh4x1 = this.scaleSlice(volScale[0] + volScale[0] + volScale[1] + mx, mx);
114884
+ let wX1 = ltwh3x1[2] * volScale[0] / (volScale[0] + volScale[0] + volScale[1]);
114885
+ if (wX1 > wX && !this.graph.autoSizeMultiplanar) {
114886
+ let pixScale = wX1 / volScale[0];
114887
+ let hY1 = volScale[1] * pixScale;
114888
+ let hZ1 = volScale[2] * pixScale;
114889
+ if (ltwh3x1[3] === ltwh4x1[3]) {
114890
+ ltwh3x1 = ltwh4x1;
114891
+ this.draw3D([
114892
+ ltwh3x1[0] + wX1 + wX1 + hY1,
114893
+ ltwh3x1[1],
114894
+ ltwh4x1[3],
114895
+ ltwh4x1[3]
114896
+ ]);
114897
+ }
114898
+ this.draw2D([ltwh3x1[0], ltwh3x1[1], wX1, hY1], 0);
114899
+ this.draw2D([ltwh3x1[0] + wX1, ltwh3x1[1], wX1, hZ1], 1);
114900
+ this.draw2D([ltwh3x1[0] + wX1 + wX1, ltwh3x1[1], hY1, hZ1], 2);
114901
+ } else {
114902
+ let wY = ltwh[2] - wX;
114903
+ let hY = ltwh[3] * volScale[1] / (volScale[1] + volScale[2]);
114904
+ let hZ = ltwh[3] - hY;
114905
+ this.draw2D([ltwh[0], ltwh[1] + hZ, wX, hY], 0);
114906
+ this.draw2D([ltwh[0], ltwh[1], wX, hZ], 1);
114907
+ this.draw2D([ltwh[0] + wX, ltwh[1], wY, hZ], 2);
114908
+ if (!this.graph.autoSizeMultiplanar)
114909
+ this.draw3D([ltwh[0] + wX, ltwh[1] + hZ, wY, hY]);
112969
114910
  }
112970
114911
  }
112971
114912
  }
112972
- if (this.isDragging && this.sliceType !== this.sliceTypeRender) {
114913
+ if (this.opts.isRuler)
114914
+ this.drawRuler();
114915
+ if (this.opts.isColorbar)
114916
+ this.drawColorbar();
114917
+ if (this.isDragging) {
114918
+ if (this.opts.isDragShowsMeasurementTool) {
114919
+ this.drawMeasurementTool([
114920
+ this.dragStart[0],
114921
+ this.dragStart[1],
114922
+ this.dragEnd[0],
114923
+ this.dragEnd[1]
114924
+ ]);
114925
+ return;
114926
+ }
114927
+ if (this.inRenderTile(this.dragStart[0], this.dragStart[1]) >= 0)
114928
+ return;
112973
114929
  let width = Math.abs(this.dragStart[0] - this.dragEnd[0]);
112974
114930
  let height = Math.abs(this.dragStart[1] - this.dragEnd[1]);
112975
114931
  this.drawSelectionBox([