@niivue/niivue 0.43.2 → 0.43.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.js CHANGED
@@ -13,7 +13,7 @@ var __publicField = (obj, key, value) => {
13
13
  import { mat4 as mat46, vec2, vec3 as vec311, vec4 as vec48 } from "gl-matrix";
14
14
 
15
15
  // package.json
16
- var version = "0.43.2";
16
+ var version = "0.43.4";
17
17
 
18
18
  // src/logger.ts
19
19
  var _Log = class _Log {
@@ -170,14 +170,16 @@ var kRenderFunc = `vec3 GetBackPosition(vec3 startPositionTex) {
170
170
  endPosition = endPosition / volScale;
171
171
  return endPosition;
172
172
  }
173
+
173
174
  vec4 applyClip (vec3 dir, inout vec4 samplePos, inout float len, inout bool isClip) {
174
175
  float cdot = dot(dir,clipPlane.xyz);
175
176
  isClip = false;
176
177
  if ((clipPlane.a > 1.0) || (cdot == 0.0)) return samplePos;
177
178
  bool frontface = (cdot > 0.0);
178
- float clipThick = 2.0;
179
179
  float dis = (-clipPlane.a - dot(clipPlane.xyz, samplePos.xyz-0.5)) / cdot;
180
- float disBackFace = (-(clipPlane.a-clipThick) - dot(clipPlane.xyz, samplePos.xyz-0.5)) / cdot;
180
+ float thick = clipThick;
181
+ if (thick <= 0.0) thick = 2.0;
182
+ float disBackFace = (-(clipPlane.a-thick) - dot(clipPlane.xyz, samplePos.xyz-0.5)) / cdot;
181
183
  if (((frontface) && (dis >= len)) || ((!frontface) && (dis <= 0.0))) {
182
184
  samplePos.a = len + 1.0;
183
185
  return samplePos;
@@ -196,6 +198,52 @@ vec4 applyClip (vec3 dir, inout vec4 samplePos, inout float len, inout bool isCl
196
198
  }
197
199
  return samplePos;
198
200
  }
201
+
202
+ void clipVolume(inout vec3 startPos, inout vec3 backPos, int dim, float frac, bool isLo) {
203
+ vec3 dir = backPos - startPos;
204
+ float len = length(dir);
205
+ dir = normalize(dir);
206
+ // Discard if both startPos and backPos are outside the clipping plane
207
+ if (isLo && startPos[dim] < frac && backPos[dim] < frac) {
208
+ discard;
209
+ }
210
+ if (!isLo && startPos[dim] > frac && backPos[dim] > frac) {
211
+ discard;
212
+ }
213
+ vec4 plane = vec4(0.0, 0.0, 0.0, 0.5 - frac);
214
+ plane[dim] = 1.0;
215
+ float cdot = dot(dir, plane.xyz);
216
+ float dis = (-plane.w - dot(plane.xyz, startPos - vec3(0.5))) / cdot;
217
+ // Adjust startPos or backPos based on the intersection with the plane
218
+ bool isFrontFace = (cdot > 0.0);
219
+ if (!isLo)
220
+ isFrontFace = !isFrontFace;
221
+ if (dis > 0.0) {
222
+ if (isFrontFace) {
223
+ if (dis <= len) {
224
+ startPos = startPos + dir * dis;
225
+ }
226
+ } else {
227
+ if (dis < len) {
228
+ backPos = startPos + dir * dis;
229
+ }
230
+ }
231
+ }
232
+ }
233
+
234
+ void clipVolumeStart (inout vec3 startPos, inout vec3 backPos) {
235
+ // vec3 clipLo = vec3(0.1, 0.2, 0.4);
236
+ // vec3 clipHi = vec3(0.8, 0.7, 0.7);
237
+ for (int i = 0; i < 3; i++) {
238
+ if (clipLo[i] > 0.0)
239
+ clipVolume(startPos, backPos, i, clipLo[i], true);
240
+ }
241
+ for (int i = 0; i < 3; i++) {
242
+ if (clipHi[i] < 1.0)
243
+ clipVolume(startPos, backPos, i, clipHi[i], false);
244
+ }
245
+ }
246
+
199
247
  float frac2ndc(vec3 frac) {
200
248
  //https://stackoverflow.com/questions/7777913/how-to-render-depth-linearly-in-modern-opengl-with-gl-fragcoord-z-in-fragment-sh
201
249
  vec4 pos = vec4(frac.xyz, 1.0); //fraction
@@ -207,144 +255,6 @@ float frac2ndc(vec3 frac) {
207
255
  float z_ndc = (mvpMtx * vec4(mm.xyz, 1.0)).z;
208
256
  return (z_ndc + 1.0) / 2.0;
209
257
  }` + kDrawFunc;
210
- var fragRenderShaderMIP = `#version 300 es
211
- #line 73
212
- precision highp int;
213
- precision highp float;
214
- uniform vec3 rayDir;
215
- uniform vec3 texVox;
216
- uniform vec3 volScale;
217
- uniform vec4 clipPlane;
218
- uniform highp sampler3D volume, overlay;
219
- uniform float overlays;
220
- uniform float backOpacity;
221
- uniform mat4 mvpMtx;
222
- uniform mat4 matRAS;
223
- uniform vec4 clipPlaneColor;
224
- uniform float renderOverlayBlend;
225
- uniform highp sampler3D drawing;
226
- uniform highp sampler2D colormap;
227
- in vec3 vColor;
228
- out vec4 fColor;
229
- ` + kRenderFunc + `void main() {
230
- fColor = vec4(0.0,0.0,0.0,0.0);
231
- //fColor = vec4(vColor.rgb, 1.0); return;
232
- vec3 start = vColor;
233
- gl_FragDepth = 0.0;
234
- vec3 backPosition = GetBackPosition(start);
235
- // fColor = vec4(backPosition, 1.0); return;
236
- vec3 dir = backPosition - start;
237
- float len = length(dir);
238
- float lenVox = length((texVox * start) - (texVox * backPosition));
239
- if ((lenVox < 0.5) || (len > 3.0)) { //length limit for parallel rays
240
- return;
241
- }
242
- float sliceSize = len / lenVox; //e.g. if ray length is 1.0 and traverses 50 voxels, each voxel is 0.02 in unit cube
243
- float stepSize = sliceSize; //quality: larger step is faster traversal, but fewer samples
244
- float opacityCorrection = stepSize/sliceSize;
245
- dir = normalize(dir);
246
- vec4 deltaDir = vec4(dir.xyz * stepSize, stepSize);
247
- vec4 samplePos = vec4(start.xyz, 0.0); //ray position
248
- float lenNoClip = len;
249
- bool isClip = false;
250
- vec4 clipPos = applyClip(dir, samplePos, len, isClip);
251
- //if ((clipPos.a != samplePos.a) && (len < 3.0)) {
252
- //start: OPTIONAL fast pass: rapid traversal until first hit
253
- float stepSizeFast = sliceSize * 1.9;
254
- vec4 deltaDirFast = vec4(dir.xyz * stepSizeFast, stepSizeFast);
255
- while (samplePos.a <= len) {
256
- float val = texture(volume, samplePos.xyz).a;
257
- if (val > 0.01) break;
258
- samplePos += deltaDirFast; //advance ray position
259
- }
260
- if ((samplePos.a >= len) && (overlays < 1.0)) {
261
- if (isClip)
262
- fColor += clipPlaneColor;
263
- return;
264
- }
265
- fColor = vec4(1.0, 1.0, 1.0, 1.0);
266
- //gl_FragDepth = frac2ndc(samplePos.xyz); //crude due to fast pass resolution
267
- samplePos -= deltaDirFast;
268
- if (samplePos.a < 0.0)
269
- vec4 samplePos = vec4(start.xyz, 0.0); //ray position
270
- //end: fast pass
271
- vec4 colAcc = vec4(0.0,0.0,0.0,0.0);
272
- vec4 firstHit = vec4(0.0,0.0,0.0,2.0 * lenNoClip);
273
- const float earlyTermination = 0.95;
274
- float backNearest = len; //assume no hit
275
- float ran = fract(sin(gl_FragCoord.x * 12.9898 + gl_FragCoord.y * 78.233) * 43758.5453);
276
- samplePos += deltaDir * ran; //jitter ray
277
- while (samplePos.a <= len) {
278
- vec4 colorSample = texture(volume, samplePos.xyz);
279
- samplePos += deltaDir; //advance ray position
280
- if (colorSample.a >= 0.01) {
281
- if (firstHit.a > lenNoClip)
282
- firstHit = samplePos;
283
- backNearest = min(backNearest, samplePos.a);
284
- if (colorSample.a > colAcc.a) //ties generate errors for TT_desai_dd_mpm
285
- colAcc = vec4(colorSample.rgb, colorSample.a+0.00001);
286
- }
287
- }
288
- if (firstHit.a < len)
289
- gl_FragDepth = frac2ndc(firstHit.xyz);
290
- colAcc.a *= backOpacity;
291
- fColor = colAcc;
292
- if (isClip) //CR
293
- fColor.rgb = mix(fColor.rgb, clipPlaneColor.rgb, clipPlaneColor.a * 0.15);
294
- if (overlays < 1.0) return;
295
- //overlay pass
296
- len = lenNoClip;
297
- samplePos = vec4(start.xyz, 0.0); //ray position
298
- //start: OPTIONAL fast pass: rapid traversal until first hit
299
- stepSizeFast = sliceSize * 1.9;
300
- deltaDirFast = vec4(dir.xyz * stepSizeFast, stepSizeFast);
301
- while (samplePos.a <= len) {
302
- float val = texture(overlay, samplePos.xyz).a;
303
- if (val > 0.01) break;
304
- samplePos += deltaDirFast; //advance ray position
305
- }
306
- if (samplePos.a >= len) {
307
- if (isClip && (fColor.a == 0.0))
308
- fColor += clipPlaneColor;
309
- return;
310
- }
311
- samplePos -= deltaDirFast;
312
- if (samplePos.a < 0.0)
313
- vec4 samplePos = vec4(start.xyz, 0.0); //ray position
314
- //end: fast pass
315
- float overFarthest = len;
316
- colAcc = vec4(0.0, 0.0, 0.0, 0.0);
317
- samplePos += deltaDir * ran; //jitter ray
318
- vec4 overFirstHit = vec4(0.0,0.0,0.0,2.0 * len);
319
- while (samplePos.a <= len) {
320
- vec4 colorSample = texture(overlay, samplePos.xyz);
321
- samplePos += deltaDir; //advance ray position
322
- if (colorSample.a >= 0.01) {
323
- if (overFirstHit.a > len)
324
- overFirstHit = samplePos;
325
- colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection);
326
- colorSample.rgb *= colorSample.a;
327
- colAcc= (1.0 - colAcc.a) * colorSample + colAcc;
328
- overFarthest = samplePos.a;
329
- if ( colAcc.a > earlyTermination )
330
- break;
331
- }
332
- }
333
- if (overFirstHit.a < firstHit.a)
334
- //if (overFirstHit.a < len)
335
- gl_FragDepth = frac2ndc(overFirstHit.xyz);
336
- float overMix = colAcc.a;
337
- float overlayDepth = 0.3;
338
- if (fColor.a <= 0.0)
339
- overMix = 1.0;
340
- else if (((overFarthest) > backNearest)) {
341
- float dx = (overFarthest - backNearest)/1.73;
342
- dx = fColor.a * pow(dx, overlayDepth);
343
- overMix *= 1.0 - dx;
344
- }
345
- fColor.rgb = mix(fColor.rgb, colAcc.rgb, overMix);
346
- fColor.a = max(fColor.a, colAcc.a);
347
- }`;
348
258
  var kRenderInit = `void main() {
349
259
  if (fColor.x > 2.0) {
350
260
  fColor = vec4(1.0, 0.0, 0.0, 0.5);
@@ -364,8 +274,10 @@ var kRenderInit = `void main() {
364
274
  gl_FragDepth = 0.0;
365
275
  vec3 backPosition = GetBackPosition(start);
366
276
  // fColor = vec4(backPosition, 1.0); return;
367
- vec3 dir = backPosition - start;
368
- float len = length(dir);
277
+ vec3 dir = normalize(backPosition - start);
278
+ clipVolumeStart(start, backPosition);
279
+ dir = normalize(dir);
280
+ float len = length(backPosition - start);
369
281
  float lenVox = length((texVox * start) - (texVox * backPosition));
370
282
  if ((lenVox < 0.5) || (len > 3.0)) { //length limit for parallel rays
371
283
  return;
@@ -373,7 +285,6 @@ var kRenderInit = `void main() {
373
285
  float sliceSize = len / lenVox; //e.g. if ray length is 1.0 and traverses 50 voxels, each voxel is 0.02 in unit cube
374
286
  float stepSize = sliceSize; //quality: larger step is faster traversal, but fewer samples
375
287
  float opacityCorrection = stepSize/sliceSize;
376
- dir = normalize(dir);
377
288
  vec4 deltaDir = vec4(dir.xyz * stepSize, stepSize);
378
289
  vec4 samplePos = vec4(start.xyz, 0.0); //ray position
379
290
  float lenNoClip = len;
@@ -545,6 +456,9 @@ uniform vec3 volScale;
545
456
  uniform vec4 clipPlane;
546
457
  uniform highp sampler3D volume, overlay;
547
458
  uniform float overlays;
459
+ uniform float clipThick;
460
+ uniform vec3 clipLo;
461
+ uniform vec3 clipHi;
548
462
  uniform float backOpacity;
549
463
  uniform mat4 mvpMtx;
550
464
  uniform mat4 matRAS;
@@ -560,8 +474,9 @@ out vec4 fColor;
560
474
  vec3 start = vColor;
561
475
  gl_FragDepth = 0.0;
562
476
  vec3 backPosition = GetBackPosition(start);
563
- vec3 dir = backPosition - start;
564
- float len = length(dir);
477
+ vec3 dir = normalize(backPosition - start);
478
+ clipVolumeStart(start, backPosition);
479
+ float len = length(backPosition - start);
565
480
  float lenVox = length((texVox * start) - (texVox * backPosition));
566
481
  if ((lenVox < 0.5) || (len > 3.0)) { //length limit for parallel rays
567
482
  fColor = vec4(0.0,0.0,0.0,0.0);
@@ -570,7 +485,6 @@ out vec4 fColor;
570
485
  float sliceSize = len / lenVox; //e.g. if ray length is 1.0 and traverses 50 voxels, each voxel is 0.02 in unit cube
571
486
  float stepSize = sliceSize; //quality: larger step is faster traversal, but fewer samples
572
487
  float opacityCorrection = stepSize/sliceSize;
573
- dir = normalize(dir);
574
488
  vec4 deltaDir = vec4(dir.xyz * stepSize, stepSize);
575
489
  vec4 samplePos = vec4(start.xyz, 0.0); //ray position
576
490
  vec4 colAcc = vec4(0.0,0.0,0.0,0.0);
@@ -624,6 +538,9 @@ uniform vec3 volScale;
624
538
  uniform vec4 clipPlane;
625
539
  uniform highp sampler3D volume, overlay;
626
540
  uniform float overlays;
541
+ uniform float clipThick;
542
+ uniform vec3 clipLo;
543
+ uniform vec3 clipHi;
627
544
  uniform float backOpacity;
628
545
  uniform mat4 mvpMtx;
629
546
  uniform mat4 matRAS;
@@ -660,6 +577,9 @@ uniform vec3 volScale;
660
577
  uniform vec4 clipPlane;
661
578
  uniform highp sampler3D volume, overlay;
662
579
  uniform float overlays;
580
+ uniform float clipThick;
581
+ uniform vec3 clipLo;
582
+ uniform vec3 clipHi;
663
583
  uniform float backOpacity;
664
584
  uniform mat4 mvpMtx;
665
585
  uniform mat4 normMtx;
@@ -1495,6 +1415,28 @@ void main() {
1495
1415
  float lightNormDot = max(dot(normalize(vN), l), 0.0);
1496
1416
  color = vec4(lightNormDot * vClr.rgb * diffuse, opacity);
1497
1417
  }`;
1418
+ var fragMeshSpecularEdgeShader = `#version 300 es
1419
+ precision highp int;
1420
+ precision highp float;
1421
+ uniform float opacity;
1422
+ in vec4 vClr;
1423
+ in vec3 vN;
1424
+ out vec4 color;
1425
+ void main() {
1426
+ float specularRGB = 0.7;
1427
+ float specularWhite = 0.3;
1428
+ float shininess = 10.0;
1429
+ float diffuse = 1.0;
1430
+ vec3 r = vec3(0.0, 0.0, 1.0); //rayDir: for orthographic projections moving in Z direction (no need for normal matrix)
1431
+ vec3 n = normalize(vN);
1432
+ vec3 l = vec3(0.0, 0.0, -1.0);
1433
+ float lightNormDot = max(dot(n, l), 0.0);
1434
+ vec3 d3 = lightNormDot * vClr.rgb * diffuse;
1435
+ float s = pow(max(dot(reflect(l, n), r), 0.0), shininess);
1436
+ vec3 s3 = specularRGB * s * vClr.rgb;
1437
+ s *= specularWhite;
1438
+ color = vec4(d3 + s3 + s, opacity);
1439
+ }`;
1498
1440
  var fragMeshShader = `#version 300 es
1499
1441
  precision highp int;
1500
1442
  precision highp float;
@@ -1679,6 +1621,9 @@ uniform vec3 texVox;
1679
1621
  uniform vec4 clipPlane;
1680
1622
  uniform highp sampler3D volume, overlay;
1681
1623
  uniform float overlays;
1624
+ uniform float clipThick;
1625
+ uniform vec3 clipLo;
1626
+ uniform vec3 clipHi;
1682
1627
  uniform mat4 matRAS;
1683
1628
  uniform mat4 mvpMtx;
1684
1629
  uniform float drawOpacity, renderOverlayBlend;
@@ -1695,8 +1640,9 @@ void main() {
1695
1640
  fColor = vec4(0.0, 0.0, 0.0, 0.0); //assume no hit: ID = 0
1696
1641
  float fid = float(id & 255)/ 255.0;
1697
1642
  vec3 backPosition = GetBackPosition(start);
1698
- vec3 dir = backPosition - start;
1699
- float len = length(dir);
1643
+ vec3 dir = normalize(backPosition - start);
1644
+ clipVolumeStart(start, backPosition);
1645
+ float len = length(backPosition - start);
1700
1646
  float lenVox = length((texVox * start) - (texVox * backPosition));
1701
1647
  if ((lenVox < 0.5) || (len > 3.0)) return;//discard; //length limit for parallel rays
1702
1648
  float sliceSize = len / lenVox; //e.g. if ray length is 1.0 and traverses 50 voxels, each voxel is 0.02 in unit cube
@@ -3292,7 +3238,7 @@ __publicField(_NiivueObject3D, "makeColoredSphere", function(vertices, indices,
3292
3238
  var NiivueObject3D = _NiivueObject3D;
3293
3239
 
3294
3240
  // src/nvmesh.ts
3295
- import { vec3 as vec34, vec4 as vec43 } from "gl-matrix";
3241
+ import { vec3 as vec35, vec4 as vec44 } from "gl-matrix";
3296
3242
  import { v4 as uuidv4 } from "@lukeed/uuid";
3297
3243
 
3298
3244
  // src/cmaps/index.js
@@ -11450,8 +11396,257 @@ var NVMeshUtilities = class {
11450
11396
  };
11451
11397
 
11452
11398
  // src/nvmesh-loaders.ts
11453
- import { mat4 as mat42, vec4 as vec42, vec3 as vec33 } from "gl-matrix";
11454
- import { decompressSync, unzipSync } from "fflate/browser";
11399
+ import { mat4 as mat43, vec4 as vec43, vec3 as vec34 } from "gl-matrix";
11400
+ import { decompressSync as decompressSync2, unzipSync } from "fflate/browser";
11401
+
11402
+ // src/nvutilities.ts
11403
+ import arrayEqual from "array-equal";
11404
+ import { compress, decompress, decompressSync, strFromU8, strToU8 } from "fflate/browser";
11405
+ import { mat4 as mat42, vec3 as vec33, vec4 as vec42 } from "gl-matrix";
11406
+ var NVUtilities = class _NVUtilities {
11407
+ static arrayBufferToBase64(arrayBuffer) {
11408
+ const bytes = new Uint8Array(arrayBuffer);
11409
+ return _NVUtilities.uint8tob64(bytes);
11410
+ }
11411
+ static readMatV4(buffer) {
11412
+ let len = buffer.byteLength;
11413
+ if (len < 40) {
11414
+ throw new Error("File too small to be MAT v4: bytes = " + buffer.byteLength);
11415
+ }
11416
+ let reader = new DataView(buffer);
11417
+ let magic = reader.getUint16(0, true);
11418
+ let _buffer = buffer;
11419
+ if (magic === 35615 || magic === 8075) {
11420
+ const raw = decompressSync(new Uint8Array(buffer));
11421
+ reader = new DataView(raw.buffer);
11422
+ magic = reader.getUint16(0, true);
11423
+ _buffer = raw.buffer;
11424
+ len = _buffer.byteLength;
11425
+ }
11426
+ const textDecoder = new TextDecoder("utf-8");
11427
+ const bytes = new Uint8Array(_buffer);
11428
+ let pos = 0;
11429
+ const mat = {};
11430
+ function getTensDigit(v) {
11431
+ return Math.floor(v / 10) % 10;
11432
+ }
11433
+ function readArray(tagDataType, tagBytesStart, tagBytesEnd) {
11434
+ const byteArray = new Uint8Array(bytes.subarray(tagBytesStart, tagBytesEnd));
11435
+ if (tagDataType === 1) {
11436
+ return new Float32Array(byteArray.buffer);
11437
+ }
11438
+ if (tagDataType === 2) {
11439
+ return new Int32Array(byteArray.buffer);
11440
+ }
11441
+ if (tagDataType === 3) {
11442
+ return new Int16Array(byteArray.buffer);
11443
+ }
11444
+ if (tagDataType === 4) {
11445
+ return new Uint16Array(byteArray.buffer);
11446
+ }
11447
+ if (tagDataType === 5) {
11448
+ return new Uint8Array(byteArray.buffer);
11449
+ }
11450
+ return new Float64Array(byteArray.buffer);
11451
+ }
11452
+ function readTag() {
11453
+ const mtype = reader.getUint32(pos, true);
11454
+ const mrows = reader.getUint32(pos + 4, true);
11455
+ const ncols = reader.getUint32(pos + 8, true);
11456
+ const imagf = reader.getUint32(pos + 12, true);
11457
+ const namlen = reader.getUint32(pos + 16, true);
11458
+ pos += 20;
11459
+ if (imagf !== 0) {
11460
+ throw new Error("Matlab V4 reader does not support imaginary numbers");
11461
+ }
11462
+ const tagArrayItems = mrows * ncols;
11463
+ if (tagArrayItems < 1) {
11464
+ throw new Error("mrows * ncols must be greater than one");
11465
+ }
11466
+ const byteArray = new Uint8Array(bytes.subarray(pos, pos + namlen));
11467
+ const tagName = textDecoder.decode(byteArray).trim().replaceAll("\0", "");
11468
+ const tagDataType = getTensDigit(mtype);
11469
+ let tagBytesPerItem = 8;
11470
+ if (tagDataType >= 1 && tagDataType <= 2) {
11471
+ tagBytesPerItem = 4;
11472
+ } else if (tagDataType >= 3 && tagDataType <= 4) {
11473
+ tagBytesPerItem = 2;
11474
+ } else if (tagDataType === 5) {
11475
+ tagBytesPerItem = 1;
11476
+ } else if (tagDataType !== 0) {
11477
+ throw new Error("impossible Matlab v4 datatype");
11478
+ }
11479
+ pos += namlen;
11480
+ if (mtype > 50) {
11481
+ throw new Error("Does not appear to be little-endian V4 Matlab file");
11482
+ }
11483
+ const posEnd = pos + tagArrayItems * tagBytesPerItem;
11484
+ mat[tagName] = readArray(tagDataType, pos, posEnd);
11485
+ pos = posEnd;
11486
+ }
11487
+ while (pos + 20 < len) {
11488
+ readTag();
11489
+ }
11490
+ return mat;
11491
+ }
11492
+ // readMatV4()
11493
+ /*
11494
+ https://gist.github.com/jonleighton/958841
11495
+ MIT LICENSE
11496
+ Copyright 2011 Jon Leighton
11497
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11498
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11499
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11500
+ */
11501
+ static uint8tob64(bytes) {
11502
+ let base64 = "";
11503
+ const encodings = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
11504
+ const byteLength = bytes.byteLength;
11505
+ const byteRemainder = byteLength % 3;
11506
+ const mainLength = byteLength - byteRemainder;
11507
+ let a, b, c, d;
11508
+ let chunk;
11509
+ for (let i = 0; i < mainLength; i = i + 3) {
11510
+ chunk = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
11511
+ a = (chunk & 16515072) >> 18;
11512
+ b = (chunk & 258048) >> 12;
11513
+ c = (chunk & 4032) >> 6;
11514
+ d = chunk & 63;
11515
+ base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
11516
+ }
11517
+ if (byteRemainder === 1) {
11518
+ chunk = bytes[mainLength];
11519
+ a = (chunk & 252) >> 2;
11520
+ b = (chunk & 3) << 4;
11521
+ base64 += encodings[a] + encodings[b] + "==";
11522
+ } else if (byteRemainder === 2) {
11523
+ chunk = bytes[mainLength] << 8 | bytes[mainLength + 1];
11524
+ a = (chunk & 64512) >> 10;
11525
+ b = (chunk & 1008) >> 4;
11526
+ c = (chunk & 15) << 2;
11527
+ base64 += encodings[a] + encodings[b] + encodings[c] + "=";
11528
+ }
11529
+ return base64;
11530
+ }
11531
+ // https://stackoverflow.com/questions/34156282/how-do-i-save-json-to-local-text-file
11532
+ static download(content, fileName, contentType) {
11533
+ const a = document.createElement("a");
11534
+ const contentArray = Array.isArray(content) ? content : [content];
11535
+ const file = new Blob(contentArray, { type: contentType });
11536
+ a.href = URL.createObjectURL(file);
11537
+ a.download = fileName;
11538
+ a.click();
11539
+ }
11540
+ static readFileAsync(file) {
11541
+ return new Promise((resolve, reject) => {
11542
+ const reader = new FileReader();
11543
+ reader.onload = () => {
11544
+ resolve(reader.result);
11545
+ };
11546
+ reader.onerror = reject;
11547
+ reader.readAsArrayBuffer(file);
11548
+ });
11549
+ }
11550
+ static blobToBase64(blob) {
11551
+ return new Promise((resolve) => {
11552
+ const reader = new FileReader();
11553
+ reader.onloadend = () => resolve(reader.result);
11554
+ reader.readAsDataURL(blob);
11555
+ });
11556
+ }
11557
+ static async decompressBase64String(base64) {
11558
+ const compressed = atob(base64);
11559
+ const compressedBuffer = new ArrayBuffer(compressed.length);
11560
+ const compressedView = new Uint8Array(compressedBuffer);
11561
+ for (let i = 0; i < compressed.length; i++) {
11562
+ compressedView[i] = compressed.charCodeAt(i);
11563
+ }
11564
+ return _NVUtilities.decompressArrayBuffer(compressedView);
11565
+ }
11566
+ static async compressToBase64String(string) {
11567
+ const buf = await _NVUtilities.compressStringToArrayBuffer(string);
11568
+ return _NVUtilities.uint8tob64(new Uint8Array(buf));
11569
+ }
11570
+ static async compressStringToArrayBuffer(input) {
11571
+ return new Promise((resolve, reject) => {
11572
+ const uint8Array = strToU8(input);
11573
+ compress(uint8Array, (err, compressed) => {
11574
+ if (err) {
11575
+ reject(err);
11576
+ } else {
11577
+ resolve(compressed.buffer);
11578
+ }
11579
+ });
11580
+ });
11581
+ }
11582
+ static isArrayBufferCompressed(buffer) {
11583
+ if (buffer && buffer.byteLength) {
11584
+ const arr = new Uint8Array(buffer);
11585
+ const magicNumber = arr[0] << 8 | arr[1];
11586
+ return magicNumber === 8075;
11587
+ } else {
11588
+ return false;
11589
+ }
11590
+ }
11591
+ static async decompressArrayBuffer(buffer) {
11592
+ return new Promise((resolve, reject) => {
11593
+ const uint8Array = new Uint8Array(buffer);
11594
+ decompress(uint8Array, (err, decompressed) => {
11595
+ if (err) {
11596
+ reject(err);
11597
+ } else {
11598
+ const result = strFromU8(decompressed);
11599
+ resolve(result);
11600
+ }
11601
+ });
11602
+ });
11603
+ }
11604
+ static arraysAreEqual(a, b) {
11605
+ return arrayEqual(a, b);
11606
+ }
11607
+ /**
11608
+ * Generate a pre-filled number array.
11609
+ *
11610
+ * @param start - start value
11611
+ * @param stop - stop value
11612
+ * @param step - step value
11613
+ * @returns filled number array
11614
+ */
11615
+ static range(start, stop, step) {
11616
+ return Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + i * step);
11617
+ }
11618
+ /**
11619
+ * convert spherical AZIMUTH, ELEVATION to Cartesian
11620
+ * @param azimuth - azimuth number
11621
+ * @param elevation - elevation number
11622
+ * @returns the converted [x, y, z] coordinates
11623
+ * @example
11624
+ * xyz = NVUtilities.sph2cartDeg(42, 42)
11625
+ */
11626
+ static sph2cartDeg(azimuth, elevation) {
11627
+ const Phi = -elevation * (Math.PI / 180);
11628
+ const Theta = (azimuth - 90) % 360 * (Math.PI / 180);
11629
+ const ret = [Math.cos(Phi) * Math.cos(Theta), Math.cos(Phi) * Math.sin(Theta), Math.sin(Phi)];
11630
+ const len = Math.sqrt(ret[0] * ret[0] + ret[1] * ret[1] + ret[2] * ret[2]);
11631
+ if (len <= 0) {
11632
+ return ret;
11633
+ }
11634
+ ret[0] /= len;
11635
+ ret[1] /= len;
11636
+ ret[2] /= len;
11637
+ return ret;
11638
+ }
11639
+ static vox2mm(XYZ, mtx) {
11640
+ const sform = mat42.clone(mtx);
11641
+ mat42.transpose(sform, sform);
11642
+ const pos = vec42.fromValues(XYZ[0], XYZ[1], XYZ[2], 1);
11643
+ vec42.transformMat4(pos, pos, sform);
11644
+ const pos3 = vec33.fromValues(pos[0], pos[1], pos[2]);
11645
+ return pos3;
11646
+ }
11647
+ };
11648
+
11649
+ // src/nvmesh-loaders.ts
11455
11650
  var utiltiesLogger = log;
11456
11651
  var NVMeshLoaders = class _NVMeshLoaders {
11457
11652
  // read undocumented AFNI tract.niml format streamlines
@@ -11535,90 +11730,9 @@ var NVMeshLoaders = class _NVMeshLoaders {
11535
11730
  // https://dsi-studio.labsolver.org/doc/cli_data.html
11536
11731
  // https://brain.labsolver.org/hcp_trk_atlas.html
11537
11732
  static readTT(buffer) {
11538
- function readMatV4(buffer2) {
11539
- let len = buffer2.byteLength;
11540
- if (len < 40) {
11541
- throw new Error("File too small to be MAT v4: bytes = " + buffer2.byteLength);
11542
- }
11543
- let reader = new DataView(buffer2);
11544
- let magic = reader.getUint16(0, true);
11545
- let _buffer = buffer2;
11546
- if (magic === 35615 || magic === 8075) {
11547
- const raw = decompressSync(new Uint8Array(buffer2));
11548
- reader = new DataView(raw.buffer);
11549
- magic = reader.getUint16(0, true);
11550
- _buffer = raw.buffer;
11551
- len = _buffer.byteLength;
11552
- }
11553
- const textDecoder = new TextDecoder("utf-8");
11554
- const bytes = new Uint8Array(_buffer);
11555
- let pos = 0;
11556
- const mat2 = {};
11557
- function getTensDigit(v) {
11558
- return Math.floor(v / 10) % 10;
11559
- }
11560
- function readArray(tagDataType, tagBytesStart, tagBytesEnd) {
11561
- const byteArray = new Uint8Array(bytes.subarray(tagBytesStart, tagBytesEnd));
11562
- if (tagDataType === 1) {
11563
- return new Float32Array(byteArray.buffer);
11564
- }
11565
- if (tagDataType === 2) {
11566
- return new Int32Array(byteArray.buffer);
11567
- }
11568
- if (tagDataType === 3) {
11569
- return new Int16Array(byteArray.buffer);
11570
- }
11571
- if (tagDataType === 4) {
11572
- return new Uint16Array(byteArray.buffer);
11573
- }
11574
- if (tagDataType === 5) {
11575
- return new Uint8Array(byteArray.buffer);
11576
- }
11577
- return new Float64Array(byteArray.buffer);
11578
- }
11579
- function readTag() {
11580
- const mtype = reader.getUint32(pos, true);
11581
- const mrows = reader.getUint32(pos + 4, true);
11582
- const ncols = reader.getUint32(pos + 8, true);
11583
- const imagf = reader.getUint32(pos + 12, true);
11584
- const namlen = reader.getUint32(pos + 16, true);
11585
- pos += 20;
11586
- if (imagf !== 0) {
11587
- throw new Error("Matlab V4 reader does not support imaginary numbers");
11588
- }
11589
- const tagArrayItems = mrows * ncols;
11590
- if (tagArrayItems < 1) {
11591
- throw new Error("mrows * ncols must be greater than one");
11592
- }
11593
- const byteArray = new Uint8Array(bytes.subarray(pos, pos + namlen));
11594
- const tagName = textDecoder.decode(byteArray).trim().replaceAll("\0", "");
11595
- const tagDataType = getTensDigit(mtype);
11596
- let tagBytesPerItem = 8;
11597
- if (tagDataType >= 1 && tagDataType <= 2) {
11598
- tagBytesPerItem = 4;
11599
- } else if (tagDataType >= 3 && tagDataType <= 4) {
11600
- tagBytesPerItem = 2;
11601
- } else if (tagDataType === 5) {
11602
- tagBytesPerItem = 1;
11603
- } else if (tagDataType !== 0) {
11604
- throw new Error("impossible Matlab v4 datatype");
11605
- }
11606
- pos += namlen;
11607
- if (mtype > 50) {
11608
- throw new Error("Does not appear to be little-endian V4 Matlab file");
11609
- }
11610
- const posEnd = pos + tagArrayItems * tagBytesPerItem;
11611
- mat2[tagName] = readArray(tagDataType, pos, posEnd);
11612
- pos = posEnd;
11613
- }
11614
- while (pos + 20 < len) {
11615
- readTag();
11616
- }
11617
- return mat2;
11618
- }
11619
11733
  let offsetPt0 = new Uint32Array(0);
11620
11734
  let pts = new Float32Array(0);
11621
- const mat = readMatV4(buffer);
11735
+ const mat = NVUtilities.readMatV4(buffer);
11622
11736
  if (!("trans_to_mni" in mat)) {
11623
11737
  throw new Error("TT format file must have 'trans_to_mni'");
11624
11738
  }
@@ -11628,9 +11742,9 @@ var NVMeshLoaders = class _NVMeshLoaders {
11628
11742
  if (!("track" in mat)) {
11629
11743
  throw new Error("TT format file must have 'track'");
11630
11744
  }
11631
- let trans_to_mni = mat42.create();
11745
+ let trans_to_mni = mat43.create();
11632
11746
  const m = mat.trans_to_mni;
11633
- trans_to_mni = mat42.fromValues(
11747
+ trans_to_mni = mat43.fromValues(
11634
11748
  m[0],
11635
11749
  m[1],
11636
11750
  m[2],
@@ -11648,7 +11762,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
11648
11762
  m[14],
11649
11763
  m[15]
11650
11764
  );
11651
- mat42.transpose(trans_to_mni, trans_to_mni);
11765
+ mat43.transpose(trans_to_mni, trans_to_mni);
11652
11766
  function parse_tt(track) {
11653
11767
  const dv = new DataView(track.buffer);
11654
11768
  const pos = [];
@@ -11688,8 +11802,8 @@ var NVMeshLoaders = class _NVMeshLoaders {
11688
11802
  }
11689
11803
  let v = 0;
11690
11804
  for (let i2 = 0; i2 < npt / 3; i2++) {
11691
- const pos2 = vec42.fromValues(pts[v], pts[v + 1], pts[v + 2], 1);
11692
- vec42.transformMat4(pos2, pos2, trans_to_mni);
11805
+ const pos2 = vec43.fromValues(pts[v], pts[v + 1], pts[v + 2], 1);
11806
+ vec43.transformMat4(pos2, pos2, trans_to_mni);
11693
11807
  pts[v++] = pos2[0];
11694
11808
  pts[v++] = pos2[1];
11695
11809
  pts[v++] = pos2[2];
@@ -11975,7 +12089,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
11975
12089
  if (magic === 4247762216) {
11976
12090
  throw new Error("zstd TRK decompression is not supported");
11977
12091
  } else {
11978
- raw = decompressSync(new Uint8Array(buffer));
12092
+ raw = decompressSync2(new Uint8Array(buffer));
11979
12093
  }
11980
12094
  buffer = raw.buffer;
11981
12095
  reader = new DataView(buffer);
@@ -12000,7 +12114,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12000
12114
  const voxel_sizeX = reader.getFloat32(12, true);
12001
12115
  const voxel_sizeY = reader.getFloat32(16, true);
12002
12116
  const voxel_sizeZ = reader.getFloat32(20, true);
12003
- const zoomMat = mat42.fromValues(
12117
+ const zoomMat = mat43.fromValues(
12004
12118
  1 / voxel_sizeX,
12005
12119
  0,
12006
12120
  0,
@@ -12029,16 +12143,16 @@ var NVMeshLoaders = class _NVMeshLoaders {
12029
12143
  vals: []
12030
12144
  });
12031
12145
  }
12032
- const mat = mat42.create();
12146
+ const mat = mat43.create();
12033
12147
  for (let i2 = 0; i2 < 16; i2++) {
12034
12148
  mat[i2] = reader.getFloat32(440 + i2 * 4, true);
12035
12149
  }
12036
12150
  if (mat[15] === 0) {
12037
12151
  log.warn("TRK vox_to_ras not set");
12038
- mat42.identity(mat);
12152
+ mat43.identity(mat);
12039
12153
  }
12040
- const vox2mmMat = mat42.create();
12041
- mat42.mul(vox2mmMat, zoomMat, mat);
12154
+ const vox2mmMat = mat43.create();
12155
+ mat43.mul(vox2mmMat, zoomMat, mat);
12042
12156
  let i32 = null;
12043
12157
  let f32 = null;
12044
12158
  i32 = new Int32Array(buffer.slice(hdr_sz));
@@ -12180,7 +12294,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12180
12294
  }
12181
12295
  pts = Array.from(positions);
12182
12296
  } else {
12183
- let lineToInts2 = function() {
12297
+ let lineToInts = function() {
12184
12298
  str = lines[pos].trim();
12185
12299
  const items2 = str.trim().split(/\s+/);
12186
12300
  asciiInts = [];
@@ -12190,22 +12304,21 @@ var NVMeshLoaders = class _NVMeshLoaders {
12190
12304
  asciiIntsPos = 0;
12191
12305
  pos++;
12192
12306
  };
12193
- var lineToInts = lineToInts2;
12194
12307
  let npt = 0;
12195
12308
  offsetPt0[0] = 0;
12196
12309
  let asciiInts = [];
12197
12310
  let asciiIntsPos = 0;
12198
- lineToInts2();
12311
+ lineToInts();
12199
12312
  for (let c = 0; c < n_count; c++) {
12200
12313
  if (asciiIntsPos >= asciiInts.length) {
12201
- lineToInts2();
12314
+ lineToInts();
12202
12315
  }
12203
12316
  const numPoints = asciiInts[asciiIntsPos++];
12204
12317
  npt += numPoints;
12205
12318
  offsetPt0[c + 1] = npt;
12206
12319
  for (let i = 0; i < numPoints; i++) {
12207
12320
  if (asciiIntsPos >= asciiInts.length) {
12208
- lineToInts2();
12321
+ lineToInts();
12209
12322
  }
12210
12323
  const idx = asciiInts[asciiIntsPos++] * 3;
12211
12324
  pts.push(positions[idx + 0]);
@@ -12400,7 +12513,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
12400
12513
  let reader = new DataView(buffer);
12401
12514
  let vers = reader.getUint16(0, true);
12402
12515
  if (vers > 5) {
12403
- const raw = decompressSync(new Uint8Array(buffer));
12516
+ const raw = decompressSync2(new Uint8Array(buffer));
12404
12517
  reader = new DataView(raw.buffer);
12405
12518
  vers = reader.getUint16(0, true);
12406
12519
  buffer = raw.buffer;
@@ -13007,7 +13120,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13007
13120
  let magic = reader.getUint16(0, true);
13008
13121
  let _buffer = buffer;
13009
13122
  if (magic === 35615 || magic === 8075) {
13010
- const raw = decompressSync(new Uint8Array(buffer));
13123
+ const raw = decompressSync2(new Uint8Array(buffer));
13011
13124
  reader = new DataView(raw.buffer);
13012
13125
  magic = reader.getUint16(0, true);
13013
13126
  _buffer = raw.buffer;
@@ -13670,7 +13783,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13670
13783
  return _NVMeshLoaders.readASC(buffer);
13671
13784
  }
13672
13785
  if (bytes[0] === 31 && bytes[1] === 139) {
13673
- const raw = decompressSync(new Uint8Array(buffer));
13786
+ const raw = decompressSync2(new Uint8Array(buffer));
13674
13787
  buffer = raw.buffer;
13675
13788
  }
13676
13789
  const reader = new DataView(buffer);
@@ -13855,7 +13968,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
13855
13968
  nvert *= dim[i];
13856
13969
  }
13857
13970
  if (intent_code >= 3e3 && intent_code <= 3099 && voxoffset > 580) {
13858
- let readStrX2 = function() {
13971
+ let readStrX = function() {
13859
13972
  while (pos < len && bytes[pos] === 10) {
13860
13973
  pos++;
13861
13974
  }
@@ -13868,16 +13981,16 @@ var NVMeshLoaders = class _NVMeshLoaders {
13868
13981
  return "";
13869
13982
  }
13870
13983
  return new TextDecoder().decode(buffer.slice(startPos, pos - 1)).trim();
13871
- }, readStr2 = function() {
13872
- let line2 = readStrX2();
13984
+ }, readStr = function() {
13985
+ let line2 = readStrX();
13873
13986
  if (!line2.startsWith("<") || line2.endsWith(">")) {
13874
13987
  return line2;
13875
13988
  }
13876
13989
  while (pos < len && !line2.endsWith(">")) {
13877
- line2 += readStrX2();
13990
+ line2 += readStrX();
13878
13991
  }
13879
13992
  return line2;
13880
- }, readNumericTag2 = function(TagName, asString = false) {
13993
+ }, readNumericTag = function(TagName, asString = false) {
13881
13994
  const tpos = line.indexOf(TagName);
13882
13995
  if (tpos < 0) {
13883
13996
  return 1;
@@ -13890,7 +14003,6 @@ var NVMeshLoaders = class _NVMeshLoaders {
13890
14003
  }
13891
14004
  return parseInt(str);
13892
14005
  };
13893
- var readStrX = readStrX2, readStr = readStr2, readNumericTag = readNumericTag2;
13894
14006
  let indexOffset = 0;
13895
14007
  let indexCount = 0;
13896
14008
  let surfaceNumberOfVertices = 0;
@@ -13902,13 +14014,13 @@ var NVMeshLoaders = class _NVMeshLoaders {
13902
14014
  const nFrame4D = dim[5];
13903
14015
  const scalars2 = new Float32Array(n_vert * nFrame4D);
13904
14016
  while (pos < len) {
13905
- line = readStr2();
14017
+ line = readStr();
13906
14018
  if (line.includes("</CIFTI>")) {
13907
14019
  break;
13908
14020
  }
13909
14021
  if (line.includes("<BrainModel")) {
13910
- const nv = readNumericTag2("SurfaceNumberOfVertices=");
13911
- const bStruct = readNumericTag2("BrainStructure=", true).toUpperCase();
14022
+ const nv = readNumericTag("SurfaceNumberOfVertices=");
14023
+ const bStruct = readNumericTag("BrainStructure=", true).toUpperCase();
13912
14024
  if (nv % n_vert !== 0) {
13913
14025
  continue;
13914
14026
  }
@@ -13930,11 +14042,11 @@ var NVMeshLoaders = class _NVMeshLoaders {
13930
14042
  continue;
13931
14043
  }
13932
14044
  surfaceNumberOfVertices = nv;
13933
- indexOffset = readNumericTag2("IndexOffset=");
13934
- indexCount = readNumericTag2("IndexCount=");
14045
+ indexOffset = readNumericTag("IndexOffset=");
14046
+ indexCount = readNumericTag("IndexCount=");
13935
14047
  brainStructure = bStruct;
13936
14048
  if (!line.includes("<VertexIndices>")) {
13937
- line = readStr2();
14049
+ line = readStr();
13938
14050
  }
13939
14051
  if (!line.startsWith("<VertexIndices>") || !line.endsWith("</VertexIndices>")) {
13940
14052
  log.warn("Unable to find CIfTI <VertexIndices>");
@@ -14032,7 +14144,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
14032
14144
  magic = reader.getUint16(0, isLittleEndian);
14033
14145
  }
14034
14146
  if (magic !== 348) {
14035
- const raw = decompressSync(new Uint8Array(buffer));
14147
+ const raw = decompressSync2(new Uint8Array(buffer));
14036
14148
  reader = new DataView(raw.buffer);
14037
14149
  buffer = raw.buffer;
14038
14150
  magic = reader.getUint16(0, isLittleEndian);
@@ -14103,7 +14215,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
14103
14215
  let reader = new DataView(buffer);
14104
14216
  let raw = buffer;
14105
14217
  if (reader.getUint8(0) === 31 && reader.getUint8(1) === 139) {
14106
- const decompressed = decompressSync(new Uint8Array(buffer));
14218
+ const decompressed = decompressSync2(new Uint8Array(buffer));
14107
14219
  raw = new ArrayBuffer(decompressed.byteLength);
14108
14220
  new Uint8Array(raw).set(new Uint8Array(decompressed));
14109
14221
  reader = new DataView(decompressed.buffer);
@@ -14461,16 +14573,16 @@ var NVMeshLoaders = class _NVMeshLoaders {
14461
14573
  } else if (height < 0) {
14462
14574
  NiivueObject3D.makeColoredSphere(positions, indices, rgba255, radius, translation, rgba);
14463
14575
  } else {
14464
- const r = mat42.create();
14465
- mat42.fromRotation(r, rotation[3], [rotation[0], rotation[1], rotation[2]]);
14466
- const pti = vec42.fromValues(0, -height * 0.5, 0, 1);
14467
- const ptj = vec42.fromValues(0, +height * 0.5, 0, 1);
14468
- vec42.transformMat4(pti, pti, r);
14469
- vec42.transformMat4(ptj, ptj, r);
14470
- vec42.add(pti, pti, translation);
14471
- vec42.add(ptj, ptj, translation);
14472
- const pti3 = vec33.fromValues(pti[0], pti[1], pti[2]);
14473
- const ptj3 = vec33.fromValues(ptj[0], ptj[1], ptj[2]);
14576
+ const r = mat43.create();
14577
+ mat43.fromRotation(r, rotation[3], [rotation[0], rotation[1], rotation[2]]);
14578
+ const pti = vec43.fromValues(0, -height * 0.5, 0, 1);
14579
+ const ptj = vec43.fromValues(0, +height * 0.5, 0, 1);
14580
+ vec43.transformMat4(pti, pti, r);
14581
+ vec43.transformMat4(ptj, ptj, r);
14582
+ vec43.add(pti, pti, translation);
14583
+ vec43.add(ptj, ptj, translation);
14584
+ const pti3 = vec34.fromValues(pti[0], pti[1], pti[2]);
14585
+ const ptj3 = vec34.fromValues(ptj[0], ptj[1], ptj[2]);
14474
14586
  NiivueObject3D.makeColoredCylinder(positions, indices, rgba255, pti3, ptj3, radius, rgba);
14475
14587
  }
14476
14588
  }
@@ -14491,7 +14603,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
14491
14603
  }
14492
14604
  let chars = new TextDecoder("ascii").decode(buffer);
14493
14605
  if (chars[0].charCodeAt(0) === 31) {
14494
- const raw = decompressSync(new Uint8Array(buffer));
14606
+ const raw = decompressSync2(new Uint8Array(buffer));
14495
14607
  buffer = raw.buffer;
14496
14608
  chars = new TextDecoder("ascii").decode(raw.buffer);
14497
14609
  }
@@ -14629,7 +14741,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
14629
14741
  }
14630
14742
  }
14631
14743
  } else if (typeof Buffer === "undefined") {
14632
- let base64ToUint82 = function(base64) {
14744
+ let base64ToUint8 = function(base64) {
14633
14745
  const binary_string = atob(base64);
14634
14746
  const len2 = binary_string.length;
14635
14747
  const bytes = new Uint8Array(len2);
@@ -14638,17 +14750,16 @@ var NVMeshLoaders = class _NVMeshLoaders {
14638
14750
  }
14639
14751
  return bytes;
14640
14752
  };
14641
- var base64ToUint8 = base64ToUint82;
14642
14753
  if (isGzip) {
14643
- const datZ = base64ToUint82(line.slice());
14644
- datBin = decompressSync(new Uint8Array(datZ));
14754
+ const datZ = base64ToUint8(line.slice());
14755
+ datBin = decompressSync2(new Uint8Array(datZ));
14645
14756
  } else {
14646
- datBin = base64ToUint82(line.slice());
14757
+ datBin = base64ToUint8(line.slice());
14647
14758
  }
14648
14759
  } else {
14649
14760
  if (isGzip) {
14650
14761
  const datZ = Buffer.from(line.slice(), "base64");
14651
- datBin = decompressSync(new Uint8Array(datZ));
14762
+ datBin = decompressSync2(new Uint8Array(datZ));
14652
14763
  } else {
14653
14764
  datBin = Buffer.from(line.slice(), "base64");
14654
14765
  }
@@ -14686,14 +14797,13 @@ var NVMeshLoaders = class _NVMeshLoaders {
14686
14797
  }
14687
14798
  }
14688
14799
  } else {
14689
- let Float32Concat2 = function(first, second) {
14800
+ let Float32Concat = function(first, second) {
14690
14801
  const firstLength = first.length;
14691
14802
  const result = new Float32Array(firstLength + second.length);
14692
14803
  result.set(first);
14693
14804
  result.set(second, firstLength);
14694
14805
  return result;
14695
14806
  };
14696
- var Float32Concat = Float32Concat2;
14697
14807
  nvert = Dims[0] * Dims[1] * Dims[2];
14698
14808
  if (n_vert !== 0) {
14699
14809
  if (nvert % n_vert !== 0) {
@@ -14715,7 +14825,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
14715
14825
  } else {
14716
14826
  throw new Error(`Invalid dataType: ${dataType}`);
14717
14827
  }
14718
- scalars = Float32Concat2(scalars, scalarsNew);
14828
+ scalars = Float32Concat(scalars, scalarsNew);
14719
14829
  }
14720
14830
  continue;
14721
14831
  }
@@ -14879,6 +14989,7 @@ var NVMesh2 = class _NVMesh {
14879
14989
  __publicField(this, "rgba255");
14880
14990
  __publicField(this, "fiberLength");
14881
14991
  __publicField(this, "fiberLengths");
14992
+ __publicField(this, "fiberDensity");
14882
14993
  __publicField(this, "fiberDither", 0.1);
14883
14994
  __publicField(this, "fiberColor", "Global");
14884
14995
  __publicField(this, "fiberDecimationStride", 1);
@@ -14887,6 +14998,8 @@ var NVMesh2 = class _NVMesh {
14887
14998
  // 1=streamline, 2=imposter, >2=mesh(cylinder with fiberSides sides)
14888
14999
  __publicField(this, "fiberRadius", 0);
14889
15000
  // in mm, e.g. 3 means 6mm diameter fibers, ignored if fiberSides < 3
15001
+ __publicField(this, "fiberOcclusion", 0);
15002
+ // value 0..1 to simulate ambient occlusion
14890
15003
  __publicField(this, "f32PerVertex", 5);
14891
15004
  // MUST be 5 or 7: number of float32s per vertex DEPRECATED, future releases will ALWAYS be 5
14892
15005
  __publicField(this, "fiberMask");
@@ -14974,7 +15087,8 @@ var NVMesh2 = class _NVMesh {
14974
15087
  if (dpv) {
14975
15088
  this.initValuesArray(dpv);
14976
15089
  }
14977
- this.offsetPt0 = tris;
15090
+ this.offsetPt0 = new Uint32Array(tris);
15091
+ this.tris = new Uint32Array(0);
14978
15092
  this.updateFibers(gl);
14979
15093
  gl.bindVertexArray(this.vaoFiber);
14980
15094
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
@@ -15011,7 +15125,7 @@ var NVMesh2 = class _NVMesh {
15011
15125
  // given streamlines (which webGL renders as a single pixel), extrude to cylinders
15012
15126
  linesToCylinders(gl, posClrF32, indices) {
15013
15127
  function v4ToV3(v4) {
15014
- return vec34.fromValues(v4[0], v4[1], v4[2]);
15128
+ return vec35.fromValues(v4[0], v4[1], v4[2]);
15015
15129
  }
15016
15130
  const primitiveRestart = Math.pow(2, 32) - 1;
15017
15131
  const n_count = indices.length;
@@ -15033,11 +15147,11 @@ var NVMesh2 = class _NVMesh {
15033
15147
  const f32 = new Float32Array(npt * f32PerVertex);
15034
15148
  const u8 = new Uint8Array(f32.buffer);
15035
15149
  let vtx = 0;
15036
- let prevV4 = vec43.create();
15037
- let currV4 = vec43.create();
15038
- let nextV4 = vec43.create();
15039
- const v1 = vec34.create();
15040
- let prevV2 = vec34.create();
15150
+ let prevV4 = vec44.create();
15151
+ let currV4 = vec44.create();
15152
+ let nextV4 = vec44.create();
15153
+ const v1 = vec35.create();
15154
+ let prevV2 = vec35.create();
15041
15155
  let node = 0;
15042
15156
  const radius = this.fiberRadius;
15043
15157
  for (let i = 0; i < n_count; i++) {
@@ -15048,47 +15162,47 @@ var NVMesh2 = class _NVMesh {
15048
15162
  let idx2 = indices[i] * 4;
15049
15163
  node++;
15050
15164
  if (node <= 1) {
15051
- prevV4 = vec43.fromValues(posClrF32[idx2 + 0], posClrF32[idx2 + 1], posClrF32[idx2 + 2], posClrF32[idx2 + 3]);
15052
- currV4 = vec43.clone(prevV4);
15165
+ prevV4 = vec44.fromValues(posClrF32[idx2 + 0], posClrF32[idx2 + 1], posClrF32[idx2 + 2], posClrF32[idx2 + 3]);
15166
+ currV4 = vec44.clone(prevV4);
15053
15167
  if (i + 1 < n_count && indices[i + 1] !== primitiveRestart) {
15054
15168
  idx2 = indices[i + 1] * 4;
15055
- nextV4 = vec43.fromValues(posClrF32[idx2 + 0], posClrF32[idx2 + 1], posClrF32[idx2 + 2], posClrF32[idx2 + 3]);
15056
- vec34.subtract(v1, v4ToV3(prevV4), v4ToV3(nextV4));
15057
- vec34.normalize(v1, v1);
15169
+ nextV4 = vec44.fromValues(posClrF32[idx2 + 0], posClrF32[idx2 + 1], posClrF32[idx2 + 2], posClrF32[idx2 + 3]);
15170
+ vec35.subtract(v1, v4ToV3(prevV4), v4ToV3(nextV4));
15171
+ vec35.normalize(v1, v1);
15058
15172
  prevV2 = NiivueObject3D.getFirstPerpVector(v1);
15059
15173
  }
15060
15174
  continue;
15061
15175
  }
15062
15176
  if (isLineEnd) {
15063
- nextV4 = vec43.clone(currV4);
15177
+ nextV4 = vec44.clone(currV4);
15064
15178
  } else {
15065
- nextV4 = vec43.fromValues(posClrF32[idx2 + 0], posClrF32[idx2 + 1], posClrF32[idx2 + 2], posClrF32[idx2 + 3]);
15066
- }
15067
- vec34.subtract(v1, v4ToV3(prevV4), v4ToV3(nextV4));
15068
- vec34.normalize(v1, v1);
15069
- const D = vec34.create();
15070
- vec34.cross(D, prevV2, v1);
15071
- const v2 = vec34.create();
15072
- vec34.cross(v2, v1, D);
15073
- prevV2 = vec34.clone(prevV2);
15074
- const v3 = vec34.create();
15075
- vec34.cross(v3, v1, v2);
15076
- vec34.normalize(v3, v3);
15077
- const vtxXYZ = vec34.create();
15179
+ nextV4 = vec44.fromValues(posClrF32[idx2 + 0], posClrF32[idx2 + 1], posClrF32[idx2 + 2], posClrF32[idx2 + 3]);
15180
+ }
15181
+ vec35.subtract(v1, v4ToV3(prevV4), v4ToV3(nextV4));
15182
+ vec35.normalize(v1, v1);
15183
+ const D = vec35.create();
15184
+ vec35.cross(D, prevV2, v1);
15185
+ const v2 = vec35.create();
15186
+ vec35.cross(v2, v1, D);
15187
+ prevV2 = vec35.clone(prevV2);
15188
+ const v3 = vec35.create();
15189
+ vec35.cross(v3, v1, v2);
15190
+ vec35.normalize(v3, v3);
15191
+ const vtxXYZ = vec35.create();
15078
15192
  for (let j = 0; j < cyl_sides; j++) {
15079
15193
  const c = Math.cos(j / cyl_sides * 2 * Math.PI);
15080
15194
  const s = Math.sin(j / cyl_sides * 2 * Math.PI);
15081
15195
  vtxXYZ[0] = radius * (c * v2[0] + s * v3[0]);
15082
15196
  vtxXYZ[1] = radius * (c * v2[1] + s * v3[1]);
15083
15197
  vtxXYZ[2] = radius * (c * v2[2] + s * v3[2]);
15084
- vec34.add(vtxXYZ, v4ToV3(currV4), vtxXYZ);
15198
+ vec35.add(vtxXYZ, v4ToV3(currV4), vtxXYZ);
15085
15199
  const fidx = vtx * f32PerVertex;
15086
15200
  f32[fidx + 0] = vtxXYZ[0];
15087
15201
  f32[fidx + 1] = vtxXYZ[1];
15088
15202
  f32[fidx + 2] = vtxXYZ[2];
15089
- const n3 = vec34.create();
15090
- vec34.subtract(n3, vtxXYZ, v4ToV3(currV4));
15091
- vec34.normalize(n3, n3);
15203
+ const n3 = vec35.create();
15204
+ vec35.subtract(n3, vtxXYZ, v4ToV3(currV4));
15205
+ vec35.normalize(n3, n3);
15092
15206
  const fidxU8 = (fidx + 3) * 4;
15093
15207
  u8[fidxU8 + 0] = n3[0] * 127;
15094
15208
  u8[fidxU8 + 1] = n3[1] * 127;
@@ -15096,8 +15210,8 @@ var NVMesh2 = class _NVMesh {
15096
15210
  f32[fidx + 4] = currV4[3];
15097
15211
  vtx++;
15098
15212
  }
15099
- prevV4 = vec43.clone(currV4);
15100
- currV4 = vec43.clone(nextV4);
15213
+ prevV4 = vec44.clone(currV4);
15214
+ currV4 = vec44.clone(nextV4);
15101
15215
  if (isLineEnd) {
15102
15216
  node = 0;
15103
15217
  }
@@ -15141,6 +15255,113 @@ var NVMesh2 = class _NVMesh {
15141
15255
  this.indexCount = nidx;
15142
15256
  }
15143
15257
  // linesToCylinders
15258
+ createFiberDensityMap() {
15259
+ if (this.fiberDensity) {
15260
+ return;
15261
+ }
15262
+ const pts = this.pts;
15263
+ const npt = pts.length / 3;
15264
+ let maxExtentsRange = 0;
15265
+ for (let i = 0; i < 3; i++) {
15266
+ const range = this.extentsMax[i] - this.extentsMin[i];
15267
+ maxExtentsRange = Math.max(maxExtentsRange, range);
15268
+ }
15269
+ this.fiberDensity = new Float32Array(npt);
15270
+ if (maxExtentsRange === 0) {
15271
+ return;
15272
+ }
15273
+ const bins = 64;
15274
+ const binWidth = maxExtentsRange / (bins - 1);
15275
+ const half = binWidth / 2;
15276
+ const scale = (bins - 1) / maxExtentsRange;
15277
+ let densityMap = new Float32Array(bins * bins * bins);
15278
+ const mn = [this.extentsMin[0] - half, this.extentsMin[1] - half, this.extentsMin[2] - half];
15279
+ const xyz = [0, 0, 0];
15280
+ const prevVx = -1;
15281
+ const binsXbins = bins * bins;
15282
+ let j = 0;
15283
+ for (let i = 0; i < npt; i++) {
15284
+ xyz[0] = Math.round((pts[j++] - mn[0]) * scale);
15285
+ xyz[1] = Math.round((pts[j++] - mn[1]) * scale);
15286
+ xyz[2] = Math.round((pts[j++] - mn[2]) * scale);
15287
+ const vx = xyz[0] + xyz[1] * bins + xyz[2] * binsXbins;
15288
+ if (vx === prevVx) {
15289
+ continue;
15290
+ }
15291
+ densityMap[vx]++;
15292
+ }
15293
+ function blur3D(vol, dim) {
15294
+ let raw = vol.slice();
15295
+ let v = -1;
15296
+ const dim1 = dim - 1;
15297
+ for (let z = 0; z < dim; z++) {
15298
+ for (let y = 0; y < dim; y++) {
15299
+ for (let x = 0; x < dim; x++) {
15300
+ v++;
15301
+ if (x < 1 || x >= dim1) {
15302
+ continue;
15303
+ }
15304
+ vol[v] = raw[v - 1] + raw[v] + raw[v] + raw[v + 1];
15305
+ }
15306
+ }
15307
+ }
15308
+ v = -1;
15309
+ raw = vol.slice();
15310
+ for (let z = 0; z < dim; z++) {
15311
+ for (let y = 0; y < dim; y++) {
15312
+ for (let x = 0; x < dim; x++) {
15313
+ v++;
15314
+ if (y < 1 || y >= dim1) {
15315
+ continue;
15316
+ }
15317
+ vol[v] = raw[v - dim] + raw[v] + raw[v] + raw[v + dim];
15318
+ }
15319
+ }
15320
+ }
15321
+ const dimXdim = dim * dim;
15322
+ v = -1;
15323
+ raw = vol.slice();
15324
+ for (let z = 0; z < dim; z++) {
15325
+ for (let y = 0; y < dim; y++) {
15326
+ for (let x = 0; x < dim; x++) {
15327
+ v++;
15328
+ if (z < 1 || z >= dim1) {
15329
+ continue;
15330
+ }
15331
+ vol[v] = raw[v - dimXdim] + raw[v] + raw[v] + raw[dimXdim];
15332
+ }
15333
+ }
15334
+ }
15335
+ return vol;
15336
+ }
15337
+ densityMap = blur3D(densityMap, bins);
15338
+ densityMap = blur3D(densityMap, bins);
15339
+ let mx = 0;
15340
+ let mn0 = Infinity;
15341
+ const binsXbinsXbins = bins * bins * bins;
15342
+ for (let i = 0; i < binsXbinsXbins; i++) {
15343
+ if (densityMap[i] <= 0) {
15344
+ continue;
15345
+ }
15346
+ mx = Math.max(mx, densityMap[i]);
15347
+ mn0 = Math.min(mn0, densityMap[i]);
15348
+ }
15349
+ if (mx <= 1 || mx <= mn0) {
15350
+ return;
15351
+ }
15352
+ j = 0;
15353
+ for (let i = 0; i < binsXbinsXbins; i++) {
15354
+ densityMap[i] = Math.max(0, densityMap[i] - mn0);
15355
+ }
15356
+ mx -= mn0;
15357
+ for (let i = 0; i < npt; i++) {
15358
+ xyz[0] = Math.round((pts[j++] - mn[0]) * scale);
15359
+ xyz[1] = Math.round((pts[j++] - mn[1]) * scale);
15360
+ xyz[2] = Math.round((pts[j++] - mn[2]) * scale);
15361
+ const vx = xyz[0] + xyz[1] * bins + xyz[2] * binsXbins;
15362
+ this.fiberDensity[i] = densityMap[vx] / mx;
15363
+ }
15364
+ }
15144
15365
  // not included in public docs
15145
15366
  // internal function filters tractogram to identify which color and visibility of streamlines
15146
15367
  updateFibers(gl) {
@@ -15158,8 +15379,8 @@ var NVMesh2 = class _NVMesh {
15158
15379
  const vEnd3 = (offsetPt0[i + 1] - 1) * 3;
15159
15380
  let len = 0;
15160
15381
  for (let j = vStart3; j < vEnd3; j += 3) {
15161
- const v = vec34.fromValues(pts[j + 0] - pts[j + 3], pts[j + 1] - pts[j + 4], pts[j + 2] - pts[j + 5]);
15162
- len += vec34.len(v);
15382
+ const v = vec35.fromValues(pts[j + 0] - pts[j + 3], pts[j + 1] - pts[j + 4], pts[j + 2] - pts[j + 5]);
15383
+ len += vec35.len(v);
15163
15384
  }
15164
15385
  this.fiberLengths[i] = len;
15165
15386
  }
@@ -15186,8 +15407,8 @@ var NVMesh2 = class _NVMesh {
15186
15407
  return r + (g << 8) + (b << 16);
15187
15408
  }
15188
15409
  function direction2rgb(x1, y1, z1, x2, y2, z2, ditherFrac) {
15189
- const v = vec34.fromValues(Math.abs(x1 - x2), Math.abs(y1 - y2), Math.abs(z1 - z2));
15190
- vec34.normalize(v, v);
15410
+ const v = vec35.fromValues(Math.abs(x1 - x2), Math.abs(y1 - y2), Math.abs(z1 - z2));
15411
+ vec35.normalize(v, v);
15191
15412
  const r = ditherFrac - ditherHalf;
15192
15413
  for (let j = 0; j < 3; j++) {
15193
15414
  v[j] = 255 * Math.max(Math.min(Math.abs(v[j]) + r, 1), 0);
@@ -15349,6 +15570,34 @@ var NVMesh2 = class _NVMesh {
15349
15570
  }
15350
15571
  }
15351
15572
  }
15573
+ if (this.fiberOcclusion > 0) {
15574
+ let shadeRGBA = function(rgba, frac) {
15575
+ const r = frac * (rgba & 255);
15576
+ const g = frac * (rgba >> 8 & 255);
15577
+ const b = frac * (rgba >> 16 & 255);
15578
+ return r + (g << 8) + (b << 16);
15579
+ };
15580
+ this.createFiberDensityMap();
15581
+ for (let i = 0; i < n_count; i++) {
15582
+ const vStart = offsetPt0[i];
15583
+ const vEnd = offsetPt0[i + 1] - 1;
15584
+ const vStart4 = vStart * 4 + 3;
15585
+ const vEnd4 = vEnd * 4 + 3;
15586
+ let vtx = vStart;
15587
+ const bias = Math.min(this.fiberOcclusion, 0.99);
15588
+ for (let j = vStart4; j <= vEnd4; j += 4) {
15589
+ let shade = this.fiberDensity[vtx++];
15590
+ if (shade <= 0) {
15591
+ continue;
15592
+ }
15593
+ shade = shade / ((1 / bias - 2) * (1 - shade) + 1);
15594
+ const frac = 1 - Math.min(shade, 0.9);
15595
+ let RGBA = posClrU32[j];
15596
+ RGBA = shadeRGBA(RGBA, frac);
15597
+ posClrU32[j] = RGBA;
15598
+ }
15599
+ }
15600
+ }
15352
15601
  const min_mm = this.fiberLength;
15353
15602
  const primitiveRestart = Math.pow(2, 32) - 1;
15354
15603
  const indices = [];
@@ -15657,10 +15906,9 @@ var NVMesh2 = class _NVMesh {
15657
15906
  }
15658
15907
  if (maxAdditiveBlend > 0) {
15659
15908
  for (let j = 0; j < nvtx; j++) {
15660
- let modulate2 = function(x, y) {
15909
+ let modulate = function(x, y) {
15661
15910
  return Math.min(x * y * (1 / 255), 255);
15662
15911
  };
15663
- var modulate = modulate2;
15664
15912
  let vtx = j * 28 + 24;
15665
15913
  if (this.f32PerVertex !== 7) {
15666
15914
  vtx = j * 20 + 16;
@@ -15670,9 +15918,9 @@ var NVMesh2 = class _NVMesh {
15670
15918
  if (opacity <= 0) {
15671
15919
  continue;
15672
15920
  }
15673
- u8[vtx + 0] = modulate2(u8[vtx + 0], additiveRGBA[v + 0]);
15674
- u8[vtx + 1] = modulate2(u8[vtx + 1], additiveRGBA[v + 1]);
15675
- u8[vtx + 2] = modulate2(u8[vtx + 2], additiveRGBA[v + 2]);
15921
+ u8[vtx + 0] = modulate(u8[vtx + 0], additiveRGBA[v + 0]);
15922
+ u8[vtx + 1] = modulate(u8[vtx + 1], additiveRGBA[v + 1]);
15923
+ u8[vtx + 2] = modulate(u8[vtx + 2], additiveRGBA[v + 2]);
15676
15924
  u8[vtx + 0] = lerp(u8[vtx + 0], additiveRGBA[v + 0], opacity);
15677
15925
  u8[vtx + 1] = lerp(u8[vtx + 1], additiveRGBA[v + 1], opacity);
15678
15926
  u8[vtx + 2] = lerp(u8[vtx + 2], additiveRGBA[v + 2], opacity);
@@ -17753,140 +18001,6 @@ var Roboto_Regular_default2 = {
17753
18001
  import { serialize, deserialize } from "@ungap/structured-clone";
17754
18002
  import { vec3 as vec38, vec4 as vec46 } from "gl-matrix";
17755
18003
 
17756
- // src/nvutilities.ts
17757
- import arrayEqual from "array-equal";
17758
- import { compressSync, decompressSync as decompressSync2, strToU8 } from "fflate/browser";
17759
- import { mat4 as mat43, vec3 as vec35, vec4 as vec44 } from "gl-matrix";
17760
- var NVUtilities = class _NVUtilities {
17761
- static arrayBufferToBase64(arrayBuffer) {
17762
- const bytes = new Uint8Array(arrayBuffer);
17763
- return _NVUtilities.uint8tob64(bytes);
17764
- }
17765
- /*
17766
- https://gist.github.com/jonleighton/958841
17767
- MIT LICENSE
17768
- Copyright 2011 Jon Leighton
17769
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
17770
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
17771
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17772
- */
17773
- static uint8tob64(bytes) {
17774
- let base64 = "";
17775
- const encodings = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
17776
- const byteLength = bytes.byteLength;
17777
- const byteRemainder = byteLength % 3;
17778
- const mainLength = byteLength - byteRemainder;
17779
- let a, b, c, d;
17780
- let chunk;
17781
- for (let i = 0; i < mainLength; i = i + 3) {
17782
- chunk = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
17783
- a = (chunk & 16515072) >> 18;
17784
- b = (chunk & 258048) >> 12;
17785
- c = (chunk & 4032) >> 6;
17786
- d = chunk & 63;
17787
- base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
17788
- }
17789
- if (byteRemainder === 1) {
17790
- chunk = bytes[mainLength];
17791
- a = (chunk & 252) >> 2;
17792
- b = (chunk & 3) << 4;
17793
- base64 += encodings[a] + encodings[b] + "==";
17794
- } else if (byteRemainder === 2) {
17795
- chunk = bytes[mainLength] << 8 | bytes[mainLength + 1];
17796
- a = (chunk & 64512) >> 10;
17797
- b = (chunk & 1008) >> 4;
17798
- c = (chunk & 15) << 2;
17799
- base64 += encodings[a] + encodings[b] + encodings[c] + "=";
17800
- }
17801
- return base64;
17802
- }
17803
- // https://stackoverflow.com/questions/34156282/how-do-i-save-json-to-local-text-file
17804
- static download(content, fileName, contentType) {
17805
- const a = document.createElement("a");
17806
- const file = new Blob([content], { type: contentType });
17807
- a.href = URL.createObjectURL(file);
17808
- a.download = fileName;
17809
- a.click();
17810
- }
17811
- static readFileAsync(file) {
17812
- return new Promise((resolve, reject) => {
17813
- const reader = new FileReader();
17814
- reader.onload = () => {
17815
- resolve(reader.result);
17816
- };
17817
- reader.onerror = reject;
17818
- reader.readAsArrayBuffer(file);
17819
- });
17820
- }
17821
- static blobToBase64(blob) {
17822
- return new Promise((resolve) => {
17823
- const reader = new FileReader();
17824
- reader.onloadend = () => resolve(reader.result);
17825
- reader.readAsDataURL(blob);
17826
- });
17827
- }
17828
- static decompressBase64String(base64) {
17829
- const compressed = atob(base64);
17830
- const compressedBuffer = new ArrayBuffer(compressed.length);
17831
- const compressedView = new Uint8Array(compressedBuffer);
17832
- for (let i = 0; i < compressed.length; i++) {
17833
- compressedView[i] = compressed.charCodeAt(i);
17834
- }
17835
- const decompressedBuffer = decompressSync2(compressedView);
17836
- const decompressed = new TextDecoder("utf-8").decode(decompressedBuffer);
17837
- return decompressed;
17838
- }
17839
- static compressToBase64String(string) {
17840
- const buf = strToU8(string);
17841
- const compressed = compressSync(buf);
17842
- const base64 = _NVUtilities.uint8tob64(compressed);
17843
- return base64;
17844
- }
17845
- static arraysAreEqual(a, b) {
17846
- return arrayEqual(a, b);
17847
- }
17848
- /**
17849
- * Generate a pre-filled number array.
17850
- *
17851
- * @param start - start value
17852
- * @param stop - stop value
17853
- * @param step - step value
17854
- * @returns filled number array
17855
- */
17856
- static range(start, stop, step) {
17857
- return Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + i * step);
17858
- }
17859
- /**
17860
- * convert spherical AZIMUTH, ELEVATION to Cartesian
17861
- * @param azimuth - azimuth number
17862
- * @param elevation - elevation number
17863
- * @returns the converted [x, y, z] coordinates
17864
- * @example
17865
- * xyz = NVUtilities.sph2cartDeg(42, 42)
17866
- */
17867
- static sph2cartDeg(azimuth, elevation) {
17868
- const Phi = -elevation * (Math.PI / 180);
17869
- const Theta = (azimuth - 90) % 360 * (Math.PI / 180);
17870
- const ret = [Math.cos(Phi) * Math.cos(Theta), Math.cos(Phi) * Math.sin(Theta), Math.sin(Phi)];
17871
- const len = Math.sqrt(ret[0] * ret[0] + ret[1] * ret[1] + ret[2] * ret[2]);
17872
- if (len <= 0) {
17873
- return ret;
17874
- }
17875
- ret[0] /= len;
17876
- ret[1] /= len;
17877
- ret[2] /= len;
17878
- return ret;
17879
- }
17880
- static vox2mm(XYZ, mtx) {
17881
- const sform = mat43.clone(mtx);
17882
- mat43.transpose(sform, sform);
17883
- const pos = vec44.fromValues(XYZ[0], XYZ[1], XYZ[2], 1);
17884
- vec44.transformMat4(pos, pos, sform);
17885
- const pos3 = vec35.fromValues(pos[0], pos[1], pos[2]);
17886
- return pos3;
17887
- }
17888
- };
17889
-
17890
18004
  // src/nvimage/index.ts
17891
18005
  import * as nifti from "nifti-reader-js";
17892
18006
  import daikon from "daikon";
@@ -17919,6 +18033,8 @@ var ImageType = /* @__PURE__ */ ((ImageType3) => {
17919
18033
  ImageType3[ImageType3["VMR"] = 14] = "VMR";
17920
18034
  ImageType3[ImageType3["HEAD"] = 15] = "HEAD";
17921
18035
  ImageType3[ImageType3["DCM_FOLDER"] = 16] = "DCM_FOLDER";
18036
+ ImageType3[ImageType3["SRC"] = 17] = "SRC";
18037
+ ImageType3[ImageType3["FIB"] = 18] = "FIB";
17922
18038
  return ImageType3;
17923
18039
  })(ImageType || {});
17924
18040
  var NVIMAGE_TYPE = Object.freeze({
@@ -17933,6 +18049,9 @@ var NVIMAGE_TYPE = Object.freeze({
17933
18049
  case "TXT":
17934
18050
  imageType = 3 /* DCM_MANIFEST */;
17935
18051
  break;
18052
+ case "FIB":
18053
+ imageType = 18 /* FIB */;
18054
+ break;
17936
18055
  case "NII":
17937
18056
  imageType = 1 /* NII */;
17938
18057
  break;
@@ -17960,6 +18079,9 @@ var NVIMAGE_TYPE = Object.freeze({
17960
18079
  case "MGZ":
17961
18080
  imageType = 11 /* MGZ */;
17962
18081
  break;
18082
+ case "SRC":
18083
+ imageType = 17 /* SRC */;
18084
+ break;
17963
18085
  case "V":
17964
18086
  imageType = 12 /* V */;
17965
18087
  break;
@@ -18256,6 +18378,9 @@ var NVImage = class _NVImage {
18256
18378
  __publicField(this, "imageType");
18257
18379
  __publicField(this, "img");
18258
18380
  __publicField(this, "imaginary");
18381
+ // only for complex data
18382
+ __publicField(this, "v1");
18383
+ // only for FIB files
18259
18384
  __publicField(this, "fileObject");
18260
18385
  __publicField(this, "dims");
18261
18386
  __publicField(this, "onColormapChange", () => {
@@ -18311,6 +18436,10 @@ var NVImage = class _NVImage {
18311
18436
  case NVIMAGE_TYPE.DCM:
18312
18437
  imgRaw = this.readDICOM(dataBuffer);
18313
18438
  break;
18439
+ case NVIMAGE_TYPE.FIB:
18440
+ ;
18441
+ [imgRaw, this.v1] = this.readFIB(dataBuffer);
18442
+ break;
18314
18443
  case NVIMAGE_TYPE.MIH:
18315
18444
  case NVIMAGE_TYPE.MIF:
18316
18445
  imgRaw = this.readMIF(dataBuffer, pairedImgData);
@@ -18327,6 +18456,9 @@ var NVImage = class _NVImage {
18327
18456
  case NVIMAGE_TYPE.MGZ:
18328
18457
  imgRaw = this.readMGH(dataBuffer);
18329
18458
  break;
18459
+ case NVIMAGE_TYPE.SRC:
18460
+ imgRaw = this.readSRC(dataBuffer);
18461
+ break;
18330
18462
  case NVIMAGE_TYPE.V:
18331
18463
  imgRaw = this.readECAT(dataBuffer);
18332
18464
  break;
@@ -18383,40 +18515,7 @@ var NVImage = class _NVImage {
18383
18515
  this.nFrame4D = nVol4D;
18384
18516
  }
18385
18517
  if ((this.hdr.intent_code === 1007 /* NIFTI_INTENT_VECTOR */ || this.hdr.intent_code === 2003 /* NIFTI_INTENT_RGB_VECTOR */) && this.nFrame4D === 3 && this.hdr.datatypeCode === 16 /* DT_FLOAT32 */) {
18386
- const tmp = new Float32Array(imgRaw);
18387
- const f32 = tmp.slice();
18388
- this.hdr.datatypeCode = 2304 /* DT_RGBA32 */;
18389
- this.nFrame4D = 1;
18390
- for (let i = 4; i < 7; i++) {
18391
- this.hdr.dims[i] = 1;
18392
- }
18393
- this.hdr.dims[0] = 3;
18394
- imgRaw = new Uint8Array(this.nVox3D * 4);
18395
- let mx = 1;
18396
- for (let i = 0; i < this.nVox3D * 3; i++) {
18397
- mx = Math.max(mx, Math.abs(f32[i]));
18398
- }
18399
- const slope = 255 / mx;
18400
- const nVox3D2 = this.nVox3D * 2;
18401
- let j = 0;
18402
- for (let i = 0; i < this.nVox3D; i++) {
18403
- const x = f32[i];
18404
- const y = f32[i + this.nVox3D];
18405
- const z = f32[i + nVox3D2];
18406
- imgRaw[j] = Math.abs(x * slope);
18407
- imgRaw[j + 1] = Math.abs(y * slope);
18408
- imgRaw[j + 2] = Math.abs(z * slope);
18409
- const xNeg = Number(x > 0) * 1;
18410
- const yNeg = Number(y > 0) * 2;
18411
- const zNeg = Number(z > 0) * 4;
18412
- let alpha = 248 + xNeg + yNeg + zNeg;
18413
- if (Math.abs(x) + Math.abs(y) + Math.abs(z) < 0.1) {
18414
- alpha = 0;
18415
- }
18416
- ;
18417
- imgRaw[j + 3] = alpha;
18418
- j += 4;
18419
- }
18518
+ imgRaw = this.float32V1asRGBA(new Float32Array(imgRaw));
18420
18519
  }
18421
18520
  if (this.hdr.pixDims[1] === 0 || this.hdr.pixDims[2] === 0 || this.hdr.pixDims[3] === 0) {
18422
18521
  log.error("pixDims not plausible", this.hdr);
@@ -18639,6 +18738,75 @@ var NVImage = class _NVImage {
18639
18738
  }
18640
18739
  return oblique_angle;
18641
18740
  }
18741
+ float32V1asRGBA(inImg) {
18742
+ if (inImg.length !== this.nVox3D * 3) {
18743
+ log.warn("float32V1asRGBA() expects " + this.nVox3D * 3 + "voxels, got ", +inImg.length);
18744
+ }
18745
+ const f32 = inImg.slice();
18746
+ this.hdr.datatypeCode = 2304 /* DT_RGBA32 */;
18747
+ this.nFrame4D = 1;
18748
+ for (let i = 4; i < 7; i++) {
18749
+ this.hdr.dims[i] = 1;
18750
+ }
18751
+ this.hdr.dims[0] = 3;
18752
+ const imgRaw = new Uint8Array(this.nVox3D * 4);
18753
+ let mx = 1;
18754
+ for (let i = 0; i < this.nVox3D * 3; i++) {
18755
+ if (isNaN(f32[i])) {
18756
+ continue;
18757
+ }
18758
+ mx = Math.max(mx, Math.abs(f32[i]));
18759
+ }
18760
+ const slope = 255 / mx;
18761
+ const nVox3D2 = this.nVox3D * 2;
18762
+ let j = 0;
18763
+ for (let i = 0; i < this.nVox3D; i++) {
18764
+ const x = f32[i];
18765
+ const y = f32[i + this.nVox3D];
18766
+ const z = f32[i + nVox3D2];
18767
+ imgRaw[j] = Math.abs(x * slope);
18768
+ imgRaw[j + 1] = Math.abs(y * slope);
18769
+ imgRaw[j + 2] = Math.abs(z * slope);
18770
+ const xNeg = Number(x > 0) * 1;
18771
+ const yNeg = Number(y > 0) * 2;
18772
+ const zNeg = Number(z > 0) * 4;
18773
+ let alpha = 248 + xNeg + yNeg + zNeg;
18774
+ if (Math.abs(x) + Math.abs(y) + Math.abs(z) < 0.1) {
18775
+ alpha = 0;
18776
+ }
18777
+ ;
18778
+ imgRaw[j + 3] = alpha;
18779
+ j += 4;
18780
+ }
18781
+ return imgRaw;
18782
+ }
18783
+ loadImgV1(isFlipX = false, isFlipY = false, isFlipZ = false) {
18784
+ let v1 = this.v1;
18785
+ if (!v1 && this.nFrame4D === 3 && this.img.constructor === Float32Array) {
18786
+ v1 = this.img.slice();
18787
+ }
18788
+ if (!v1) {
18789
+ log.warn("Image does not have V1 data");
18790
+ return false;
18791
+ }
18792
+ if (isFlipX) {
18793
+ for (let i = 0; i < this.nVox3D; i++) {
18794
+ v1[i] = -v1[i];
18795
+ }
18796
+ }
18797
+ if (isFlipY) {
18798
+ for (let i = this.nVox3D; i < 2 * this.nVox3D; i++) {
18799
+ v1[i] = -v1[i];
18800
+ }
18801
+ }
18802
+ if (isFlipZ) {
18803
+ for (let i = 2 * this.nVox3D; i < 3 * this.nVox3D; i++) {
18804
+ v1[i] = -v1[i];
18805
+ }
18806
+ }
18807
+ this.img = this.float32V1asRGBA(v1);
18808
+ return true;
18809
+ }
18642
18810
  // not included in public docs
18643
18811
  // detect difference between voxel grid and world space
18644
18812
  calculateOblique() {
@@ -19213,6 +19381,142 @@ var NVImage = class _NVImage {
19213
19381
  }
19214
19382
  // readMGH()
19215
19383
  // not included in public docs
19384
+ // read DSI-Studio FIB format image
19385
+ // https://dsi-studio.labsolver.org/doc/cli_data.html
19386
+ readFIB(buffer) {
19387
+ this.hdr = new nifti.NIFTI1();
19388
+ const hdr = this.hdr;
19389
+ hdr.littleEndian = false;
19390
+ hdr.dims = [3, 1, 1, 1, 0, 0, 0, 0];
19391
+ hdr.pixDims = [1, 1, 1, 1, 1, 0, 0, 0];
19392
+ const mat = NVUtilities.readMatV4(buffer);
19393
+ if (!("dimension" in mat) || !("dti_fa" in mat)) {
19394
+ throw new Error("Not a valid DSIstudio FIB file");
19395
+ }
19396
+ const hasV1 = "index0" in mat && "index1" in mat && "index2" in mat;
19397
+ hdr.numBitsPerVoxel = 32;
19398
+ hdr.datatypeCode = 16 /* DT_FLOAT32 */;
19399
+ hdr.dims[1] = mat.dimension[0];
19400
+ hdr.dims[2] = mat.dimension[1];
19401
+ hdr.dims[3] = mat.dimension[2];
19402
+ hdr.dims[4] = 1;
19403
+ hdr.pixDims[1] = mat.voxel_size[0];
19404
+ hdr.pixDims[2] = mat.voxel_size[1];
19405
+ hdr.pixDims[3] = mat.voxel_size[2];
19406
+ hdr.sform_code = 1;
19407
+ const xmm = (hdr.dims[1] - 1) * 0.5 * hdr.pixDims[1];
19408
+ const ymm = (hdr.dims[2] - 1) * 0.5 * hdr.pixDims[2];
19409
+ const zmm = (hdr.dims[3] - 1) * 0.5 * hdr.pixDims[3];
19410
+ hdr.affine = [
19411
+ [hdr.pixDims[1], 0, 0, -xmm],
19412
+ [0, -hdr.pixDims[2], 0, ymm],
19413
+ [0, 0, hdr.pixDims[2], -zmm],
19414
+ [0, 0, 0, 1]
19415
+ ];
19416
+ hdr.littleEndian = true;
19417
+ const nVox3D = hdr.dims[1] * hdr.dims[2] * hdr.dims[3];
19418
+ const nBytes3D = nVox3D * Math.ceil(hdr.numBitsPerVoxel / 8);
19419
+ const nBytes = nBytes3D * hdr.dims[4];
19420
+ const buff8v1 = new Uint8Array(new ArrayBuffer(nVox3D * 4 * 3));
19421
+ if (hasV1) {
19422
+ const nvox = hdr.dims[1] * hdr.dims[2] * hdr.dims[3];
19423
+ const dir0 = new Float32Array(nvox);
19424
+ const dir1 = new Float32Array(nvox);
19425
+ const dir2 = new Float32Array(nvox);
19426
+ const idxs = mat.index0;
19427
+ const dirs = mat.odf_vertices;
19428
+ for (let i = 0; i < nvox; i++) {
19429
+ const idx = idxs[i] * 3;
19430
+ dir0[i] = dirs[idx + 0];
19431
+ dir1[i] = dirs[idx + 1];
19432
+ dir2[i] = -dirs[idx + 2];
19433
+ }
19434
+ buff8v1.set(new Uint8Array(dir0.buffer, dir0.byteOffset, dir0.byteLength), 0 * nBytes3D);
19435
+ buff8v1.set(new Uint8Array(dir1.buffer, dir1.byteOffset, dir1.byteLength), 1 * nBytes3D);
19436
+ buff8v1.set(new Uint8Array(dir2.buffer, dir2.byteOffset, dir2.byteLength), 2 * nBytes3D);
19437
+ }
19438
+ const buff8 = new Uint8Array(new ArrayBuffer(nBytes));
19439
+ const arrFA = Float32Array.from(mat.dti_fa);
19440
+ const imgFA = new Uint8Array(arrFA.buffer, arrFA.byteOffset, arrFA.byteLength);
19441
+ buff8.set(imgFA, 0);
19442
+ if ("report" in mat) {
19443
+ hdr.description = new TextDecoder().decode(mat.report.subarray(0, Math.min(79, mat.report.byteLength)));
19444
+ }
19445
+ return [buff8.buffer, new Float32Array(buff8v1.buffer)];
19446
+ }
19447
+ // readFIB()
19448
+ // not included in public docs
19449
+ // read DSI-Studio SRC format image
19450
+ // https://dsi-studio.labsolver.org/doc/cli_data.html
19451
+ readSRC(buffer) {
19452
+ this.hdr = new nifti.NIFTI1();
19453
+ const hdr = this.hdr;
19454
+ hdr.littleEndian = false;
19455
+ hdr.dims = [3, 1, 1, 1, 0, 0, 0, 0];
19456
+ hdr.pixDims = [1, 1, 1, 1, 1, 0, 0, 0];
19457
+ const mat = NVUtilities.readMatV4(buffer);
19458
+ if (!("dimension" in mat) || !("image0" in mat)) {
19459
+ throw new Error("Not a valid DSIstudio SRC file");
19460
+ }
19461
+ let n = 0;
19462
+ let len = 0;
19463
+ for (const [key, value] of Object.entries(mat)) {
19464
+ if (!key.startsWith("image")) {
19465
+ continue;
19466
+ }
19467
+ if (n === 0) {
19468
+ len = value.length;
19469
+ } else if (len !== value.length) {
19470
+ len = -1;
19471
+ }
19472
+ if (value.constructor !== Uint16Array) {
19473
+ throw new Error("DSIstudio SRC files always use Uint16 datatype");
19474
+ }
19475
+ n++;
19476
+ }
19477
+ if (len < 1 || n < 1) {
19478
+ throw new Error("SRC file not valid DSI Studio data. The image(s) should have the same length");
19479
+ }
19480
+ hdr.numBitsPerVoxel = 16;
19481
+ hdr.datatypeCode = 512 /* DT_UINT16 */;
19482
+ hdr.dims[1] = mat.dimension[0];
19483
+ hdr.dims[2] = mat.dimension[1];
19484
+ hdr.dims[3] = mat.dimension[2];
19485
+ hdr.dims[4] = n;
19486
+ if (hdr.dims[4] > 1) {
19487
+ hdr.dims[0] = 4;
19488
+ }
19489
+ hdr.pixDims[1] = mat.voxel_size[0];
19490
+ hdr.pixDims[2] = mat.voxel_size[1];
19491
+ hdr.pixDims[3] = mat.voxel_size[2];
19492
+ hdr.sform_code = 1;
19493
+ const xmm = (hdr.dims[1] - 1) * 0.5 * hdr.pixDims[1];
19494
+ const ymm = (hdr.dims[2] - 1) * 0.5 * hdr.pixDims[2];
19495
+ const zmm = (hdr.dims[3] - 1) * 0.5 * hdr.pixDims[3];
19496
+ hdr.affine = [
19497
+ [hdr.pixDims[1], 0, 0, -xmm],
19498
+ [0, -hdr.pixDims[2], 0, ymm],
19499
+ [0, 0, hdr.pixDims[2], -zmm],
19500
+ [0, 0, 0, 1]
19501
+ ];
19502
+ hdr.littleEndian = true;
19503
+ const nBytes3D = hdr.dims[1] * hdr.dims[2] * hdr.dims[3] * (hdr.numBitsPerVoxel / 8);
19504
+ const nBytes = nBytes3D * hdr.dims[4];
19505
+ const buff8 = new Uint8Array(new ArrayBuffer(nBytes));
19506
+ let offset = 0;
19507
+ for (let i = 0; i < n; i++) {
19508
+ const arr = mat[`image${i}`];
19509
+ const img8 = new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
19510
+ buff8.set(img8, offset);
19511
+ offset += nBytes3D;
19512
+ }
19513
+ if ("report" in mat) {
19514
+ hdr.description = new TextDecoder().decode(mat.report.subarray(0, Math.min(79, mat.report.byteLength)));
19515
+ }
19516
+ return buff8.buffer;
19517
+ }
19518
+ // readSRC()
19519
+ // not included in public docs
19216
19520
  // read AFNI head/brik format image
19217
19521
  readHEAD(dataBuffer, pairedImgData) {
19218
19522
  this.hdr = new nifti.NIFTI1();
@@ -19531,6 +19835,7 @@ var NVImage = class _NVImage {
19531
19835
  throw new Error("Not a valid MIF file");
19532
19836
  }
19533
19837
  const layout = [];
19838
+ let isBit = false;
19534
19839
  let nTransform = 0;
19535
19840
  let TR = 0;
19536
19841
  let isDetached = false;
@@ -19569,7 +19874,10 @@ var NVImage = class _NVImage {
19569
19874
  case "datatype":
19570
19875
  {
19571
19876
  const dt = items[0];
19572
- if (dt.startsWith("Int8")) {
19877
+ if (dt.startsWith("Bit")) {
19878
+ isBit = true;
19879
+ hdr.datatypeCode = 2 /* DT_UINT8 */;
19880
+ } else if (dt.startsWith("Int8")) {
19573
19881
  hdr.datatypeCode = 256 /* DT_INT8 */;
19574
19882
  } else if (dt.startsWith("UInt8")) {
19575
19883
  hdr.datatypeCode = 2 /* DT_UINT8 */;
@@ -19616,6 +19924,9 @@ var NVImage = class _NVImage {
19616
19924
  hdr.affine[nTransform][3] = parseFloat(items[3]);
19617
19925
  nTransform++;
19618
19926
  break;
19927
+ case "comments":
19928
+ hdr.description = items[0].substring(0, Math.min(79, items[0].length));
19929
+ break;
19619
19930
  case "RepetitionTime":
19620
19931
  TR = parseFloat(items[0]);
19621
19932
  break;
@@ -19636,7 +19947,6 @@ var NVImage = class _NVImage {
19636
19947
  for (let i = 0; i < ndim; i++) {
19637
19948
  nvox *= Math.max(hdr.dims[i + 1], 1);
19638
19949
  }
19639
- log.debug(nvox);
19640
19950
  for (let i = 0; i < 3; i++) {
19641
19951
  for (let j2 = 0; j2 < 3; j2++) {
19642
19952
  hdr.affine[i][j2] *= hdr.pixDims[j2 + 1];
@@ -19652,6 +19962,20 @@ var NVImage = class _NVImage {
19652
19962
  let rawImg;
19653
19963
  if (pairedImgData && isDetached) {
19654
19964
  rawImg = pairedImgData.slice(0);
19965
+ } else if (isBit) {
19966
+ hdr.numBitsPerVoxel = 8;
19967
+ const img8 = new Uint8Array(nvox);
19968
+ const buffer1 = buffer.slice(hdr.vox_offset, hdr.vox_offset + Math.ceil(nvox / 8));
19969
+ const img1 = new Uint8Array(buffer1);
19970
+ let j2 = 0;
19971
+ for (let i = 0; i < nvox; i++) {
19972
+ const bit = i % 8;
19973
+ img8[i] = img1[j2] >> 7 - bit & 1;
19974
+ if (bit === 7) {
19975
+ j2++;
19976
+ }
19977
+ }
19978
+ rawImg = img8.buffer;
19655
19979
  } else {
19656
19980
  rawImg = buffer.slice(hdr.vox_offset, hdr.vox_offset + nvox * (hdr.numBitsPerVoxel / 8));
19657
19981
  }
@@ -19712,25 +20036,41 @@ var NVImage = class _NVImage {
19712
20036
  let j = 0;
19713
20037
  let inVs;
19714
20038
  let outVs;
19715
- switch (hdr.numBitsPerVoxel) {
19716
- case 8:
20039
+ switch (hdr.datatypeCode) {
20040
+ case 256 /* DT_INT8 */:
20041
+ inVs = new Int8Array(rawImg);
20042
+ outVs = new Int8Array(nvox);
20043
+ break;
20044
+ case 2 /* DT_UINT8 */:
19717
20045
  inVs = new Uint8Array(rawImg);
19718
20046
  outVs = new Uint8Array(nvox);
19719
20047
  break;
19720
- case 16:
20048
+ case 4 /* DT_INT16 */:
20049
+ inVs = new Int16Array(rawImg);
20050
+ outVs = new Int16Array(nvox);
20051
+ break;
20052
+ case 512 /* DT_UINT16 */:
19721
20053
  inVs = new Uint16Array(rawImg);
19722
20054
  outVs = new Uint16Array(nvox);
19723
20055
  break;
19724
- case 32:
20056
+ case 8 /* DT_INT32 */:
20057
+ inVs = new Int32Array(rawImg);
20058
+ outVs = new Int32Array(nvox);
20059
+ break;
20060
+ case 768 /* DT_UINT32 */:
19725
20061
  inVs = new Uint32Array(rawImg);
19726
20062
  outVs = new Uint32Array(nvox);
19727
20063
  break;
19728
- case 64:
19729
- inVs = new BigUint64Array(rawImg);
19730
- outVs = new BigUint64Array(nvox);
20064
+ case 16 /* DT_FLOAT32 */:
20065
+ inVs = new Float32Array(rawImg);
20066
+ outVs = new Float32Array(nvox);
20067
+ break;
20068
+ case 64 /* DT_FLOAT64 */:
20069
+ inVs = new Float64Array(rawImg);
20070
+ outVs = new Float64Array(nvox);
19731
20071
  break;
19732
20072
  default:
19733
- throw new Error("unknown numBitsPerVoxel");
20073
+ throw new Error("unknown datatypeCode");
19734
20074
  }
19735
20075
  for (let d = 0; d < hdr.dims[5]; d++) {
19736
20076
  for (let t = 0; t < hdr.dims[4]; t++) {
@@ -19917,7 +20257,7 @@ var NVImage = class _NVImage {
19917
20257
  }
19918
20258
  break;
19919
20259
  case "space":
19920
- if (value.includes("right-anterior-superior") || value.includes("RAS")) {
20260
+ if (value.includes("right-anterior-superior") || value.includes("ras")) {
19921
20261
  rot33 = mat3.fromValues(
19922
20262
  1,
19923
20263
  0,
@@ -19929,7 +20269,7 @@ var NVImage = class _NVImage {
19929
20269
  0,
19930
20270
  1
19931
20271
  );
19932
- } else if (value.includes("left-anterior-superior") || value.includes("LAS")) {
20272
+ } else if (value.includes("left-anterior-superior") || value.includes("las")) {
19933
20273
  rot33 = mat3.fromValues(
19934
20274
  -1,
19935
20275
  0,
@@ -19941,7 +20281,7 @@ var NVImage = class _NVImage {
19941
20281
  0,
19942
20282
  1
19943
20283
  );
19944
- } else if (value.includes("left-posterior-superior") || value.includes("LPS")) {
20284
+ } else if (value.includes("left-posterior-superior") || value.includes("lps")) {
19945
20285
  rot33 = mat3.fromValues(
19946
20286
  -1,
19947
20287
  0,
@@ -20349,6 +20689,9 @@ var NVImage = class _NVImage {
20349
20689
  mx = Math.max(this.img[i], mx);
20350
20690
  }
20351
20691
  }
20692
+ if (this.ignoreZeroVoxels && mn === mx && nZero > 0) {
20693
+ mn = 0;
20694
+ }
20352
20695
  const mnScale = this.intensityRaw2Scaled(mn);
20353
20696
  const mxScale = this.intensityRaw2Scaled(mx);
20354
20697
  const cmap = cmapper.colormapFromKey(this._colormap);
@@ -20519,8 +20862,8 @@ var NVImage = class _NVImage {
20519
20862
  odata.set(opad, hdrBytes.length);
20520
20863
  odata.set(img8, hdrBytes.length + opad.length);
20521
20864
  let saveData = null;
20522
- const compress = fnm.endsWith(".gz");
20523
- if (compress) {
20865
+ const compress2 = fnm.endsWith(".gz");
20866
+ if (compress2) {
20524
20867
  saveData = gzipSync(odata, {
20525
20868
  // GZIP-specific: the filename to use when decompressed
20526
20869
  filename: fnm,
@@ -21428,7 +21771,7 @@ var NVImage = class _NVImage {
21428
21771
  */
21429
21772
  toUint8Array(drawingBytes = null) {
21430
21773
  const isDrawing = drawingBytes !== null;
21431
- const hdrBytes = hdrToArrayBuffer(this.hdr, isDrawing);
21774
+ const hdrBytes = hdrToArrayBuffer({ ...this.hdr, vox_offset: 352 }, isDrawing);
21432
21775
  let drawingBytesToBeConverted = drawingBytes;
21433
21776
  if (isDrawing) {
21434
21777
  const perm = this.permRAS;
@@ -21603,6 +21946,9 @@ var DEFAULT_OPTIONS = {
21603
21946
  fontColor: [0.5, 0.5, 0.5, 1],
21604
21947
  selectionBoxColor: [1, 1, 1, 0.5],
21605
21948
  clipPlaneColor: [0.7, 0, 0.7, 0.5],
21949
+ clipThick: 2,
21950
+ clipVolumeLow: [0, 0, 0],
21951
+ clipVolumeHigh: [1, 1, 1],
21606
21952
  rulerColor: [1, 0, 0, 0.8],
21607
21953
  colorbarMargin: 0.05,
21608
21954
  trustCalMinMax: true,
@@ -21658,7 +22004,10 @@ var INITIAL_SCENE_DATA = {
21658
22004
  clipPlane: [0, 0, 0, 0],
21659
22005
  clipPlaneDepthAziElev: [2, 0, 0],
21660
22006
  volScaleMultiplier: 1,
21661
- pan2Dxyzmm: vec46.fromValues(0, 0, 0, 1)
22007
+ pan2Dxyzmm: vec46.fromValues(0, 0, 0, 1),
22008
+ clipThick: 2,
22009
+ clipVolumeLow: [0, 0, 0],
22010
+ clipVolumeHigh: [1, 1, 1]
21662
22011
  };
21663
22012
  var NVDocument = class _NVDocument {
21664
22013
  constructor() {
@@ -21684,7 +22033,7 @@ var NVDocument = class _NVDocument {
21684
22033
  },
21685
22034
  onZoom3DChange: () => {
21686
22035
  },
21687
- sceneData: INITIAL_SCENE_DATA,
22036
+ sceneData: { ...INITIAL_SCENE_DATA },
21688
22037
  get renderAzimuth() {
21689
22038
  return this.sceneData.azimuth;
21690
22039
  },
@@ -21805,6 +22154,12 @@ var NVDocument = class _NVDocument {
21805
22154
  set labels(labels) {
21806
22155
  this.data.labels = labels;
21807
22156
  }
22157
+ get customData() {
22158
+ return this.data.customData;
22159
+ }
22160
+ set customData(data) {
22161
+ this.data.customData = data;
22162
+ }
21808
22163
  /**
21809
22164
  * Checks if document has an image by id
21810
22165
  */
@@ -21877,6 +22232,7 @@ var NVDocument = class _NVDocument {
21877
22232
  data.opts.meshThicknessOn2D = "infinity";
21878
22233
  }
21879
22234
  data.labels = [...this.data.labels];
22235
+ data.customData = this.customData;
21880
22236
  if (this.volumes.length) {
21881
22237
  let imageOptions = this.imageOptionsArray[0];
21882
22238
  if (!imageOptions) {
@@ -22008,7 +22364,6 @@ var NVDocument = class _NVDocument {
22008
22364
  edges: mesh.edges && Array.isArray(mesh.edges) ? [...mesh.edges] : [],
22009
22365
  extentsMax: mesh.extentsMax,
22010
22366
  extentsMin: mesh.extentsMin,
22011
- fiberGroupColormap: mesh.fiberGroupColormap,
22012
22367
  furthestVertexFromOrigin: mesh.furthestVertexFromOrigin,
22013
22368
  nodeColormap: mesh.nodeColormap,
22014
22369
  nodeColormapNegative: mesh.nodeColormapNegative,
@@ -22019,6 +22374,14 @@ var NVDocument = class _NVDocument {
22019
22374
  offsetPt0: mesh.offsetPt0,
22020
22375
  nodes: mesh.nodes
22021
22376
  };
22377
+ if (mesh.offsetPt0 && mesh.offsetPt0.length > 0) {
22378
+ copyMesh.offsetPt0 = mesh.offsetPt0;
22379
+ copyMesh.fiberGroupColormap = mesh.fiberGroupColormap;
22380
+ copyMesh.fiberColor = mesh.fiberColor;
22381
+ copyMesh.fiberDither = mesh.fiberDither;
22382
+ copyMesh.fiberRadius = mesh.fiberRadius;
22383
+ copyMesh.colormap = mesh.colormap;
22384
+ }
22022
22385
  meshes.push(copyMesh);
22023
22386
  }
22024
22387
  data.meshesString = JSON.stringify(serialize(meshes));
@@ -22027,9 +22390,17 @@ var NVDocument = class _NVDocument {
22027
22390
  /**
22028
22391
  * Downloads a JSON file with options, scene, images, meshes and drawing of {@link Niivue} instance
22029
22392
  */
22030
- download(fileName) {
22393
+ async download(fileName, compress2) {
22031
22394
  const data = this.json();
22032
- NVUtilities.download(JSON.stringify(data), fileName, "application/json");
22395
+ const dataText = JSON.stringify(data);
22396
+ const contentType = compress2 ? "application/gzip" : "application/json";
22397
+ let content;
22398
+ if (compress2) {
22399
+ content = await NVUtilities.compressStringToArrayBuffer(dataText);
22400
+ } else {
22401
+ content = JSON.stringify(data);
22402
+ }
22403
+ NVUtilities.download(content, fileName, contentType);
22033
22404
  }
22034
22405
  /**
22035
22406
  * Deserialize mesh data objects
@@ -22056,17 +22427,30 @@ var NVDocument = class _NVDocument {
22056
22427
  */
22057
22428
  static async loadFromUrl(url) {
22058
22429
  const response = await fetch(url);
22059
- const data = await response.json();
22060
- return _NVDocument.loadFromJSON(data);
22430
+ const buffer = await response.arrayBuffer();
22431
+ let documentData;
22432
+ if (NVUtilities.isArrayBufferCompressed(buffer)) {
22433
+ const documentText = await NVUtilities.decompressArrayBuffer(buffer);
22434
+ documentData = JSON.parse(documentText);
22435
+ } else {
22436
+ const utf8decoder = new TextDecoder();
22437
+ documentData = JSON.parse(utf8decoder.decode(buffer));
22438
+ }
22439
+ return _NVDocument.loadFromJSON(documentData);
22061
22440
  }
22062
22441
  /**
22063
22442
  * Factory method to return an instance of NVDocument from a File object
22064
22443
  */
22065
22444
  static async loadFromFile(file) {
22066
22445
  const arrayBuffer = await NVUtilities.readFileAsync(file);
22446
+ let dataString;
22067
22447
  const document2 = new _NVDocument();
22068
- const utf8decoder = new TextDecoder();
22069
- const dataString = utf8decoder.decode(arrayBuffer);
22448
+ if (NVUtilities.isArrayBufferCompressed(arrayBuffer)) {
22449
+ dataString = await NVUtilities.decompressArrayBuffer(arrayBuffer);
22450
+ } else {
22451
+ const utf8decoder = new TextDecoder();
22452
+ dataString = utf8decoder.decode(arrayBuffer);
22453
+ }
22070
22454
  document2.data = JSON.parse(dataString);
22071
22455
  if (document2.data.opts.meshThicknessOn2D === "infinity") {
22072
22456
  document2.data.opts.meshThicknessOn2D = Infinity;
@@ -23472,6 +23856,10 @@ var Niivue = class _Niivue {
23472
23856
  Name: "Outline",
23473
23857
  Frag: fragMeshOutlineShader
23474
23858
  },
23859
+ {
23860
+ Name: "Specular",
23861
+ Frag: fragMeshSpecularEdgeShader
23862
+ },
23475
23863
  {
23476
23864
  Name: "Toon",
23477
23865
  Frag: fragMeshToonShader
@@ -25905,6 +26293,8 @@ var Niivue = class _Niivue {
25905
26293
  this.scene.pan2Dxyzmm[1] += zoomChange * mm[1];
25906
26294
  this.scene.pan2Dxyzmm[2] += zoomChange * mm[2];
25907
26295
  this.drawScene();
26296
+ this.canvas.focus();
26297
+ this.sync();
25908
26298
  return;
25909
26299
  }
25910
26300
  this.mouseClick(x, y, posChange, isDelta);
@@ -25963,6 +26353,34 @@ var Niivue = class _Niivue {
25963
26353
  this.gl.uniform4fv(this.renderShader.uniforms.clipPlaneColor, this.opts.clipPlaneColor);
25964
26354
  this.drawScene();
25965
26355
  }
26356
+ /**
26357
+ * adjust thickness of the 3D clip plane
26358
+ * @param thick - thickness of slab. Value 0..1.73 (cube opposite corner length is sqrt(3)).
26359
+ * @example
26360
+ * niivue.setClipPlaneThick(0.3) // thin slab
26361
+ * @see {@link https://niivue.github.io/niivue/features/clipplanes.html | live demo usage}
26362
+ */
26363
+ setClipPlaneThick(thick) {
26364
+ this.opts.clipThick = thick;
26365
+ this.renderShader.use(this.gl);
26366
+ this.gl.uniform1f(this.renderShader.uniforms.clipThick, this.opts.clipThick);
26367
+ this.drawScene();
26368
+ }
26369
+ /**
26370
+ * set the clipping region for volume rendering
26371
+ * @param color - the new color. expects an array of RGBA values. values can range from 0 to 1
26372
+ * @example
26373
+ * niivue.setClipPlaneColor([0.0, 0.0, 0.2], [1.0, 1.0, 0.7]) // remove inferior 20% and superior 30%
26374
+ * @see {@link https://niivue.github.io/niivue/features/clipplanes.html | live demo usage}
26375
+ */
26376
+ setClipVolume(low, high) {
26377
+ this.opts.clipVolumeLow = [Math.min(low[0], high[0]), Math.min(low[1], high[1]), Math.min(low[2], high[2])];
26378
+ this.opts.clipVolumeHigh = [Math.max(low[0], high[0]), Math.max(low[1], high[1]), Math.max(low[2], high[2])];
26379
+ this.renderShader.use(this.gl);
26380
+ this.gl.uniform3fv(this.renderShader.uniforms.clipLo, this.opts.clipVolumeLow);
26381
+ this.gl.uniform3fv(this.renderShader.uniforms.clipHi, this.opts.clipVolumeHigh);
26382
+ this.drawScene();
26383
+ }
25966
26384
  /**
25967
26385
  * set proportion of volume rendering influenced by selected matcap.
25968
26386
  * @param gradientAmount - amount of matcap (0..1), default 0 (matte, surface normal does not influence color)
@@ -26066,6 +26484,14 @@ var Niivue = class _Niivue {
26066
26484
  }
26067
26485
  const image = NVImage.loadFromBase64({ base64: base642, ...imageOptions });
26068
26486
  if (image) {
26487
+ if (image.colormapLabel) {
26488
+ const length = Object.keys(image.colormapLabel.lut).length;
26489
+ const uint8ClampedArray = new Uint8ClampedArray(length);
26490
+ for (const key in image.colormapLabel.lut) {
26491
+ uint8ClampedArray[key] = image.colormapLabel.lut[key];
26492
+ }
26493
+ image.colormapLabel.lut = uint8ClampedArray;
26494
+ }
26069
26495
  this.addVolume(image);
26070
26496
  }
26071
26497
  }
@@ -26083,6 +26509,10 @@ var Niivue = class _Niivue {
26083
26509
  }
26084
26510
  for (const meshDataObject of document2.meshDataObjects ?? []) {
26085
26511
  const meshInit = { gl: this.gl, ...meshDataObject };
26512
+ if (meshDataObject.offsetPt0) {
26513
+ meshInit.rgba255[3] = 0;
26514
+ meshInit.tris = new Uint32Array(meshDataObject.offsetPt0);
26515
+ }
26086
26516
  log.debug(meshInit);
26087
26517
  const meshToAdd = new NVMesh2(
26088
26518
  meshInit.pts,
@@ -26097,6 +26527,13 @@ var Niivue = class _Niivue {
26097
26527
  meshInit.dps,
26098
26528
  meshInit.dpv
26099
26529
  );
26530
+ if (meshDataObject.offsetPt0) {
26531
+ meshToAdd.fiberGroupColormap = meshDataObject.fiberGroupColormap;
26532
+ meshToAdd.fiberColor = meshDataObject.fiberColor;
26533
+ meshToAdd.fiberDither = meshDataObject.fiberDither;
26534
+ meshToAdd.fiberRadius = meshDataObject.fiberRadius;
26535
+ meshToAdd.colormap = meshDataObject.colormap;
26536
+ }
26100
26537
  meshToAdd.meshShaderIndex = meshInit.meshShaderIndex;
26101
26538
  meshToAdd.layers = meshInit.layers;
26102
26539
  meshToAdd.updateMesh(this.gl);
@@ -26125,29 +26562,31 @@ var Niivue = class _Niivue {
26125
26562
  * const html = `<html><body><canvas id="gl1"></canvas><script type="module" async>
26126
26563
  ${javascript}</script></body></html>`;
26127
26564
  */
26128
- generateLoadDocumentJavaScript(canvasId, esm) {
26565
+ async generateLoadDocumentJavaScript(canvasId, esm) {
26129
26566
  const json = this.json();
26130
- const base64 = NVUtilities.compressToBase64String(JSON.stringify(json));
26567
+ const base64 = await NVUtilities.compressToBase64String(JSON.stringify(json));
26131
26568
  const javascript = `
26132
26569
  ${esm}
26133
26570
 
26134
- function saveNiivueAsHtml(pageName) {
26571
+ async function saveNiivueAsHtml(pageName) {
26135
26572
  //get new docstring
26136
26573
  const docString = nv1.json();
26137
26574
  const html =
26138
26575
  document.getElementsByTagName("html")[0]
26139
- .innerHTML.replace(base64, NVUtilities.compressToBase64String(JSON.stringify(docString)));
26576
+ .innerHTML.replace(base64, await NVUtilities.compressToBase64String(JSON.stringify(docString)));
26140
26577
  NVUtilities.download(html, pageName, "application/html");
26141
26578
  }
26142
26579
 
26143
26580
  var nv1 = new Niivue();
26144
26581
  nv1.attachTo("${canvasId}");
26145
26582
  var base64 = "${base64}";
26146
- var jsonText = NVUtilities.decompressBase64String(base64);
26147
- var json = JSON.parse(jsonText); // string -> JSON
26148
- var doc = NVDocument.loadFromJSON(json);
26149
- nv1.loadDocument(doc);
26150
- nv1.updateGLVolume();
26583
+ NVUtilities.decompressBase64String(base64).then((jsonText) => {
26584
+ var json = JSON.parse(jsonText); // string -> JSON
26585
+ var doc = NVDocument.loadFromJSON(json);
26586
+ nv1.loadDocument(doc);
26587
+ nv1.updateGLVolume();
26588
+ });
26589
+
26151
26590
  `;
26152
26591
  return javascript;
26153
26592
  }
@@ -26161,8 +26600,8 @@ var Niivue = class _Niivue {
26161
26600
  * %%javascript%%</script></body></html>`;
26162
26601
  * nv1.generateHTML("page.html", esm);
26163
26602
  */
26164
- generateHTML(canvasId = "gl1", esm) {
26165
- const javascript = this.generateLoadDocumentJavaScript(canvasId, esm);
26603
+ async generateHTML(canvasId = "gl1", esm) {
26604
+ const javascript = await this.generateLoadDocumentJavaScript(canvasId, esm);
26166
26605
  const html = `<!DOCTYPE html>
26167
26606
  <html lang="en">
26168
26607
  <head>
@@ -26240,9 +26679,9 @@ var Niivue = class _Niivue {
26240
26679
  * @param canvasId - id of canvas NiiVue will be attached to
26241
26680
  * @param esm - bundled version of NiiVue
26242
26681
  */
26243
- saveHTML(fileName = "untitled.html", canvasId = "gl1", esm) {
26244
- const html = this.generateHTML(canvasId, esm);
26245
- NVUtilities.download(html, fileName, "application/html");
26682
+ async saveHTML(fileName = "untitled.html", canvasId = "gl1", esm) {
26683
+ const html = await this.generateHTML(canvasId, esm);
26684
+ return NVUtilities.download(html, fileName, "application/html");
26246
26685
  }
26247
26686
  /**
26248
26687
  * Converts NiiVue scene to JSON
@@ -26260,18 +26699,19 @@ var Niivue = class _Niivue {
26260
26699
  /**
26261
26700
  * save the entire scene (objects and settings) as a document
26262
26701
  * @param fileName - the name of the document storing the scene
26702
+ * @param compress - whether the file should be compressed
26263
26703
  * @example
26264
26704
  * niivue.saveDocument("niivue.basic.nvd")
26265
26705
  * @see {@link https://niivue.github.io/niivue/features/document.3d.html | live demo usage}
26266
26706
  */
26267
- async saveDocument(fileName = "untitled.nvd") {
26707
+ async saveDocument(fileName = "untitled.nvd", compress2 = true) {
26268
26708
  this.document.title = fileName;
26269
26709
  log.debug("saveDocument", this.volumes[0]);
26270
26710
  this.drawScene();
26271
26711
  this.document.previewImageDataURL = this.canvas.toDataURL();
26272
26712
  this.document.volumes = this.volumes;
26273
26713
  this.document.meshes = this.meshes;
26274
- this.document.download(fileName);
26714
+ return this.document.download(fileName, compress2);
26275
26715
  }
26276
26716
  /**
26277
26717
  * load an array of volume objects
@@ -26739,7 +27179,7 @@ var Niivue = class _Niivue {
26739
27179
  Q.push(seedVx);
26740
27180
  img[seedVx] = 2;
26741
27181
  while (Q.length > 0) {
26742
- let testNeighbor2 = function(offset) {
27182
+ let testNeighbor = function(offset) {
26743
27183
  const xyzN = xyz.slice();
26744
27184
  xyzN[0] += offset[0];
26745
27185
  xyzN[1] += offset[1];
@@ -26757,42 +27197,41 @@ var Niivue = class _Niivue {
26757
27197
  img[vxT] = 2;
26758
27198
  Q.push(vxT);
26759
27199
  };
26760
- var testNeighbor = testNeighbor2;
26761
27200
  const vx = Q[0];
26762
27201
  Q.shift();
26763
27202
  const xyz = vx2xyz(vx);
26764
- testNeighbor2([0, 0, -1]);
26765
- testNeighbor2([0, 0, 1]);
26766
- testNeighbor2([0, -1, 0]);
26767
- testNeighbor2([0, 1, 0]);
26768
- testNeighbor2([-1, 0, 0]);
26769
- testNeighbor2([1, 0, 0]);
27203
+ testNeighbor([0, 0, -1]);
27204
+ testNeighbor([0, 0, 1]);
27205
+ testNeighbor([0, -1, 0]);
27206
+ testNeighbor([0, 1, 0]);
27207
+ testNeighbor([-1, 0, 0]);
27208
+ testNeighbor([1, 0, 0]);
26770
27209
  if (neighbors <= 6) {
26771
27210
  continue;
26772
27211
  }
26773
- testNeighbor2([-1, -1, 0]);
26774
- testNeighbor2([1, 1, 0]);
26775
- testNeighbor2([-1, 1, 0]);
26776
- testNeighbor2([1, 1, 0]);
26777
- testNeighbor2([0, -1, -1]);
26778
- testNeighbor2([0, 1, -1]);
26779
- testNeighbor2([-1, 0, -1]);
26780
- testNeighbor2([1, 0, -1]);
26781
- testNeighbor2([0, -1, 1]);
26782
- testNeighbor2([0, 1, 1]);
26783
- testNeighbor2([-1, 0, 1]);
26784
- testNeighbor2([1, 0, 1]);
27212
+ testNeighbor([-1, -1, 0]);
27213
+ testNeighbor([1, 1, 0]);
27214
+ testNeighbor([-1, 1, 0]);
27215
+ testNeighbor([1, 1, 0]);
27216
+ testNeighbor([0, -1, -1]);
27217
+ testNeighbor([0, 1, -1]);
27218
+ testNeighbor([-1, 0, -1]);
27219
+ testNeighbor([1, 0, -1]);
27220
+ testNeighbor([0, -1, 1]);
27221
+ testNeighbor([0, 1, 1]);
27222
+ testNeighbor([-1, 0, 1]);
27223
+ testNeighbor([1, 0, 1]);
26785
27224
  if (neighbors <= 18) {
26786
27225
  continue;
26787
27226
  }
26788
- testNeighbor2([-1, -1, -1]);
26789
- testNeighbor2([1, -1, -1]);
26790
- testNeighbor2([-1, 1, -1]);
26791
- testNeighbor2([1, 1, -1]);
26792
- testNeighbor2([-1, -1, 1]);
26793
- testNeighbor2([1, -1, 1]);
26794
- testNeighbor2([-1, 1, 1]);
26795
- testNeighbor2([1, 1, 1]);
27227
+ testNeighbor([-1, -1, -1]);
27228
+ testNeighbor([1, -1, -1]);
27229
+ testNeighbor([-1, 1, -1]);
27230
+ testNeighbor([1, 1, -1]);
27231
+ testNeighbor([-1, -1, 1]);
27232
+ testNeighbor([1, -1, 1]);
27233
+ testNeighbor([-1, 1, 1]);
27234
+ testNeighbor([1, 1, 1]);
26796
27235
  }
26797
27236
  }
26798
27237
  // not included in public docs
@@ -28198,6 +28637,9 @@ var Niivue = class _Niivue {
28198
28637
  const volScale = slicescl.volScale;
28199
28638
  this.gl.uniform1f(this.renderShader.uniforms.overlays, this.overlays);
28200
28639
  this.gl.uniform4fv(this.renderShader.uniforms.clipPlaneColor, this.opts.clipPlaneColor);
28640
+ this.gl.uniform1f(this.renderShader.uniforms.clipThick, this.opts.clipThick);
28641
+ this.gl.uniform3fv(this.renderShader.uniforms.clipLo, this.opts.clipVolumeLow);
28642
+ this.gl.uniform3fv(this.renderShader.uniforms.clipHi, this.opts.clipVolumeHigh);
28201
28643
  this.gl.uniform1f(this.renderShader.uniforms.backOpacity, this.volumes[0].opacity);
28202
28644
  this.gl.uniform1f(this.renderShader.uniforms.renderOverlayBlend, this.opts.renderOverlayBlend);
28203
28645
  this.gl.uniform4fv(this.renderShader.uniforms.clipPlane, this.scene.clipPlane);
@@ -29408,6 +29850,7 @@ var Niivue = class _Niivue {
29408
29850
  this.scene.pan2Dxyzmm[0] = this.uiData.pan2DxyzmmAtMouseDown[0] + zoom * v[0];
29409
29851
  this.scene.pan2Dxyzmm[1] = this.uiData.pan2DxyzmmAtMouseDown[1] + zoom * v[1];
29410
29852
  this.scene.pan2Dxyzmm[2] = this.uiData.pan2DxyzmmAtMouseDown[2] + zoom * v[2];
29853
+ this.canvas.focus();
29411
29854
  }
29412
29855
  dragForCenterButton(startXYendXY) {
29413
29856
  this.dragForPanZoom(startXYendXY);