@niivue/niivue 0.24.3 → 0.26.2

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/src/nvimage.js CHANGED
@@ -52,7 +52,8 @@ export function NVImage(
52
52
  ignoreZeroVoxels = false,
53
53
  visible = true,
54
54
  isDICOMDIR = false,
55
- useQFormNotSForm = false
55
+ useQFormNotSForm = false,
56
+ colorMapNegative = ""
56
57
  ) {
57
58
  // https://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1.h
58
59
  this.DT_NONE = 0;
@@ -84,7 +85,9 @@ export function NVImage(
84
85
  this.percentileFrac = percentileFrac;
85
86
  this.ignoreZeroVoxels = ignoreZeroVoxels;
86
87
  this.trustCalMinMax = trustCalMinMax;
88
+ this.colorMapNegative = colorMapNegative;
87
89
  this.visible = visible;
90
+ this.modulationImage = null;
88
91
  this.series = []; // for concatenating dicom images
89
92
 
90
93
  // Added to support zerosLike
@@ -381,7 +384,36 @@ export function NVImage(
381
384
  this.calMinMax();
382
385
  }
383
386
 
387
+ NVImage.prototype.computeObliqueAngle = function (mtx44) {
388
+ //https://github.com/afni/afni/blob/25e77d564f2c67ff480fa99a7b8e48ec2d9a89fc/src/thd_coords.c#L717
389
+ let mtx = mat4.clone(mtx44);
390
+ mat4.transpose(mtx, mtx44);
391
+ let dxtmp = Math.sqrt(mtx[0] * mtx[0] + mtx[1] * mtx[1] + mtx[2] * mtx[2]);
392
+ let xmax =
393
+ Math.max(Math.max(Math.abs(mtx[0]), Math.abs(mtx[1])), Math.abs(mtx[2])) /
394
+ dxtmp;
395
+ let dytmp = Math.sqrt(mtx[4] * mtx[4] + mtx[5] * mtx[5] + mtx[6] * mtx[6]);
396
+ let ymax =
397
+ Math.max(Math.max(Math.abs(mtx[4]), Math.abs(mtx[5])), Math.abs(mtx[6])) /
398
+ dytmp;
399
+ let dztmp = Math.sqrt(mtx[8] * mtx[8] + mtx[9] * mtx[9] + mtx[10] * mtx[10]);
400
+ let zmax =
401
+ Math.max(Math.max(Math.abs(mtx[8]), Math.abs(mtx[9])), Math.abs(mtx[10])) /
402
+ dztmp;
403
+ let fig_merit = Math.min(Math.min(xmax, ymax), zmax);
404
+ let oblique_angle = Math.abs((Math.acos(fig_merit) * 180.0) / 3.141592653);
405
+ if (oblique_angle > 0.01)
406
+ console.log(
407
+ "Warning voxels not aligned with world space: " +
408
+ oblique_angle +
409
+ " degrees from plumb.\n"
410
+ );
411
+ else oblique_angle = 0.0;
412
+ return oblique_angle;
413
+ };
414
+
384
415
  NVImage.prototype.calculateOblique = function () {
416
+ this.oblique_angle = this.computeObliqueAngle(this.matRAS);
385
417
  let LPI = this.vox2mm([0.0, 0.0, 0.0], this.matRAS);
386
418
  let X1mm = this.vox2mm([1.0 / this.pixDimsRAS[1], 0.0, 0.0], this.matRAS);
387
419
  let Y1mm = this.vox2mm([0.0, 1.0 / this.pixDimsRAS[2], 0.0], this.matRAS);
@@ -414,6 +446,28 @@ NVImage.prototype.calculateOblique = function () {
414
446
  let maxShear = Math.max(Math.max(XY, XZ), YZ);
415
447
  if (maxShear > 0.1)
416
448
  log.debug("Warning: shear detected (gantry tilt) of %f degrees", maxShear);
449
+ //compute a matrix to transform vectors from factional space to mm:
450
+ let dim = vec4.fromValues(
451
+ this.dimsRAS[1],
452
+ this.dimsRAS[2],
453
+ this.dimsRAS[3],
454
+ 1
455
+ );
456
+ let sform = mat4.clone(this.matRAS);
457
+ mat4.transpose(sform, sform);
458
+ let shim = vec4.fromValues(-0.5, -0.5, -0.5, 0); //bitmap with 5 voxels scaled 0..1, voxel centers are 0.1,0.3,0.5,0.7,0.9
459
+ mat4.translate(sform, sform, shim);
460
+ //mat.mat4.scale(sform, sform, dim);
461
+ sform[0] *= dim[0];
462
+ sform[1] *= dim[0];
463
+ sform[2] *= dim[0];
464
+ sform[4] *= dim[1];
465
+ sform[5] *= dim[1];
466
+ sform[6] *= dim[1];
467
+ sform[8] *= dim[2];
468
+ sform[9] *= dim[2];
469
+ sform[10] *= dim[2];
470
+ this.frac2mm = mat4.clone(sform);
417
471
  };
418
472
 
419
473
  NVImage.prototype.THD_daxes_to_NIFTI = function (
@@ -2206,6 +2260,7 @@ NVImage.loadFromUrl = async function ({
2206
2260
  percentileFrac = 0.02,
2207
2261
  ignoreZeroVoxels = false,
2208
2262
  visible = true,
2263
+ colorMapNegative = "",
2209
2264
  } = {}) {
2210
2265
  if (url === "") {
2211
2266
  throw Error("url must not be empty");
@@ -2251,7 +2306,10 @@ NVImage.loadFromUrl = async function ({
2251
2306
  trustCalMinMax,
2252
2307
  percentileFrac,
2253
2308
  ignoreZeroVoxels,
2254
- visible
2309
+ visible,
2310
+ false,
2311
+ false,
2312
+ colorMapNegative
2255
2313
  );
2256
2314
  } else {
2257
2315
  alert("Unable to load buffer properly from volume");
@@ -2589,27 +2647,23 @@ function getExtents(positions, forceOriginInVolume = true) {
2589
2647
  */
2590
2648
  NVImage.prototype.toNiivueObject3D = function (id, gl) {
2591
2649
  //cube has 8 vertices: left/right, posterior/anterior, inferior/superior
2592
- let LPI = this.vox2mm([0.0, 0.0, 0.0], this.matRAS);
2593
- //TODO: ray direction needs to be corrected for oblique rotations
2594
- let LAI = this.vox2mm([0.0, this.dimsRAS[2] - 1, 0.0], this.matRAS);
2595
- let LPS = this.vox2mm([0.0, 0.0, this.dimsRAS[3] - 1], this.matRAS);
2596
- let LAS = this.vox2mm(
2597
- [0.0, this.dimsRAS[2] - 1, this.dimsRAS[3] - 1],
2598
- this.matRAS
2599
- );
2600
- let RPI = this.vox2mm([this.dimsRAS[1] - 1, 0.0, 0.0], this.matRAS);
2601
- let RAI = this.vox2mm(
2602
- [this.dimsRAS[1] - 1, this.dimsRAS[2] - 1, 0.0],
2603
- this.matRAS
2604
- );
2605
- let RPS = this.vox2mm(
2606
- [this.dimsRAS[1] - 1, 0.0, this.dimsRAS[3] - 1],
2607
- this.matRAS
2608
- );
2609
- let RAS = this.vox2mm(
2610
- [this.dimsRAS[1] - 1, this.dimsRAS[2] - 1, this.dimsRAS[3] - 1],
2611
- this.matRAS
2612
- );
2650
+ //n.b. voxel coordinates are from VOXEL centers
2651
+ // add/subtract 0.5 to get full image field of view
2652
+ let L = -0.5;
2653
+ let P = -0.5;
2654
+ let I = -0.5;
2655
+ let R = this.dimsRAS[1] - 1 + 0.5;
2656
+ let A = this.dimsRAS[2] - 1 + 0.5;
2657
+ let S = this.dimsRAS[3] - 1 + 0.5;
2658
+
2659
+ let LPI = this.vox2mm([L, P, I], this.matRAS);
2660
+ let LAI = this.vox2mm([L, A, I], this.matRAS);
2661
+ let LPS = this.vox2mm([L, P, S], this.matRAS);
2662
+ let LAS = this.vox2mm([L, A, S], this.matRAS);
2663
+ let RPI = this.vox2mm([R, P, I], this.matRAS);
2664
+ let RAI = this.vox2mm([R, A, I], this.matRAS);
2665
+ let RPS = this.vox2mm([R, P, S], this.matRAS);
2666
+ let RAS = this.vox2mm([R, A, S], this.matRAS);
2613
2667
 
2614
2668
  let posTex = [
2615
2669
  //spatial position (XYZ), texture coordinates UVW
@@ -2712,7 +2766,6 @@ NVImage.prototype.toNiivueObject3D = function (id, gl) {
2712
2766
  indexBuffer,
2713
2767
  vao
2714
2768
  );
2715
-
2716
2769
  const extents = getExtents([
2717
2770
  ...LPS,
2718
2771
  ...RPS,
@@ -2723,10 +2776,56 @@ NVImage.prototype.toNiivueObject3D = function (id, gl) {
2723
2776
  ...RAI,
2724
2777
  ...RPI,
2725
2778
  ]);
2726
- obj3D.extentsMin = extents.min;
2727
- obj3D.extentsMax = extents.max;
2779
+ obj3D.extentsMin = extents.min.slice();
2780
+ obj3D.extentsMax = extents.max.slice();
2781
+
2728
2782
  obj3D.furthestVertexFromOrigin = extents.furthestVertexFromOrigin;
2729
2783
  obj3D.originNegate = vec3.clone(extents.origin);
2730
2784
  vec3.negate(obj3D.originNegate, obj3D.originNegate);
2785
+ /*
2786
+ let matDeOblique = mat4.fromValues(
2787
+ this.pixDimsRAS[1],
2788
+ 0,
2789
+ 0,
2790
+ this.matRAS[3],
2791
+ 0,
2792
+ this.pixDimsRAS[2],
2793
+ 0,
2794
+ this.matRAS[7],
2795
+ 0,
2796
+ 0,
2797
+ this.pixDimsRAS[3],
2798
+ this.matRAS[11],
2799
+ 0,
2800
+ 0,
2801
+ 0,
2802
+ 1
2803
+ );
2804
+ LPI = this.vox2mm([L,P,I], matDeOblique);
2805
+ LAI = this.vox2mm([L,A,I], matDeOblique);
2806
+ LPS = this.vox2mm([L,P,S], matDeOblique);
2807
+ LAS = this.vox2mm([L,A,S], matDeOblique);
2808
+ RPI = this.vox2mm([R,P,I], matDeOblique);
2809
+ RAI = this.vox2mm([R,A,I], matDeOblique);
2810
+ RPS = this.vox2mm([R,P,S], matDeOblique);
2811
+ RAS = this.vox2mm([R,A,S], matDeOblique);
2812
+ const extentsDeOblique = getExtents([
2813
+ ...LPS,
2814
+ ...RPS,
2815
+ ...RAS,
2816
+ ...LAS,
2817
+ ...LPI,
2818
+ ...LAI,
2819
+ ...RAI,
2820
+ ...RPI,
2821
+ ]);
2822
+ obj3D.extentsDeObliqueMin = extentsDeOblique.min.slice();
2823
+ obj3D.extentsDeObliqueMax = extentsDeOblique.max.slice();
2824
+ */
2825
+ obj3D.fieldOfViewDeObliqueMM = [
2826
+ this.dimsRAS[1] * this.pixDimsRAS[1],
2827
+ this.dimsRAS[2] * this.pixDimsRAS[2],
2828
+ this.dimsRAS[3] * this.pixDimsRAS[3],
2829
+ ];
2731
2830
  return obj3D;
2732
2831
  };
@@ -130,12 +130,13 @@ void main() {
130
130
  while (samplePos.a <= len) {
131
131
  vec4 colorSample = texture(volume, samplePos.xyz);
132
132
  samplePos += deltaDir; //advance ray position
133
- if (colorSample.a < 0.01) continue;
134
- if (firstHit.a > lenNoClip)
135
- firstHit = samplePos;
136
- backNearest = min(backNearest, samplePos.a);
137
- if (colorSample.a > colAcc.a) //ties generate errors for TT_desai_dd_mpm
138
- colAcc = vec4(colorSample.rgb, colorSample.a+0.00001);
133
+ if (colorSample.a >= 0.01) {
134
+ if (firstHit.a > lenNoClip)
135
+ firstHit = samplePos;
136
+ backNearest = min(backNearest, samplePos.a);
137
+ if (colorSample.a > colAcc.a) //ties generate errors for TT_desai_dd_mpm
138
+ colAcc = vec4(colorSample.rgb, colorSample.a+0.00001);
139
+ }
139
140
  }
140
141
  if (firstHit.a < len)
141
142
  gl_FragDepth = frac2ndc(firstHit.xyz);
@@ -171,15 +172,16 @@ void main() {
171
172
  while (samplePos.a <= len) {
172
173
  vec4 colorSample = texture(overlay, samplePos.xyz);
173
174
  samplePos += deltaDir; //advance ray position
174
- if (colorSample.a < 0.01) continue;
175
- if (overFirstHit.a > len)
176
- overFirstHit = samplePos;
177
- colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection);
178
- colorSample.rgb *= colorSample.a;
179
- colAcc= (1.0 - colAcc.a) * colorSample + colAcc;
180
- overFarthest = samplePos.a;
181
- if ( colAcc.a > earlyTermination )
182
- break;
175
+ if (colorSample.a >= 0.01) {
176
+ if (overFirstHit.a > len)
177
+ overFirstHit = samplePos;
178
+ colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection);
179
+ colorSample.rgb *= colorSample.a;
180
+ colAcc= (1.0 - colAcc.a) * colorSample + colAcc;
181
+ overFarthest = samplePos.a;
182
+ if ( colAcc.a > earlyTermination )
183
+ break;
184
+ }
183
185
  }
184
186
  if (overFirstHit.a < firstHit.a)
185
187
  //if (overFirstHit.a < len)
@@ -231,6 +233,10 @@ float frac2ndc(vec3 frac) {
231
233
 
232
234
  }
233
235
  void main() {
236
+ if (fColor.x > 2.0) {
237
+ fColor = vec4(1.0, 0.0, 0.0, 0.5);
238
+ return;
239
+ }
234
240
  fColor = vec4(0.0,0.0,0.0,0.0);
235
241
  vec4 clipPlaneColorX = clipPlaneColor;
236
242
  //if (clipPlaneColor.a < 0.0)
@@ -266,19 +272,23 @@ void main() {
266
272
  vec4 deltaDirFast = vec4(dir.xyz * stepSizeFast, stepSizeFast);
267
273
  while (samplePos.a <= len) {
268
274
  float val = texture(volume, samplePos.xyz).a;
269
- if (val > 0.01) break;
275
+ if (val > 0.01) {
276
+ break;
277
+ }
270
278
  samplePos += deltaDirFast; //advance ray position
271
279
  }
272
280
  if ((samplePos.a >= len) && ((overlays < 1.0) || (backgroundMasksOverlays > 0))) {
273
- if (isClip)
281
+ if (isClip){
274
282
  fColor += clipPlaneColorX;
283
+ }
275
284
  return;
276
285
  }
277
286
  fColor = vec4(1.0, 1.0, 1.0, 1.0);
278
287
  //gl_FragDepth = frac2ndc(samplePos.xyz); //crude due to fast pass resolution
279
288
  samplePos -= deltaDirFast;
280
- if (samplePos.a < 0.0)
289
+ if (samplePos.a < 0.0){
281
290
  vec4 samplePos = vec4(start.xyz, 0.0); //ray position
291
+ }
282
292
  //end: fast pass
283
293
  vec4 colAcc = vec4(0.0,0.0,0.0,0.0);
284
294
  vec4 firstHit = vec4(0.0,0.0,0.0,2.0 * lenNoClip);
@@ -289,25 +299,29 @@ void main() {
289
299
  while (samplePos.a <= len) {
290
300
  vec4 colorSample = texture(volume, samplePos.xyz);
291
301
  samplePos += deltaDir; //advance ray position
292
- if (colorSample.a < 0.01) continue;
293
- if (firstHit.a > lenNoClip)
294
- firstHit = samplePos;
295
- backNearest = min(backNearest, samplePos.a);
296
- colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection);
297
- colorSample.rgb *= colorSample.a;
298
- colAcc= (1.0 - colAcc.a) * colorSample + colAcc;
299
- if ( colAcc.a > earlyTermination )
300
- break;
302
+ if (colorSample.a >= 0.01) {
303
+ if (firstHit.a > lenNoClip)
304
+ firstHit = samplePos;
305
+ backNearest = min(backNearest, samplePos.a);
306
+ colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection);
307
+ colorSample.rgb *= colorSample.a;
308
+ colAcc= (1.0 - colAcc.a) * colorSample + colAcc;
309
+ if ( colAcc.a > earlyTermination )
310
+ break;
311
+ }
301
312
  }
302
- if (firstHit.a < len)
313
+ if (firstHit.a < len){
303
314
  gl_FragDepth = frac2ndc(firstHit.xyz);
315
+ }
304
316
  colAcc.a = (colAcc.a / earlyTermination) * backOpacity;
305
317
  fColor = colAcc;
306
318
  //if (isClip) //CR
307
319
  if ((isColorPlaneInVolume) && (clipPos.a != samplePos.a) && (abs(firstHit.a - clipPos.a) < deltaDir.a))
308
320
  fColor.rgb = mix(fColor.rgb, clipPlaneColorX.rgb, abs(clipPlaneColor.a));
309
321
  //fColor.rgb = mix(fColor.rgb, clipPlaneColorX.rgb, clipPlaneColorX.a * 0.65);
310
- if (overlays < 1.0) return;
322
+ if (overlays < 1.0) {
323
+ return;
324
+ }
311
325
  //overlay pass
312
326
  len = lenNoClip;
313
327
  samplePos = vec4(start.xyz, 0.0); //ray position
@@ -316,48 +330,56 @@ void main() {
316
330
  deltaDirFast = vec4(dir.xyz * stepSizeFast, stepSizeFast);
317
331
  while (samplePos.a <= len) {
318
332
  float val = texture(overlay, samplePos.xyz).a;
319
- if (val > 0.01) break;
333
+ if (val > 0.01) {
334
+ break;
335
+ }
320
336
  samplePos += deltaDirFast; //advance ray position
321
337
  }
322
338
  if (samplePos.a >= len) {
323
- if (isClip && (fColor.a == 0.0))
339
+ if (isClip && (fColor.a == 0.0)) {
324
340
  fColor += clipPlaneColorX;
341
+ }
325
342
  return;
326
343
  }
327
344
  samplePos -= deltaDirFast;
328
- if (samplePos.a < 0.0)
345
+ if (samplePos.a < 0.0){
329
346
  vec4 samplePos = vec4(start.xyz, 0.0); //ray position
347
+ }
330
348
  //end: fast pass
331
349
  float overFarthest = len;
332
350
  colAcc = vec4(0.0, 0.0, 0.0, 0.0);
333
351
  samplePos += deltaDir * ran; //jitter ray
334
352
  vec4 overFirstHit = vec4(0.0,0.0,0.0,2.0 * len);
335
- if (backgroundMasksOverlays > 0)
353
+ if (backgroundMasksOverlays > 0){
336
354
  samplePos = firstHit;
355
+ }
337
356
  while (samplePos.a <= len) {
338
357
  vec4 colorSample = texture(overlay, samplePos.xyz);
339
358
  samplePos += deltaDir; //advance ray position
340
- if (colorSample.a < 0.01) continue;
341
- if (overFirstHit.a > len)
342
- overFirstHit = samplePos;
343
- colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection);
344
- colorSample.rgb *= colorSample.a;
345
- colAcc= (1.0 - colAcc.a) * colorSample + colAcc;
346
- overFarthest = samplePos.a;
347
- if ( colAcc.a > earlyTermination )
348
- break;
359
+ if (colorSample.a >= 0.01) {
360
+ if (overFirstHit.a > len)
361
+ overFirstHit = samplePos;
362
+ colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection);
363
+ colorSample.rgb *= colorSample.a;
364
+ colAcc= (1.0 - colAcc.a) * colorSample + colAcc;
365
+ overFarthest = samplePos.a;
366
+ if ( colAcc.a > earlyTermination )
367
+ break;
368
+ }
349
369
  }
350
370
  if (samplePos.a >= len) {
351
- if (isClip && (fColor.a == 0.0))
371
+ if (isClip && (fColor.a == 0.0)){
352
372
  fColor += clipPlaneColorX;
373
+ }
353
374
  return;
354
375
  }
355
376
  //if (overFirstHit.a < len)
356
- gl_FragDepth = frac2ndc(overFirstHit.xyz);
377
+ gl_FragDepth = frac2ndc(overFirstHit.xyz);
357
378
  float overMix = colAcc.a;
358
379
  float overlayDepth = 0.3;
359
- if (fColor.a <= 0.0)
380
+ if (fColor.a <= 0.0){
360
381
  overMix = 1.0;
382
+ }
361
383
  else if (((overFarthest) > backNearest)) {
362
384
  float dx = (overFarthest - backNearest)/1.73;
363
385
  dx = fColor.a * pow(dx, overlayDepth);
@@ -373,15 +395,22 @@ layout(location=0) in vec3 pos;
373
395
  uniform int axCorSag;
374
396
  uniform float slice;
375
397
  uniform vec2 canvasWidthHeight;
398
+ uniform vec3 panXYscale;
376
399
  uniform vec4 leftTopWidthHeight;
377
400
  out vec3 texPos;
378
401
  void main(void) {
379
402
  //convert pixel x,y space 1..canvasWidth,1..canvasHeight to WebGL 1..-1,-1..1
380
403
  vec2 frac;
381
- frac.x = (leftTopWidthHeight.x + (pos.x * leftTopWidthHeight.z)) / canvasWidthHeight.x; //0..1
382
- frac.y = 1.0 - ((leftTopWidthHeight.y + ((1.0 - pos.y) * leftTopWidthHeight.w)) / canvasWidthHeight.y); //1..0
404
+ vec3 vpos = pos;
405
+ frac.x = (leftTopWidthHeight.x + (vpos.x * leftTopWidthHeight.z)) / canvasWidthHeight.x; //0..1
406
+ frac.y = 1.0 - ((leftTopWidthHeight.y + ((1.0 - vpos.y) * leftTopWidthHeight.w)) / canvasWidthHeight.y); //1..0
407
+ //frac.x = pos.x; //0..1
408
+ //frac.y = pos.y; //1..0
409
+
383
410
  frac = (frac * 2.0) - 1.0;
384
411
  gl_Position = vec4(frac, 0.0, 1.0);
412
+ gl_Position.x += panXYscale.x;
413
+ gl_Position.y += panXYscale.y;
385
414
  if (axCorSag == 1)
386
415
  texPos = vec3(pos.x, slice, pos.y);
387
416
  else if (axCorSag == 2)
@@ -432,7 +461,70 @@ void main() {
432
461
  color.a = aout;
433
462
  }`;
434
463
 
435
- export var fragLineShader = `#version 300 es
464
+ export var vertSliceMMShader = `#version 300 es
465
+ #line 4
466
+ layout(location=0) in vec3 pos;
467
+ uniform int axCorSag;
468
+ uniform mat4 mvpMtx;
469
+ uniform mat4 frac2mm;
470
+ uniform float slice;
471
+ out vec3 texPos;
472
+ void main(void) {
473
+ texPos = vec3(pos.x, pos.y, slice);
474
+ if (axCorSag > 1)
475
+ texPos = vec3(slice, pos.x, pos.y);
476
+ else if (axCorSag > 0)
477
+ texPos = vec3(pos.x, slice, pos.y);
478
+ vec4 mm = frac2mm * vec4(texPos, 1.0);
479
+ //vec4 mm = vec4(texPos, 1.0) * frac2mm;
480
+ gl_Position = mvpMtx * mm;
481
+ //gl_Position = mm;
482
+ }`;
483
+
484
+ export var fragSliceMMShader = `#version 300 es
485
+ #line 228
486
+ precision highp int;
487
+ precision highp float;
488
+ uniform highp sampler3D volume, overlay;
489
+ uniform int backgroundMasksOverlays;
490
+ uniform float overlays;
491
+ uniform float opacity;
492
+ uniform float drawOpacity;
493
+ uniform highp sampler3D drawing;
494
+ in vec3 texPos;
495
+ out vec4 color;
496
+ void main() {
497
+ //color = vec4(1.0, 0.0, 1.0, 1.0);return;
498
+ vec4 background = texture(volume, texPos);
499
+ color = vec4(background.rgb, opacity);
500
+ vec4 ocolor = vec4(0.0);
501
+ if (overlays > 0.0) {
502
+ ocolor = texture(overlay, texPos);
503
+ }
504
+ float draw = texture(drawing, texPos).r;
505
+ if (draw > 0.0) {
506
+ vec3 dcolor = vec3(0.0, 0.0, 0.0);
507
+ if (draw >= (4.0/255.0))
508
+ dcolor.rgb = vec3(draw,0.0,draw);
509
+ else if (draw >= (3.0/255.0))
510
+ dcolor.b = 1.0;
511
+ else if (draw >= (2.0/255.0))
512
+ dcolor.g = 1.0;
513
+ else
514
+ dcolor.r = 1.0;
515
+ color.rgb = mix(color.rgb, dcolor, drawOpacity);
516
+ color.a = max(drawOpacity, color.a);
517
+ }
518
+ if ((backgroundMasksOverlays > 0) && (background.a == 0.0))
519
+ return;
520
+ //float aout = ocolor.a + (1.0 - ocolor.a) * color.a;
521
+ //if (aout <= 0.0) return;
522
+ //color.rgb = ((ocolor.rgb * ocolor.a) + (color.rgb * color.a * (1.0 - ocolor.a))) / aout;
523
+ color.rgb = mix(color.rgb, ocolor.rgb, ocolor.a);
524
+ color.a = 1.0;
525
+ }`;
526
+
527
+ export var fragRectShader = `#version 300 es
436
528
  #line 189
437
529
  precision highp int;
438
530
  precision highp float;
@@ -463,10 +555,13 @@ export var fragColorbarShader = `#version 300 es
463
555
  precision highp int;
464
556
  precision highp float;
465
557
  uniform highp sampler2D colormap;
558
+ uniform float layer;
466
559
  in vec2 vColor;
467
560
  out vec4 color;
468
561
  void main() {
469
- color = vec4(texture(colormap, vColor).rgb, 1.0);
562
+ float nlayer = float(textureSize(colormap, 0).y);
563
+ float fmap = (0.5 + layer) / nlayer;
564
+ color = vec4(texture(colormap, vec2(vColor.x, fmap)).rgb, 1.0);
470
565
  }`;
471
566
 
472
567
  export var vertGraphShader = `#version 300 es
@@ -509,7 +604,7 @@ void main(void) {
509
604
  gl_Position = vec4(frac, 0.0, 1.0);
510
605
  }`;
511
606
 
512
- export var vertLineShader = `#version 300 es
607
+ export var vertRectShader = `#version 300 es
513
608
  #line 229
514
609
  layout(location=0) in vec3 pos;
515
610
  uniform vec2 canvasWidthHeight;
@@ -523,6 +618,21 @@ void main(void) {
523
618
  gl_Position = vec4(frac, 0.0, 1.0);
524
619
  }`;
525
620
 
621
+ export var vertLineShader = `#version 300 es
622
+ #line 229
623
+ layout(location=0) in vec3 pos;
624
+ uniform vec2 canvasWidthHeight;
625
+ uniform float thickness;
626
+ uniform vec4 startXYendXY;
627
+ void main(void) {
628
+ vec2 posXY = mix(startXYendXY.xy, startXYendXY.zw, pos.x);
629
+ vec2 dir = normalize(startXYendXY.xy - startXYendXY.zw);
630
+ posXY += vec2(-dir.y, dir.x) * thickness * (pos.y - 0.5);
631
+ posXY.x = (posXY.x) / canvasWidthHeight.x; //0..1
632
+ posXY.y = 1.0 - (posXY.y / canvasWidthHeight.y); //1..0
633
+ gl_Position = vec4((posXY * 2.0) - 1.0, 0.0, 1.0);
634
+ }`;
635
+
526
636
  export var vertBmpShader = `#version 300 es
527
637
  #line 229
528
638
  layout(location=0) in vec3 pos;
@@ -618,7 +728,7 @@ in vec2 TexCoord;
618
728
  out vec4 FragColor;
619
729
  uniform float coordZ;
620
730
  uniform float layer;
621
- uniform float numLayers;
731
+ //uniform float numLayers;
622
732
  uniform highp sampler2D colormap;
623
733
  uniform lowp sampler3D blend3D;
624
734
  uniform float opacity;
@@ -648,7 +758,9 @@ void main(void) {
648
758
  }
649
759
  idx = ((idx - uint(1)) % uint(100))+uint(1);
650
760
  float fx = (float(idx)+0.5) / 256.0;
651
- float y = (2.0 * layer + 1.0)/(2.0 * numLayers);
761
+ float nlayer = float(textureSize(colormap, 0).y) * 0.5; //0.5 as both each layer has positive and negative color slot
762
+ float y = (2.0 * layer + 1.0)/(4.0 * nlayer);
763
+ //float y = (2.0 * layer + 1.0)/(4.0 * numLayers);
652
764
  FragColor = texture(colormap, vec2(fx, y)).rgba;
653
765
  FragColor.a *= opacity;
654
766
  if (layer < 2.0) return;
@@ -668,28 +780,43 @@ in vec2 TexCoord;
668
780
  out vec4 FragColor;
669
781
  uniform float coordZ;
670
782
  uniform float layer;
671
- uniform float numLayers;
783
+ //uniform float numLayers;
672
784
  uniform float scl_slope;
673
785
  uniform float scl_inter;
674
786
  uniform float cal_max;
675
787
  uniform float cal_min;
676
788
  uniform highp sampler2D colormap;
677
789
  uniform lowp sampler3D blend3D;
790
+ uniform int modulation;
791
+ uniform highp sampler3D modulationVol;
678
792
  uniform float opacity;
679
793
  uniform mat4 mtx;
680
794
  void main(void) {
681
795
  vec4 vx = vec4(TexCoord.xy, coordZ, 1.0) * mtx;
682
796
  float f = (scl_slope * float(texture(intensityVol, vx.xyz).r)) + scl_inter;
797
+ bool isNegative = (f < 0.0);
683
798
  float r = max(0.00001, abs(cal_max - cal_min));
684
799
  float mn = min(cal_min, cal_max);
685
- f = mix(0.0, 1.0, (f - mn) / r);
686
- //float y = 1.0 / numLayers;
687
- //y = ((layer + 0.5) * y);
800
+ float txl = mix(0.0, 1.0, (f - mn) / r);
688
801
  //https://stackoverflow.com/questions/5879403/opengl-texture-coordinates-in-pixel-space
689
- float y = (2.0 * layer + 1.0)/(2.0 * numLayers);
690
- FragColor = texture(colormap, vec2(f, y)).rgba;
802
+ float nlayer = float(textureSize(colormap, 0).y) * 0.5; //0.5 as both each layer has positive and negative color slot
803
+ float y = (2.0 * layer + 1.0)/(4.0 * nlayer);
804
+ FragColor = texture(colormap, vec2(txl, y)).rgba;
805
+ if (isNegative) {
806
+ y = (2.0 * layer + nlayer + nlayer + 1.0)/(4.0 * nlayer);
807
+ //select texels at positions 0 and 1 of lookup table:
808
+ vec4 v0 = texture(colormap, vec2(0.5/256.0, y));
809
+ vec4 v1 = texture(colormap, vec2(1.5/256.0, y));
810
+ txl = mix(0.0, 1.0, (- f - mn) / r);
811
+ //detect bogus color: negative color slot not used than
812
+ // v0 = 1,1,1,0 and v1 = 0,0,0,1
813
+ if ((v0.r != 1.0) || (v0.a != 0.0) || (v1.r != 0.0) || (v1.a != 1.0))
814
+ FragColor = texture(colormap, vec2(txl, y));
815
+ }
691
816
  if (layer > 0.7)
692
817
  FragColor.a = step(0.00001, FragColor.a);
818
+ if (modulation > 0)
819
+ FragColor.rgb *= texture(modulationVol, vx.xyz).r;
693
820
  FragColor.a *= opacity;
694
821
  if (layer < 1.0) return;
695
822
  vec2 texXY = TexCoord.xy*0.5 +vec2(0.5,0.5);
@@ -708,7 +835,7 @@ in vec2 TexCoord;
708
835
  out vec4 FragColor;
709
836
  uniform float coordZ;
710
837
  uniform float layer;
711
- uniform float numLayers;
838
+ //uniform float numLayers;
712
839
  uniform float scl_slope;
713
840
  uniform float scl_inter;
714
841
  uniform float cal_max;
@@ -718,10 +845,14 @@ uniform lowp sampler3D blend3D;
718
845
  uniform float opacity;
719
846
  uniform mat4 mtx;
720
847
  uniform bool hasAlpha;
848
+ uniform int modulation;
849
+ uniform highp sampler3D modulationVol;
721
850
  void main(void) {
722
851
  vec4 vx = vec4(TexCoord.xy, coordZ, 1.0) * mtx;
723
852
  uvec4 aColor = texture(intensityVol, vx.xyz);
724
853
  FragColor = vec4(float(aColor.r) / 255.0, float(aColor.g) / 255.0, float(aColor.b) / 255.0, float(aColor.a) / 255.0);
854
+ if (modulation > 0)
855
+ FragColor.rgb *= texture(modulationVol, vx.xyz).r;
725
856
  if (!hasAlpha)
726
857
  FragColor.a = (FragColor.r * 0.21 + FragColor.g * 0.72 + FragColor.b * 0.07);
727
858
  FragColor.a *= opacity;
@@ -880,6 +1011,25 @@ void main(void) {
880
1011
  vClr = clr;
881
1012
  }`;
882
1013
 
1014
+ //report depth for fragment
1015
+ // https://github.com/rii-mango/Papaya/blob/782a19341af77a510d674c777b6da46afb8c65f1/src/js/viewer/screensurface.js#L89
1016
+ export var fragMeshDepthShader = `#version 300 es
1017
+ precision highp int;
1018
+ precision highp float;
1019
+ uniform float opacity;
1020
+ out vec4 color;
1021
+ vec4 packFloatToVec4i(const float value) {
1022
+ //this Papaya function uses BSD 3-clause license Copyright (c) 2012-2015, RII-UTHSCSA
1023
+ const vec4 bitSh = vec4(256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0);
1024
+ const vec4 bitMsk = vec4(0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0);
1025
+ vec4 res = fract(value * bitSh);
1026
+ res -= res.xxyz * bitMsk;
1027
+ return res;
1028
+ }
1029
+ void main() {
1030
+ color = packFloatToVec4i(gl_FragCoord.z);
1031
+ }`;
1032
+
883
1033
  //ToonShader https://prideout.net/blog/old/blog/index.html@tag=toon-shader.html
884
1034
  export var fragMeshToonShader = `#version 300 es
885
1035
  precision highp int;