@niivue/niivue 0.44.0 → 0.44.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.d.ts +35 -2
- package/build/index.js +363 -58
- package/build/index.js.map +1 -1
- package/build/index.min.js +1 -1
- package/dist/index.d.ts +35 -2
- package/dist/index.js +25866 -30696
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/niivue.umd.js +72 -32
- package/package.json +18 -17
- package/src/niivue/index.ts +32 -2
- package/src/nvimage/index.ts +25 -2
- package/src/nvimage/utils.ts +61 -42
- package/src/nvmesh-loaders.ts +26 -5
- package/src/nvmesh-utilities.ts +139 -0
- package/src/nvmesh.ts +96 -0
- package/src/shader-srcs.ts +43 -3
package/build/index.js
CHANGED
|
@@ -4,16 +4,13 @@ var __export = (target, all) => {
|
|
|
4
4
|
for (var name in all)
|
|
5
5
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
6
|
};
|
|
7
|
-
var __publicField = (obj, key, value) =>
|
|
8
|
-
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
9
|
-
return value;
|
|
10
|
-
};
|
|
7
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
11
8
|
|
|
12
9
|
// src/niivue/index.ts
|
|
13
10
|
import { mat4 as mat46, vec2, vec3 as vec311, vec4 as vec48 } from "gl-matrix";
|
|
14
11
|
|
|
15
12
|
// package.json
|
|
16
|
-
var version = "0.44.
|
|
13
|
+
var version = "0.44.2";
|
|
17
14
|
|
|
18
15
|
// src/logger.ts
|
|
19
16
|
var _Log = class _Log {
|
|
@@ -1271,11 +1268,11 @@ uniform mat4 mvpMtx;
|
|
|
1271
1268
|
uniform mat4 normMtx;
|
|
1272
1269
|
out vec4 vClr;
|
|
1273
1270
|
out vec3 vN;
|
|
1274
|
-
out vec4
|
|
1271
|
+
out vec4 vPc;
|
|
1275
1272
|
void main(void) {
|
|
1276
1273
|
vec3 lightPosition = vec3(0.0, 0.0, -10.0);
|
|
1277
|
-
|
|
1278
|
-
gl_Position =
|
|
1274
|
+
vPc = mvpMtx * vec4(pos, 1.0);
|
|
1275
|
+
gl_Position = vPc;
|
|
1279
1276
|
vN = normalize((normMtx * vec4(norm.xyz,1.0)).xyz);
|
|
1280
1277
|
//vV = -vec3(modelMtx*vec4(pos,1.0));
|
|
1281
1278
|
vClr = clr;
|
|
@@ -1420,6 +1417,45 @@ void main() {
|
|
|
1420
1417
|
s *= specularWhite;
|
|
1421
1418
|
color = vec4(d3 + s3 + s, opacity);
|
|
1422
1419
|
}`;
|
|
1420
|
+
var fragMeshShaderCrevice = `#version 300 es
|
|
1421
|
+
precision highp int;
|
|
1422
|
+
precision highp float;
|
|
1423
|
+
uniform float opacity;
|
|
1424
|
+
in vec4 vClr;
|
|
1425
|
+
in vec3 vN;
|
|
1426
|
+
in vec4 vPc;
|
|
1427
|
+
out vec4 color;
|
|
1428
|
+
void main() {
|
|
1429
|
+
vec3 n = normalize(vN);
|
|
1430
|
+
// Compute curvature
|
|
1431
|
+
vec3 dx = dFdx(n);
|
|
1432
|
+
vec3 dy = dFdy(n);
|
|
1433
|
+
vec3 xneg = n - dx;
|
|
1434
|
+
vec3 xpos = n + dx;
|
|
1435
|
+
vec3 yneg = n - dy;
|
|
1436
|
+
vec3 ypos = n + dy;
|
|
1437
|
+
float depth = length(vPc.xyz);
|
|
1438
|
+
float curv = (cross(xneg, xpos).y - cross(yneg, ypos).x) / depth;
|
|
1439
|
+
//at this stage 0.5 for flat, with valleys dark and ridges bright
|
|
1440
|
+
curv = 1.0 - (curv + 0.5);
|
|
1441
|
+
//clamp
|
|
1442
|
+
curv = min(max(curv, 0.0), 1.0);
|
|
1443
|
+
// easing function
|
|
1444
|
+
curv = pow(curv, 0.5);
|
|
1445
|
+
//modulate ambient and diffuse with curvature
|
|
1446
|
+
vec3 r = vec3(0.0, 0.0, 1.0); //rayDir: for orthographic projections moving in Z direction (no need for normal matrix)
|
|
1447
|
+
float ambient = 0.6;
|
|
1448
|
+
float diffuse = 0.6;
|
|
1449
|
+
float specular = 0.2;
|
|
1450
|
+
float shininess = 10.0;
|
|
1451
|
+
vec3 lightPosition = vec3(0.0, 10.0, -2.0);
|
|
1452
|
+
vec3 l = normalize(lightPosition);
|
|
1453
|
+
float lightNormDot = dot(n, l);
|
|
1454
|
+
vec3 a = vClr.rgb * ambient * curv;
|
|
1455
|
+
vec3 d = max(lightNormDot, 0.0) * vClr.rgb * diffuse;
|
|
1456
|
+
float s = specular * pow(max(dot(reflect(l, n), r), 0.0), shininess);
|
|
1457
|
+
color = vec4(a + d + s, opacity);
|
|
1458
|
+
}`;
|
|
1423
1459
|
var fragMeshShader = `#version 300 es
|
|
1424
1460
|
precision highp int;
|
|
1425
1461
|
precision highp float;
|
|
@@ -11254,6 +11290,7 @@ var cmapper = new ColorTables();
|
|
|
11254
11290
|
|
|
11255
11291
|
// src/nvmesh-utilities.ts
|
|
11256
11292
|
import { vec3 as vec32 } from "gl-matrix";
|
|
11293
|
+
import { gzipSync } from "fflate/browser";
|
|
11257
11294
|
var NVMeshUtilities = class {
|
|
11258
11295
|
static getClusterBoundaryU8(u8, faces) {
|
|
11259
11296
|
const border = new Array(u8.length).fill(false);
|
|
@@ -11279,6 +11316,112 @@ var NVMeshUtilities = class {
|
|
|
11279
11316
|
}
|
|
11280
11317
|
return border;
|
|
11281
11318
|
}
|
|
11319
|
+
static createMZ3(vertices, indices, compress2 = false) {
|
|
11320
|
+
const magic = 23117;
|
|
11321
|
+
const attr = 3;
|
|
11322
|
+
const nface = indices.length / 3;
|
|
11323
|
+
const nvert = vertices.length / 3;
|
|
11324
|
+
const nskip = 0;
|
|
11325
|
+
const headerSize = 16;
|
|
11326
|
+
const indexSize = nface * 3 * 4;
|
|
11327
|
+
const vertexSize = nvert * 3 * 4;
|
|
11328
|
+
const totalSize = headerSize + indexSize + vertexSize;
|
|
11329
|
+
const buffer = new ArrayBuffer(totalSize);
|
|
11330
|
+
const writer = new DataView(buffer);
|
|
11331
|
+
writer.setUint16(0, magic, true);
|
|
11332
|
+
writer.setUint16(2, attr, true);
|
|
11333
|
+
writer.setUint32(4, nface, true);
|
|
11334
|
+
writer.setUint32(8, nvert, true);
|
|
11335
|
+
writer.setUint32(12, nskip, true);
|
|
11336
|
+
let offset = headerSize;
|
|
11337
|
+
new Uint32Array(buffer, offset, indices.length).set(indices);
|
|
11338
|
+
offset += indexSize;
|
|
11339
|
+
new Float32Array(buffer, offset, vertices.length).set(vertices);
|
|
11340
|
+
if (compress2) {
|
|
11341
|
+
return gzipSync(new Uint8Array(buffer));
|
|
11342
|
+
}
|
|
11343
|
+
return buffer;
|
|
11344
|
+
}
|
|
11345
|
+
static createOBJ(vertices, indices) {
|
|
11346
|
+
let objContent = "";
|
|
11347
|
+
for (let i = 0; i < vertices.length; i += 3) {
|
|
11348
|
+
objContent += `v ${vertices[i]} ${vertices[i + 1]} ${vertices[i + 2]}
|
|
11349
|
+
`;
|
|
11350
|
+
}
|
|
11351
|
+
for (let i = 0; i < indices.length; i += 3) {
|
|
11352
|
+
objContent += `f ${indices[i] + 1} ${indices[i + 1] + 1} ${indices[i + 2] + 1}
|
|
11353
|
+
`;
|
|
11354
|
+
}
|
|
11355
|
+
const encoder = new TextEncoder();
|
|
11356
|
+
const arrayBuffer = encoder.encode(objContent).buffer;
|
|
11357
|
+
return arrayBuffer;
|
|
11358
|
+
}
|
|
11359
|
+
static createSTL(vertices, indices) {
|
|
11360
|
+
const numTriangles = indices.length / 3;
|
|
11361
|
+
const bufferLength = 84 + numTriangles * 50;
|
|
11362
|
+
const arrayBuffer = new ArrayBuffer(bufferLength);
|
|
11363
|
+
const dataView = new DataView(arrayBuffer);
|
|
11364
|
+
for (let i = 0; i < 80; i++) {
|
|
11365
|
+
dataView.setUint8(i, 0);
|
|
11366
|
+
}
|
|
11367
|
+
dataView.setUint32(80, numTriangles, true);
|
|
11368
|
+
let offset = 84;
|
|
11369
|
+
for (let i = 0; i < indices.length; i += 3) {
|
|
11370
|
+
const i0 = indices[i] * 3;
|
|
11371
|
+
const i1 = indices[i + 1] * 3;
|
|
11372
|
+
const i2 = indices[i + 2] * 3;
|
|
11373
|
+
dataView.setFloat32(offset, 0, true);
|
|
11374
|
+
dataView.setFloat32(offset + 4, 0, true);
|
|
11375
|
+
dataView.setFloat32(offset + 8, 0, true);
|
|
11376
|
+
offset += 12;
|
|
11377
|
+
dataView.setFloat32(offset, vertices[i0], true);
|
|
11378
|
+
dataView.setFloat32(offset + 4, vertices[i0 + 1], true);
|
|
11379
|
+
dataView.setFloat32(offset + 8, vertices[i0 + 2], true);
|
|
11380
|
+
offset += 12;
|
|
11381
|
+
dataView.setFloat32(offset, vertices[i1], true);
|
|
11382
|
+
dataView.setFloat32(offset + 4, vertices[i1 + 1], true);
|
|
11383
|
+
dataView.setFloat32(offset + 8, vertices[i1 + 2], true);
|
|
11384
|
+
offset += 12;
|
|
11385
|
+
dataView.setFloat32(offset, vertices[i2], true);
|
|
11386
|
+
dataView.setFloat32(offset + 4, vertices[i2 + 1], true);
|
|
11387
|
+
dataView.setFloat32(offset + 8, vertices[i2 + 2], true);
|
|
11388
|
+
offset += 12;
|
|
11389
|
+
dataView.setUint16(offset, 0, true);
|
|
11390
|
+
offset += 2;
|
|
11391
|
+
}
|
|
11392
|
+
return arrayBuffer;
|
|
11393
|
+
}
|
|
11394
|
+
static downloadArrayBuffer(buffer, filename) {
|
|
11395
|
+
const blob = new Blob([buffer], { type: "application/octet-stream" });
|
|
11396
|
+
const url = URL.createObjectURL(blob);
|
|
11397
|
+
const a = document.createElement("a");
|
|
11398
|
+
a.href = url;
|
|
11399
|
+
a.download = filename;
|
|
11400
|
+
document.body.appendChild(a);
|
|
11401
|
+
a.style.display = "none";
|
|
11402
|
+
a.click();
|
|
11403
|
+
setTimeout(() => {
|
|
11404
|
+
document.body.removeChild(a);
|
|
11405
|
+
URL.revokeObjectURL(url);
|
|
11406
|
+
}, 0);
|
|
11407
|
+
}
|
|
11408
|
+
static saveMesh(vertices, indices, filename = ".mz3", compress2 = false) {
|
|
11409
|
+
let buff = new ArrayBuffer(0);
|
|
11410
|
+
if (/\.obj$/i.test(filename)) {
|
|
11411
|
+
buff = this.createOBJ(vertices, indices);
|
|
11412
|
+
} else if (/\.stl$/i.test(filename)) {
|
|
11413
|
+
buff = this.createSTL(vertices, indices);
|
|
11414
|
+
} else {
|
|
11415
|
+
if (!/\.mz3$/i.test(filename)) {
|
|
11416
|
+
filename += ".mz3";
|
|
11417
|
+
}
|
|
11418
|
+
buff = this.createMZ3(vertices, indices, compress2);
|
|
11419
|
+
}
|
|
11420
|
+
if (filename.length > 4) {
|
|
11421
|
+
this.downloadArrayBuffer(buff, filename);
|
|
11422
|
+
}
|
|
11423
|
+
return buff;
|
|
11424
|
+
}
|
|
11282
11425
|
static getClusterBoundary(rgba8, faces) {
|
|
11283
11426
|
const rgba32 = new Uint32Array(rgba8.buffer);
|
|
11284
11427
|
const border = new Array(rgba32.length).fill(false);
|
|
@@ -12669,7 +12812,8 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
12669
12812
|
static readANNOT(buffer, n_vert, isReadColortables = false) {
|
|
12670
12813
|
const view = new DataView(buffer);
|
|
12671
12814
|
const n_vertex = view.getUint32(0, false);
|
|
12672
|
-
|
|
12815
|
+
const n_vertexDecimated = this.decimateLayerVertices(n_vertex, n_vert);
|
|
12816
|
+
if (n_vert !== n_vertexDecimated) {
|
|
12673
12817
|
throw new Error("ANNOT file has different number of vertices than mesh");
|
|
12674
12818
|
}
|
|
12675
12819
|
if (buffer.byteLength < 4 + 8 * n_vertex) {
|
|
@@ -13917,6 +14061,20 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
13917
14061
|
};
|
|
13918
14062
|
}
|
|
13919
14063
|
// readSTL()
|
|
14064
|
+
static decimateLayerVertices(nVertLayer, nVertMesh) {
|
|
14065
|
+
if (nVertLayer % nVertMesh === 0) {
|
|
14066
|
+
return nVertLayer;
|
|
14067
|
+
}
|
|
14068
|
+
const V0 = 12;
|
|
14069
|
+
const orderLayer = Math.round(Math.log((nVertLayer - 2) / (V0 - 2)) / Math.log(4));
|
|
14070
|
+
const orderMesh = Math.round(Math.log((nVertMesh - 2) / (V0 - 2)) / Math.log(4));
|
|
14071
|
+
const nVLayer = Math.pow(4, orderLayer) * (V0 - 2) + 2;
|
|
14072
|
+
const nVMesh = Math.pow(4, orderMesh) * (V0 - 2) + 2;
|
|
14073
|
+
if (nVLayer !== nVertLayer || nVMesh !== nVertMesh) {
|
|
14074
|
+
return nVertLayer;
|
|
14075
|
+
}
|
|
14076
|
+
return nVertMesh;
|
|
14077
|
+
}
|
|
13920
14078
|
// read NIfTI2 format with embedded CIfTI
|
|
13921
14079
|
// this variation very specific to connectome workbench
|
|
13922
14080
|
// https://brainder.org/2015/04/03/the-nifti-2-file-format/
|
|
@@ -14077,8 +14235,9 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
14077
14235
|
);
|
|
14078
14236
|
return scalars2;
|
|
14079
14237
|
}
|
|
14238
|
+
nvert = this.decimateLayerVertices(nvert, n_vert);
|
|
14080
14239
|
if (nvert % n_vert !== 0) {
|
|
14081
|
-
throw new Error("Vertices in
|
|
14240
|
+
throw new Error("Vertices in layer (" + nvert + ") is not a multiple of number of vertices (" + n_vert + ")");
|
|
14082
14241
|
}
|
|
14083
14242
|
if (isLittleEndian) {
|
|
14084
14243
|
if (datatype === 16) {
|
|
@@ -14157,8 +14316,9 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
14157
14316
|
const dim = reader.getUint16(40 + i * 2, isLittleEndian);
|
|
14158
14317
|
nvert *= Math.max(dim, 1);
|
|
14159
14318
|
}
|
|
14319
|
+
nvert = this.decimateLayerVertices(nvert, n_vert);
|
|
14160
14320
|
if (nvert % n_vert !== 0) {
|
|
14161
|
-
throw new Error("Vertices in
|
|
14321
|
+
throw new Error("Vertices in layer (" + nvert + ") is not a multiple of number of vertices (" + n_vert + ")");
|
|
14162
14322
|
}
|
|
14163
14323
|
if (isLittleEndian) {
|
|
14164
14324
|
if (datatype === 16) {
|
|
@@ -14214,10 +14374,11 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
14214
14374
|
if (version2 !== 1 || mtype < 0 || mtype > 4) {
|
|
14215
14375
|
log.warn("Not a valid MGH file");
|
|
14216
14376
|
}
|
|
14217
|
-
|
|
14377
|
+
let nvert = width * height * depth * nframes;
|
|
14218
14378
|
let scalars = [];
|
|
14379
|
+
nvert = this.decimateLayerVertices(nvert, n_vert);
|
|
14219
14380
|
if (nvert % n_vert !== 0) {
|
|
14220
|
-
log.warn("Vertices in
|
|
14381
|
+
log.warn("Vertices in layer (" + nvert + ") is not a multiple of number of vertices (" + n_vert + ")");
|
|
14221
14382
|
return scalars;
|
|
14222
14383
|
}
|
|
14223
14384
|
if (mtype === 3) {
|
|
@@ -15961,6 +16122,91 @@ var NVMesh2 = class _NVMesh {
|
|
|
15961
16122
|
}
|
|
15962
16123
|
this.updateMesh(gl);
|
|
15963
16124
|
}
|
|
16125
|
+
hierarchicalOrder() {
|
|
16126
|
+
const V0 = 12;
|
|
16127
|
+
const F0 = 20;
|
|
16128
|
+
const nF = this.tris.length / 3;
|
|
16129
|
+
const order = Math.log(nF / F0) / Math.log(4);
|
|
16130
|
+
if (nF !== Math.pow(4, order) * F0) {
|
|
16131
|
+
return NaN;
|
|
16132
|
+
}
|
|
16133
|
+
const nV = this.pts.length / 3;
|
|
16134
|
+
if (nV !== Math.pow(4, order) * (V0 - 2) + 2) {
|
|
16135
|
+
return NaN;
|
|
16136
|
+
}
|
|
16137
|
+
for (let i = 0; i < 15; i += 3) {
|
|
16138
|
+
if (this.tris[i] !== 0) {
|
|
16139
|
+
return NaN;
|
|
16140
|
+
}
|
|
16141
|
+
}
|
|
16142
|
+
for (let i = 15; i < 24; i += 3) {
|
|
16143
|
+
if (this.tris[i] !== 3) {
|
|
16144
|
+
return NaN;
|
|
16145
|
+
}
|
|
16146
|
+
}
|
|
16147
|
+
for (let i = 24; i < 30; i += 3) {
|
|
16148
|
+
if (this.tris[i] !== 4) {
|
|
16149
|
+
return NaN;
|
|
16150
|
+
}
|
|
16151
|
+
}
|
|
16152
|
+
return order;
|
|
16153
|
+
}
|
|
16154
|
+
decimateFaces(n, ntarget) {
|
|
16155
|
+
let fac = this.tris;
|
|
16156
|
+
const V0 = 12;
|
|
16157
|
+
const F0 = 20;
|
|
16158
|
+
for (let j = n - 1; j >= ntarget; j--) {
|
|
16159
|
+
const nVjprev = Math.pow(4, j + 1) * (V0 - 2) + 2;
|
|
16160
|
+
const nVj = Math.pow(4, j) * (V0 - 2) + 2;
|
|
16161
|
+
const nFjprev = fac.length / 3;
|
|
16162
|
+
const nFj = Math.pow(4, j) * F0;
|
|
16163
|
+
console.log(`order ${j + 1} -> ${j} vertices ${nVjprev} -> ${nVj} faces ${nFjprev} -> ${nFj}`);
|
|
16164
|
+
const remap = Array.from({ length: nVjprev }, (_, i) => i + 1);
|
|
16165
|
+
for (let i = 0; i < nFjprev; i++) {
|
|
16166
|
+
const v1 = fac[3 * i];
|
|
16167
|
+
const v2 = fac[3 * i + 1];
|
|
16168
|
+
const v3 = fac[3 * i + 2];
|
|
16169
|
+
remap[v1 - 1] = Math.min(remap[v1 - 1], v2, v3);
|
|
16170
|
+
}
|
|
16171
|
+
const facJ = new Uint32Array(nFj * 3);
|
|
16172
|
+
for (let i = 0; i < nFj; i++) {
|
|
16173
|
+
facJ[3 * i] = remap[fac[3 * i] - 1];
|
|
16174
|
+
facJ[3 * i + 1] = remap[fac[3 * i + 1] - 1];
|
|
16175
|
+
facJ[3 * i + 2] = remap[fac[3 * i + 2] - 1];
|
|
16176
|
+
}
|
|
16177
|
+
fac = facJ;
|
|
16178
|
+
}
|
|
16179
|
+
this.tris = new Uint32Array(fac);
|
|
16180
|
+
}
|
|
16181
|
+
// internal function simplifies FreeSurfer triangulated mesh and overlays
|
|
16182
|
+
decimateHierarchicalMesh(gl, order = 4) {
|
|
16183
|
+
const inputOrder = this.hierarchicalOrder();
|
|
16184
|
+
if (isNaN(inputOrder)) {
|
|
16185
|
+
log.warn("Unable to decimate mesh: it does not have a hierarchical structure");
|
|
16186
|
+
return false;
|
|
16187
|
+
}
|
|
16188
|
+
if (order >= inputOrder) {
|
|
16189
|
+
log.warn(`Unable to decimate mesh: input order (${inputOrder}) must be larger than downsampled order (${order})`);
|
|
16190
|
+
return false;
|
|
16191
|
+
}
|
|
16192
|
+
const inputVLength = this.pts.length / 3;
|
|
16193
|
+
const V0 = 12;
|
|
16194
|
+
const nV = Math.pow(4, order) * (V0 - 2) + 2;
|
|
16195
|
+
this.pts = new Float32Array(this.pts.slice(0, nV * 3));
|
|
16196
|
+
this.decimateFaces(inputOrder, order);
|
|
16197
|
+
if (this.layers && this.layers.length > 0) {
|
|
16198
|
+
for (let i = 0; i < this.layers.length; i++) {
|
|
16199
|
+
const layer = this.layers[i];
|
|
16200
|
+
if (layer.values instanceof Float32Array || layer.values.length !== inputVLength) {
|
|
16201
|
+
layer.values = new Float32Array(layer.values.slice(0, nV));
|
|
16202
|
+
} else {
|
|
16203
|
+
log.warn(`decimation logic needs to be updated`);
|
|
16204
|
+
}
|
|
16205
|
+
}
|
|
16206
|
+
}
|
|
16207
|
+
this.updateMesh(gl);
|
|
16208
|
+
return true;
|
|
16209
|
+
}
|
|
15964
16210
|
// adjust attributes of a mesh layer. invoked by niivue.setMeshLayerProperty()
|
|
15965
16211
|
// TODO this method is a bit too generic
|
|
15966
16212
|
setLayerProperty(id, key, val, gl) {
|
|
@@ -18016,7 +18262,7 @@ import { vec3 as vec38, vec4 as vec46 } from "gl-matrix";
|
|
|
18016
18262
|
import * as nifti from "nifti-reader-js";
|
|
18017
18263
|
import daikon from "daikon";
|
|
18018
18264
|
import { mat3, mat4 as mat44, vec3 as vec37, vec4 as vec45 } from "gl-matrix";
|
|
18019
|
-
import { Decompress, decompressSync as decompressSync3, gzipSync } from "fflate/browser";
|
|
18265
|
+
import { Decompress, decompressSync as decompressSync3, gzipSync as gzipSync2 } from "fflate/browser";
|
|
18020
18266
|
import { v4 as uuidv42 } from "@lukeed/uuid";
|
|
18021
18267
|
|
|
18022
18268
|
// src/nvimage/utils.ts
|
|
@@ -18159,76 +18405,90 @@ function getBestTransform(imageDirections, voxelDimensions, imagePosition) {
|
|
|
18159
18405
|
}
|
|
18160
18406
|
return m;
|
|
18161
18407
|
}
|
|
18162
|
-
function str2Buffer(str) {
|
|
18408
|
+
function str2Buffer(str, maxLen = 80) {
|
|
18409
|
+
str = str.replace(/[`$]/g, "");
|
|
18163
18410
|
const bytes = [];
|
|
18164
|
-
|
|
18411
|
+
const len = Math.min(maxLen, str.length);
|
|
18412
|
+
for (let i = 0; i < len; i++) {
|
|
18165
18413
|
const char = str.charCodeAt(i);
|
|
18166
18414
|
bytes.push(char & 255);
|
|
18167
18415
|
}
|
|
18168
18416
|
return bytes;
|
|
18169
18417
|
}
|
|
18170
|
-
function hdrToArrayBuffer(hdr, isDrawing8 = false) {
|
|
18418
|
+
function hdrToArrayBuffer(hdr, isDrawing8 = false, isInputEndian = false) {
|
|
18171
18419
|
const SHORT_SIZE = 2;
|
|
18172
18420
|
const FLOAT32_SIZE = 4;
|
|
18421
|
+
let isLittleEndian = true;
|
|
18422
|
+
if (isInputEndian) {
|
|
18423
|
+
isLittleEndian = hdr.littleEndian;
|
|
18424
|
+
}
|
|
18173
18425
|
const byteArray = new Uint8Array(348);
|
|
18174
18426
|
const view = new DataView(byteArray.buffer);
|
|
18175
|
-
view.setInt32(0, 348,
|
|
18427
|
+
view.setInt32(0, 348, isLittleEndian);
|
|
18428
|
+
view.setUint8(38, 114);
|
|
18176
18429
|
view.setUint8(39, hdr.dim_info);
|
|
18177
18430
|
for (let i = 0; i < 8; i++) {
|
|
18178
|
-
view.setUint16(40 + SHORT_SIZE * i, hdr.dims[i],
|
|
18431
|
+
view.setUint16(40 + SHORT_SIZE * i, hdr.dims[i], isLittleEndian);
|
|
18179
18432
|
}
|
|
18180
|
-
view.setFloat32(56, hdr.intent_p1,
|
|
18181
|
-
view.setFloat32(60, hdr.intent_p2,
|
|
18182
|
-
view.setFloat32(64, hdr.intent_p3,
|
|
18183
|
-
view.setInt16(68, hdr.intent_code,
|
|
18433
|
+
view.setFloat32(56, hdr.intent_p1, isLittleEndian);
|
|
18434
|
+
view.setFloat32(60, hdr.intent_p2, isLittleEndian);
|
|
18435
|
+
view.setFloat32(64, hdr.intent_p3, isLittleEndian);
|
|
18436
|
+
view.setInt16(68, hdr.intent_code, isLittleEndian);
|
|
18184
18437
|
if (isDrawing8) {
|
|
18185
|
-
view.setInt16(70, 2,
|
|
18186
|
-
view.setInt16(72, 8,
|
|
18438
|
+
view.setInt16(70, 2, isLittleEndian);
|
|
18439
|
+
view.setInt16(72, 8, isLittleEndian);
|
|
18187
18440
|
} else {
|
|
18188
|
-
view.setInt16(70, hdr.datatypeCode,
|
|
18189
|
-
view.setInt16(72, hdr.numBitsPerVoxel,
|
|
18441
|
+
view.setInt16(70, hdr.datatypeCode, isLittleEndian);
|
|
18442
|
+
view.setInt16(72, hdr.numBitsPerVoxel, isLittleEndian);
|
|
18190
18443
|
}
|
|
18191
|
-
view.setInt16(74, hdr.slice_start,
|
|
18444
|
+
view.setInt16(74, hdr.slice_start, isLittleEndian);
|
|
18192
18445
|
for (let i = 0; i < 8; i++) {
|
|
18193
|
-
view.setFloat32(76 + FLOAT32_SIZE * i, hdr.pixDims[i],
|
|
18446
|
+
view.setFloat32(76 + FLOAT32_SIZE * i, hdr.pixDims[i], isLittleEndian);
|
|
18194
18447
|
}
|
|
18195
18448
|
if (isDrawing8) {
|
|
18196
|
-
view.setFloat32(108, 352,
|
|
18197
|
-
view.setFloat32(112, 1,
|
|
18198
|
-
view.setFloat32(116, 0,
|
|
18449
|
+
view.setFloat32(108, 352, isLittleEndian);
|
|
18450
|
+
view.setFloat32(112, 1, isLittleEndian);
|
|
18451
|
+
view.setFloat32(116, 0, isLittleEndian);
|
|
18199
18452
|
} else {
|
|
18200
|
-
view.setFloat32(108,
|
|
18201
|
-
view.setFloat32(112, hdr.scl_slope,
|
|
18202
|
-
view.setFloat32(116, hdr.scl_inter,
|
|
18453
|
+
view.setFloat32(108, 352, isLittleEndian);
|
|
18454
|
+
view.setFloat32(112, hdr.scl_slope, isLittleEndian);
|
|
18455
|
+
view.setFloat32(116, hdr.scl_inter, isLittleEndian);
|
|
18203
18456
|
}
|
|
18204
|
-
view.setInt16(120, hdr.slice_end,
|
|
18457
|
+
view.setInt16(120, hdr.slice_end, isLittleEndian);
|
|
18205
18458
|
view.setUint8(122, hdr.slice_code);
|
|
18206
|
-
|
|
18459
|
+
if (hdr.xyzt_units === 0) {
|
|
18460
|
+
view.setUint8(123, 10);
|
|
18461
|
+
} else {
|
|
18462
|
+
view.setUint8(123, hdr.xyzt_units);
|
|
18463
|
+
}
|
|
18207
18464
|
if (isDrawing8) {
|
|
18208
|
-
view.setFloat32(124, 0,
|
|
18209
|
-
view.setFloat32(128, 0,
|
|
18465
|
+
view.setFloat32(124, 0, isLittleEndian);
|
|
18466
|
+
view.setFloat32(128, 0, isLittleEndian);
|
|
18210
18467
|
} else {
|
|
18211
|
-
view.setFloat32(124, hdr.cal_max,
|
|
18212
|
-
view.setFloat32(128, hdr.cal_min,
|
|
18468
|
+
view.setFloat32(124, hdr.cal_max, isLittleEndian);
|
|
18469
|
+
view.setFloat32(128, hdr.cal_min, isLittleEndian);
|
|
18213
18470
|
}
|
|
18214
|
-
view.setFloat32(132, hdr.slice_duration,
|
|
18215
|
-
view.setFloat32(136, hdr.toffset,
|
|
18471
|
+
view.setFloat32(132, hdr.slice_duration, isLittleEndian);
|
|
18472
|
+
view.setFloat32(136, hdr.toffset, isLittleEndian);
|
|
18216
18473
|
byteArray.set(str2Buffer(hdr.description), 148);
|
|
18217
18474
|
byteArray.set(str2Buffer(hdr.aux_file), 228);
|
|
18218
|
-
view.setInt16(252, hdr.qform_code,
|
|
18219
|
-
|
|
18220
|
-
|
|
18221
|
-
|
|
18222
|
-
|
|
18223
|
-
|
|
18224
|
-
view.setFloat32(
|
|
18225
|
-
view.setFloat32(
|
|
18475
|
+
view.setInt16(252, hdr.qform_code, isLittleEndian);
|
|
18476
|
+
if (hdr.sform_code < 1 || hdr.sform_code < 1) {
|
|
18477
|
+
view.setInt16(254, 1, isLittleEndian);
|
|
18478
|
+
} else {
|
|
18479
|
+
view.setInt16(254, hdr.sform_code, isLittleEndian);
|
|
18480
|
+
}
|
|
18481
|
+
view.setFloat32(256, hdr.quatern_b, isLittleEndian);
|
|
18482
|
+
view.setFloat32(260, hdr.quatern_c, isLittleEndian);
|
|
18483
|
+
view.setFloat32(264, hdr.quatern_d, isLittleEndian);
|
|
18484
|
+
view.setFloat32(268, hdr.qoffset_x, isLittleEndian);
|
|
18485
|
+
view.setFloat32(272, hdr.qoffset_y, isLittleEndian);
|
|
18486
|
+
view.setFloat32(276, hdr.qoffset_z, isLittleEndian);
|
|
18226
18487
|
const flattened = hdr.affine.flat();
|
|
18227
18488
|
for (let i = 0; i < 12; i++) {
|
|
18228
|
-
view.setFloat32(280 + FLOAT32_SIZE * i, flattened[i],
|
|
18489
|
+
view.setFloat32(280 + FLOAT32_SIZE * i, flattened[i], isLittleEndian);
|
|
18229
18490
|
}
|
|
18230
|
-
|
|
18231
|
-
byteArray.set(str2Buffer(hdr.magic), 344);
|
|
18491
|
+
view.setInt32(344, 3222382, true);
|
|
18232
18492
|
return byteArray;
|
|
18233
18493
|
}
|
|
18234
18494
|
function getExtents(positions, forceOriginInVolume = true) {
|
|
@@ -20875,7 +21135,7 @@ var NVImage = class _NVImage {
|
|
|
20875
21135
|
let saveData = null;
|
|
20876
21136
|
const compress2 = fnm.endsWith(".gz");
|
|
20877
21137
|
if (compress2) {
|
|
20878
|
-
saveData =
|
|
21138
|
+
saveData = gzipSync2(odata, {
|
|
20879
21139
|
// GZIP-specific: the filename to use when decompressed
|
|
20880
21140
|
filename: fnm,
|
|
20881
21141
|
// GZIP-specific: the modification time. Can be a Date, date string,
|
|
@@ -21338,7 +21598,7 @@ var NVImage = class _NVImage {
|
|
|
21338
21598
|
* read a 3D slab of voxels from a volume
|
|
21339
21599
|
* @param voxStart - first row, column and slice (RAS order) for selection
|
|
21340
21600
|
* @param voxEnd - final row, column and slice (RAS order) for selection
|
|
21341
|
-
* @param dataType - array data type. Options: 'same' (default), 'uint8', 'float32'
|
|
21601
|
+
* @param dataType - array data type. Options: 'same' (default), 'uint8', 'float32', 'scaled', 'normalized', 'windowed'
|
|
21342
21602
|
* @returns the an array where ret[0] is the voxel values and ret[1] is dimension of selection
|
|
21343
21603
|
* @see {@link https://niivue.github.io/niivue/features/slab_selection.html | live demo usage}
|
|
21344
21604
|
*/
|
|
@@ -21362,7 +21622,7 @@ var NVImage = class _NVImage {
|
|
|
21362
21622
|
let dt = this.hdr.datatypeCode;
|
|
21363
21623
|
if (dataType === "uint8") {
|
|
21364
21624
|
dt = 2 /* DT_UINT8 */;
|
|
21365
|
-
} else if (dataType === "float32") {
|
|
21625
|
+
} else if (dataType === "float32" || dataType === "scaled" || dataType === "normalized" || dataType === "windowed") {
|
|
21366
21626
|
dt = 16 /* DT_FLOAT32 */;
|
|
21367
21627
|
}
|
|
21368
21628
|
if (dt === 2 /* DT_UINT8 */) {
|
|
@@ -21392,6 +21652,24 @@ var NVImage = class _NVImage {
|
|
|
21392
21652
|
}
|
|
21393
21653
|
}
|
|
21394
21654
|
}
|
|
21655
|
+
if (dataType === "scaled" || dataType === "normalized" || dataType === "windowed") {
|
|
21656
|
+
for (let i2 = 0; i2 < img.length; i2++) {
|
|
21657
|
+
img[i2] = img[i2] * this.hdr.scl_slope + this.hdr.scl_inter;
|
|
21658
|
+
}
|
|
21659
|
+
}
|
|
21660
|
+
if (dataType === "normalized" || dataType === "windowed") {
|
|
21661
|
+
let mn = this.cal_min;
|
|
21662
|
+
let mx = this.cal_max;
|
|
21663
|
+
if (dataType === "normalized") {
|
|
21664
|
+
mn = this.global_min;
|
|
21665
|
+
mx = this.global_max;
|
|
21666
|
+
}
|
|
21667
|
+
const scale = 1 / (mx - mn);
|
|
21668
|
+
for (let i2 = 0; i2 < img.length; i2++) {
|
|
21669
|
+
img[i2] = (img[i2] - mn) * scale;
|
|
21670
|
+
img[i2] = Math.max(Math.min(img[i2], 1), 0);
|
|
21671
|
+
}
|
|
21672
|
+
}
|
|
21395
21673
|
return [img, slabDims];
|
|
21396
21674
|
}
|
|
21397
21675
|
// getVolumeData()
|
|
@@ -23873,6 +24151,10 @@ var Niivue = class _Niivue {
|
|
|
23873
24151
|
Name: "Hemispheric",
|
|
23874
24152
|
Frag: fragMeshHemiShader
|
|
23875
24153
|
},
|
|
24154
|
+
{
|
|
24155
|
+
Name: "Crevice",
|
|
24156
|
+
Frag: fragMeshShaderCrevice
|
|
24157
|
+
},
|
|
23876
24158
|
{
|
|
23877
24159
|
Name: "Edge",
|
|
23878
24160
|
Frag: fragMeshEdgeShader
|
|
@@ -25275,7 +25557,6 @@ var Niivue = class _Niivue {
|
|
|
25275
25557
|
this.loadDrawing(volume);
|
|
25276
25558
|
} else {
|
|
25277
25559
|
this.addVolume(volume);
|
|
25278
|
-
this.setDrawingEnabled(true);
|
|
25279
25560
|
}
|
|
25280
25561
|
}).catch((e2) => {
|
|
25281
25562
|
throw e2;
|
|
@@ -25966,6 +26247,24 @@ var Niivue = class _Niivue {
|
|
|
25966
26247
|
}
|
|
25967
26248
|
return this.meshes[idx].indexNearestXYZmm(Xmm, Ymm, Zmm);
|
|
25968
26249
|
}
|
|
26250
|
+
/**
|
|
26251
|
+
* reduce complexity of FreeSurfer mesh
|
|
26252
|
+
* @param mesh - identity of mesh to change
|
|
26253
|
+
* @param order - decimation order 0..6
|
|
26254
|
+
* @example niivue.decimateHierarchicalMesh(niivue.meshes[0].id, 4)
|
|
26255
|
+
* @returns boolean false if mesh is not hierarchical or of lower order
|
|
26256
|
+
* @see {@link https://niivue.github.io/niivue/features/meshes.html | live demo usage}
|
|
26257
|
+
*/
|
|
26258
|
+
decimateHierarchicalMesh(mesh, order = 3) {
|
|
26259
|
+
const idx = this.getMeshIndexByID(mesh);
|
|
26260
|
+
if (idx < 0) {
|
|
26261
|
+
log.warn("reverseFaces() id not loaded", mesh);
|
|
26262
|
+
return;
|
|
26263
|
+
}
|
|
26264
|
+
const ret = this.meshes[idx].decimateHierarchicalMesh(this.gl, order);
|
|
26265
|
+
this.updateGLVolume();
|
|
26266
|
+
return ret;
|
|
26267
|
+
}
|
|
25969
26268
|
/**
|
|
25970
26269
|
* reverse triangle winding of mesh (swap front and back faces)
|
|
25971
26270
|
* @param id - identity of mesh to change
|
|
@@ -26464,6 +26763,9 @@ var Niivue = class _Niivue {
|
|
|
26464
26763
|
this.renderShader.use(this.gl);
|
|
26465
26764
|
this.gl.uniform3fv(this.renderShader.uniforms.clipLo, this.opts.clipVolumeLow);
|
|
26466
26765
|
this.gl.uniform3fv(this.renderShader.uniforms.clipHi, this.opts.clipVolumeHigh);
|
|
26766
|
+
this.pickingImageShader.use(this.gl);
|
|
26767
|
+
this.gl.uniform3fv(this.pickingImageShader.uniforms.clipLo, this.opts.clipVolumeLow);
|
|
26768
|
+
this.gl.uniform3fv(this.pickingImageShader.uniforms.clipHi, this.opts.clipVolumeHigh);
|
|
26467
26769
|
this.drawScene();
|
|
26468
26770
|
}
|
|
26469
26771
|
/**
|
|
@@ -28765,6 +29067,8 @@ var Niivue = class _Niivue {
|
|
|
28765
29067
|
this.pickingImageShader.use(this.gl);
|
|
28766
29068
|
this.gl.uniform1f(this.pickingImageShader.uniforms.overlays, this.overlays.length);
|
|
28767
29069
|
this.gl.uniform3fv(this.pickingImageShader.uniforms.texVox, vox);
|
|
29070
|
+
this.gl.uniform3fv(this.pickingImageShader.uniforms.clipLo, this.opts.clipVolumeLow);
|
|
29071
|
+
this.gl.uniform3fv(this.pickingImageShader.uniforms.clipHi, this.opts.clipVolumeHigh);
|
|
28768
29072
|
let shader = this.sliceMMShader;
|
|
28769
29073
|
if (this.opts.isV1SliceShader) {
|
|
28770
29074
|
shader = this.sliceV1Shader;
|
|
@@ -32679,6 +32983,7 @@ export {
|
|
|
32679
32983
|
NVMeshFromUrlOptions,
|
|
32680
32984
|
NVMeshLayerDefaults,
|
|
32681
32985
|
NVMeshLoaders,
|
|
32986
|
+
NVMeshUtilities,
|
|
32682
32987
|
NVUtilities,
|
|
32683
32988
|
Niivue,
|
|
32684
32989
|
SHOW_RENDER,
|