@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/README.md +17 -9
- package/build/index.d.ts +88 -28
- package/build/index.js +1008 -565
- package/build/index.js.map +1 -1
- package/build/index.min.js +1 -1
- package/dist/index.d.ts +88 -28
- package/dist/index.js +1219 -620
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/niivue.umd.js +130 -45
- package/package.json +1 -7
- package/src/niivue/index.ts +113 -25
- package/src/nvdocument.ts +72 -11
- package/src/nvimage/index.ts +340 -61
- package/src/nvimage/utils.ts +10 -2
- package/src/nvmesh-loaders.ts +3 -91
- package/src/nvmesh-types.ts +4 -3
- package/src/nvmesh.ts +161 -2
- package/src/nvutilities.ts +148 -15
- package/src/shader-srcs.ts +95 -152
- package/src/types.ts +18 -0
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
11745
|
+
let trans_to_mni = mat43.create();
|
|
11632
11746
|
const m = mat.trans_to_mni;
|
|
11633
|
-
trans_to_mni =
|
|
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
|
-
|
|
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 =
|
|
11692
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
12152
|
+
mat43.identity(mat);
|
|
12039
12153
|
}
|
|
12040
|
-
const vox2mmMat =
|
|
12041
|
-
|
|
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
|
|
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
|
-
|
|
12311
|
+
lineToInts();
|
|
12199
12312
|
for (let c = 0; c < n_count; c++) {
|
|
12200
12313
|
if (asciiIntsPos >= asciiInts.length) {
|
|
12201
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
},
|
|
13872
|
-
let line2 =
|
|
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 +=
|
|
13990
|
+
line2 += readStrX();
|
|
13878
13991
|
}
|
|
13879
13992
|
return line2;
|
|
13880
|
-
},
|
|
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 =
|
|
14017
|
+
line = readStr();
|
|
13906
14018
|
if (line.includes("</CIFTI>")) {
|
|
13907
14019
|
break;
|
|
13908
14020
|
}
|
|
13909
14021
|
if (line.includes("<BrainModel")) {
|
|
13910
|
-
const nv =
|
|
13911
|
-
const bStruct =
|
|
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 =
|
|
13934
|
-
indexCount =
|
|
14045
|
+
indexOffset = readNumericTag("IndexOffset=");
|
|
14046
|
+
indexCount = readNumericTag("IndexCount=");
|
|
13935
14047
|
brainStructure = bStruct;
|
|
13936
14048
|
if (!line.includes("<VertexIndices>")) {
|
|
13937
|
-
line =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
14465
|
-
|
|
14466
|
-
const pti =
|
|
14467
|
-
const ptj =
|
|
14468
|
-
|
|
14469
|
-
|
|
14470
|
-
|
|
14471
|
-
|
|
14472
|
-
const pti3 =
|
|
14473
|
-
const ptj3 =
|
|
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 =
|
|
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
|
|
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 =
|
|
14644
|
-
datBin =
|
|
14754
|
+
const datZ = base64ToUint8(line.slice());
|
|
14755
|
+
datBin = decompressSync2(new Uint8Array(datZ));
|
|
14645
14756
|
} else {
|
|
14646
|
-
datBin =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
15037
|
-
let currV4 =
|
|
15038
|
-
let nextV4 =
|
|
15039
|
-
const v1 =
|
|
15040
|
-
let prevV2 =
|
|
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 =
|
|
15052
|
-
currV4 =
|
|
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 =
|
|
15056
|
-
|
|
15057
|
-
|
|
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 =
|
|
15177
|
+
nextV4 = vec44.clone(currV4);
|
|
15064
15178
|
} else {
|
|
15065
|
-
nextV4 =
|
|
15066
|
-
}
|
|
15067
|
-
|
|
15068
|
-
|
|
15069
|
-
const D =
|
|
15070
|
-
|
|
15071
|
-
const v2 =
|
|
15072
|
-
|
|
15073
|
-
prevV2 =
|
|
15074
|
-
const v3 =
|
|
15075
|
-
|
|
15076
|
-
|
|
15077
|
-
const vtxXYZ =
|
|
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
|
-
|
|
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 =
|
|
15090
|
-
|
|
15091
|
-
|
|
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 =
|
|
15100
|
-
currV4 =
|
|
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 =
|
|
15162
|
-
len +=
|
|
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 =
|
|
15190
|
-
|
|
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
|
|
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] =
|
|
15674
|
-
u8[vtx + 1] =
|
|
15675
|
-
u8[vtx + 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
|
-
|
|
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("
|
|
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.
|
|
19716
|
-
case
|
|
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
|
|
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
|
|
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
|
|
19729
|
-
inVs = new
|
|
19730
|
-
outVs = new
|
|
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
|
|
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("
|
|
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("
|
|
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("
|
|
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
|
|
20523
|
-
if (
|
|
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
|
-
|
|
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
|
|
22060
|
-
|
|
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
|
-
|
|
22069
|
-
|
|
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
|
-
|
|
26147
|
-
|
|
26148
|
-
|
|
26149
|
-
|
|
26150
|
-
|
|
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
|
|
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
|
-
|
|
26765
|
-
|
|
26766
|
-
|
|
26767
|
-
|
|
26768
|
-
|
|
26769
|
-
|
|
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
|
-
|
|
26774
|
-
|
|
26775
|
-
|
|
26776
|
-
|
|
26777
|
-
|
|
26778
|
-
|
|
26779
|
-
|
|
26780
|
-
|
|
26781
|
-
|
|
26782
|
-
|
|
26783
|
-
|
|
26784
|
-
|
|
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
|
-
|
|
26789
|
-
|
|
26790
|
-
|
|
26791
|
-
|
|
26792
|
-
|
|
26793
|
-
|
|
26794
|
-
|
|
26795
|
-
|
|
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);
|