@niivue/niivue 0.44.0 → 0.44.1
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 +262 -10
- 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 +384 -133
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/niivue.umd.js +31 -29
- package/package.json +1 -1
- package/src/niivue/index.ts +26 -1
- package/src/nvimage/index.ts +25 -2
- package/src/nvmesh-loaders.ts +26 -5
- package/src/nvmesh-utilities.ts +139 -0
- package/src/nvmesh.ts +96 -0
package/build/index.d.ts
CHANGED
|
@@ -437,7 +437,7 @@ declare class NVImage {
|
|
|
437
437
|
* read a 3D slab of voxels from a volume
|
|
438
438
|
* @param voxStart - first row, column and slice (RAS order) for selection
|
|
439
439
|
* @param voxEnd - final row, column and slice (RAS order) for selection
|
|
440
|
-
* @param dataType - array data type. Options: 'same' (default), 'uint8', 'float32'
|
|
440
|
+
* @param dataType - array data type. Options: 'same' (default), 'uint8', 'float32', 'scaled', 'normalized', 'windowed'
|
|
441
441
|
* @returns the an array where ret[0] is the voxel values and ret[1] is dimension of selection
|
|
442
442
|
* @see {@link https://niivue.github.io/niivue/features/slab_selection.html | live demo usage}
|
|
443
443
|
*/
|
|
@@ -1191,6 +1191,9 @@ declare class NVMesh {
|
|
|
1191
1191
|
unloadMesh(gl: WebGL2RenderingContext): void;
|
|
1192
1192
|
updateMesh(gl: WebGL2RenderingContext): void;
|
|
1193
1193
|
reverseFaces(gl: WebGL2RenderingContext): void;
|
|
1194
|
+
hierarchicalOrder(): number;
|
|
1195
|
+
decimateFaces(n: number, ntarget: number): void;
|
|
1196
|
+
decimateHierarchicalMesh(gl: WebGL2RenderingContext, order?: number): boolean;
|
|
1194
1197
|
setLayerProperty(id: number, key: keyof NVMeshLayer, val: number | string | boolean, gl: WebGL2RenderingContext): void;
|
|
1195
1198
|
setProperty(key: keyof this, val: unknown, gl: WebGL2RenderingContext): void;
|
|
1196
1199
|
generatePosNormClr(pts: Float32Array, tris: Uint32Array, rgba255: Uint8Array): Float32Array;
|
|
@@ -1520,6 +1523,7 @@ declare class NVMeshLoaders {
|
|
|
1520
1523
|
static readSRF(buffer: ArrayBuffer): DefaultMeshType;
|
|
1521
1524
|
static readTxtSTL(buffer: ArrayBuffer): DefaultMeshType;
|
|
1522
1525
|
static readSTL(buffer: ArrayBuffer): DefaultMeshType;
|
|
1526
|
+
static decimateLayerVertices(nVertLayer: number, nVertMesh: number): number;
|
|
1523
1527
|
static readNII2(buffer: ArrayBuffer, n_vert?: number, anatomicalStructurePrimary?: string): Int32Array | Float32Array | Int16Array | Uint8Array;
|
|
1524
1528
|
static readNII(buffer: ArrayBuffer, n_vert?: number, anatomicalStructurePrimary?: string): Float32Array | Uint8Array | Int32Array | Int16Array;
|
|
1525
1529
|
static readMGH(buffer: ArrayBuffer, n_vert?: number, isReadColortables?: boolean): MGH;
|
|
@@ -1527,6 +1531,26 @@ declare class NVMeshLoaders {
|
|
|
1527
1531
|
static readGII(buffer: ArrayBuffer, n_vert?: number): GII;
|
|
1528
1532
|
}
|
|
1529
1533
|
|
|
1534
|
+
type Extents = {
|
|
1535
|
+
mxDx: number;
|
|
1536
|
+
extentsMin: number | number[];
|
|
1537
|
+
extentsMax: number | number[];
|
|
1538
|
+
};
|
|
1539
|
+
/**
|
|
1540
|
+
* Utilities class for common mesh functions
|
|
1541
|
+
*/
|
|
1542
|
+
declare class NVMeshUtilities {
|
|
1543
|
+
static getClusterBoundaryU8(u8: Uint8Array, faces: number[] | Uint32Array): boolean[];
|
|
1544
|
+
static createMZ3(vertices: Float32Array, indices: Uint32Array, compress?: boolean): ArrayBuffer;
|
|
1545
|
+
static createOBJ(vertices: Float32Array, indices: Uint32Array): ArrayBuffer;
|
|
1546
|
+
static createSTL(vertices: Float32Array, indices: Uint32Array): ArrayBuffer;
|
|
1547
|
+
static downloadArrayBuffer(buffer: ArrayBuffer, filename: string): void;
|
|
1548
|
+
static saveMesh(vertices: Float32Array, indices: Uint32Array, filename?: string, compress?: boolean): ArrayBuffer;
|
|
1549
|
+
static getClusterBoundary(rgba8: Uint8Array, faces: number[] | Uint32Array): boolean[];
|
|
1550
|
+
static getExtents(pts: number[] | Float32Array): Extents;
|
|
1551
|
+
static generateNormals(pts: number[] | Float32Array, tris: number[] | Uint32Array): Float32Array;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1530
1554
|
type ColormapListEntry = {
|
|
1531
1555
|
name: string;
|
|
1532
1556
|
min: number;
|
|
@@ -2264,6 +2288,15 @@ declare class Niivue {
|
|
|
2264
2288
|
* @see {@link https://niivue.github.io/niivue/features/clipplanes.html | live demo usage}
|
|
2265
2289
|
*/
|
|
2266
2290
|
indexNearestXYZmm(mesh: number, Xmm: number, Ymm: number, Zmm: number): number[];
|
|
2291
|
+
/**
|
|
2292
|
+
* reduce complexity of FreeSurfer mesh
|
|
2293
|
+
* @param mesh - identity of mesh to change
|
|
2294
|
+
* @param order - decimation order 0..6
|
|
2295
|
+
* @example niivue.decimateHierarchicalMesh(niivue.meshes[0].id, 4)
|
|
2296
|
+
* @returns boolean false if mesh is not hierarchical or of lower order
|
|
2297
|
+
* @see {@link https://niivue.github.io/niivue/features/meshes.html | live demo usage}
|
|
2298
|
+
*/
|
|
2299
|
+
decimateHierarchicalMesh(mesh: number, order?: number): boolean;
|
|
2267
2300
|
/**
|
|
2268
2301
|
* reverse triangle winding of mesh (swap front and back faces)
|
|
2269
2302
|
* @param id - identity of mesh to change
|
|
@@ -3009,4 +3042,4 @@ declare class Niivue {
|
|
|
3009
3042
|
set gl(gl: WebGL2RenderingContext | null);
|
|
3010
3043
|
}
|
|
3011
3044
|
|
|
3012
|
-
export { type Connectome, type ConnectomeOptions, DEFAULT_OPTIONS, DRAG_MODE, type DocumentData, type DragReleaseParams, type ExportDocumentData, INITIAL_SCENE_DATA, LabelLineTerminator, LabelTextAlignment, type LegacyConnectome, type LegacyNodes, MULTIPLANAR_TYPE, type NVConfigOptions, type NVConnectomeEdge, type NVConnectomeNode, NVController, NVDocument, NVImage, NVImageFromUrlOptions, NVLabel3D, NVLabel3DStyle, NVMesh, NVMeshFromUrlOptions, NVMeshLayerDefaults, NVMeshLoaders, NVUtilities, type NiftiHeader, type NiiVueLocation, type NiiVueLocationValue, Niivue, type Point, SHOW_RENDER, SLICE_TYPE, type Scene, type Volume, cmapper, ColorTables as colortables };
|
|
3045
|
+
export { type Connectome, type ConnectomeOptions, DEFAULT_OPTIONS, DRAG_MODE, type DocumentData, type DragReleaseParams, type ExportDocumentData, INITIAL_SCENE_DATA, LabelLineTerminator, LabelTextAlignment, type LegacyConnectome, type LegacyNodes, MULTIPLANAR_TYPE, type NVConfigOptions, type NVConnectomeEdge, type NVConnectomeNode, NVController, NVDocument, NVImage, NVImageFromUrlOptions, NVLabel3D, NVLabel3DStyle, NVMesh, NVMeshFromUrlOptions, NVMeshLayerDefaults, NVMeshLoaders, NVMeshUtilities, NVUtilities, type NiftiHeader, type NiiVueLocation, type NiiVueLocationValue, Niivue, type Point, SHOW_RENDER, SLICE_TYPE, type Scene, type Volume, cmapper, ColorTables as colortables };
|
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.44.
|
|
16
|
+
var version = "0.44.1";
|
|
17
17
|
|
|
18
18
|
// src/logger.ts
|
|
19
19
|
var _Log = class _Log {
|
|
@@ -11254,6 +11254,7 @@ var cmapper = new ColorTables();
|
|
|
11254
11254
|
|
|
11255
11255
|
// src/nvmesh-utilities.ts
|
|
11256
11256
|
import { vec3 as vec32 } from "gl-matrix";
|
|
11257
|
+
import { gzipSync } from "fflate/browser";
|
|
11257
11258
|
var NVMeshUtilities = class {
|
|
11258
11259
|
static getClusterBoundaryU8(u8, faces) {
|
|
11259
11260
|
const border = new Array(u8.length).fill(false);
|
|
@@ -11279,6 +11280,112 @@ var NVMeshUtilities = class {
|
|
|
11279
11280
|
}
|
|
11280
11281
|
return border;
|
|
11281
11282
|
}
|
|
11283
|
+
static createMZ3(vertices, indices, compress2 = false) {
|
|
11284
|
+
const magic = 23117;
|
|
11285
|
+
const attr = 3;
|
|
11286
|
+
const nface = indices.length / 3;
|
|
11287
|
+
const nvert = vertices.length / 3;
|
|
11288
|
+
const nskip = 0;
|
|
11289
|
+
const headerSize = 16;
|
|
11290
|
+
const indexSize = nface * 3 * 4;
|
|
11291
|
+
const vertexSize = nvert * 3 * 4;
|
|
11292
|
+
const totalSize = headerSize + indexSize + vertexSize;
|
|
11293
|
+
const buffer = new ArrayBuffer(totalSize);
|
|
11294
|
+
const writer = new DataView(buffer);
|
|
11295
|
+
writer.setUint16(0, magic, true);
|
|
11296
|
+
writer.setUint16(2, attr, true);
|
|
11297
|
+
writer.setUint32(4, nface, true);
|
|
11298
|
+
writer.setUint32(8, nvert, true);
|
|
11299
|
+
writer.setUint32(12, nskip, true);
|
|
11300
|
+
let offset = headerSize;
|
|
11301
|
+
new Uint32Array(buffer, offset, indices.length).set(indices);
|
|
11302
|
+
offset += indexSize;
|
|
11303
|
+
new Float32Array(buffer, offset, vertices.length).set(vertices);
|
|
11304
|
+
if (compress2) {
|
|
11305
|
+
return gzipSync(new Uint8Array(buffer));
|
|
11306
|
+
}
|
|
11307
|
+
return buffer;
|
|
11308
|
+
}
|
|
11309
|
+
static createOBJ(vertices, indices) {
|
|
11310
|
+
let objContent = "";
|
|
11311
|
+
for (let i = 0; i < vertices.length; i += 3) {
|
|
11312
|
+
objContent += `v ${vertices[i]} ${vertices[i + 1]} ${vertices[i + 2]}
|
|
11313
|
+
`;
|
|
11314
|
+
}
|
|
11315
|
+
for (let i = 0; i < indices.length; i += 3) {
|
|
11316
|
+
objContent += `f ${indices[i] + 1} ${indices[i + 1] + 1} ${indices[i + 2] + 1}
|
|
11317
|
+
`;
|
|
11318
|
+
}
|
|
11319
|
+
const encoder = new TextEncoder();
|
|
11320
|
+
const arrayBuffer = encoder.encode(objContent).buffer;
|
|
11321
|
+
return arrayBuffer;
|
|
11322
|
+
}
|
|
11323
|
+
static createSTL(vertices, indices) {
|
|
11324
|
+
const numTriangles = indices.length / 3;
|
|
11325
|
+
const bufferLength = 84 + numTriangles * 50;
|
|
11326
|
+
const arrayBuffer = new ArrayBuffer(bufferLength);
|
|
11327
|
+
const dataView = new DataView(arrayBuffer);
|
|
11328
|
+
for (let i = 0; i < 80; i++) {
|
|
11329
|
+
dataView.setUint8(i, 0);
|
|
11330
|
+
}
|
|
11331
|
+
dataView.setUint32(80, numTriangles, true);
|
|
11332
|
+
let offset = 84;
|
|
11333
|
+
for (let i = 0; i < indices.length; i += 3) {
|
|
11334
|
+
const i0 = indices[i] * 3;
|
|
11335
|
+
const i1 = indices[i + 1] * 3;
|
|
11336
|
+
const i2 = indices[i + 2] * 3;
|
|
11337
|
+
dataView.setFloat32(offset, 0, true);
|
|
11338
|
+
dataView.setFloat32(offset + 4, 0, true);
|
|
11339
|
+
dataView.setFloat32(offset + 8, 0, true);
|
|
11340
|
+
offset += 12;
|
|
11341
|
+
dataView.setFloat32(offset, vertices[i0], true);
|
|
11342
|
+
dataView.setFloat32(offset + 4, vertices[i0 + 1], true);
|
|
11343
|
+
dataView.setFloat32(offset + 8, vertices[i0 + 2], true);
|
|
11344
|
+
offset += 12;
|
|
11345
|
+
dataView.setFloat32(offset, vertices[i1], true);
|
|
11346
|
+
dataView.setFloat32(offset + 4, vertices[i1 + 1], true);
|
|
11347
|
+
dataView.setFloat32(offset + 8, vertices[i1 + 2], true);
|
|
11348
|
+
offset += 12;
|
|
11349
|
+
dataView.setFloat32(offset, vertices[i2], true);
|
|
11350
|
+
dataView.setFloat32(offset + 4, vertices[i2 + 1], true);
|
|
11351
|
+
dataView.setFloat32(offset + 8, vertices[i2 + 2], true);
|
|
11352
|
+
offset += 12;
|
|
11353
|
+
dataView.setUint16(offset, 0, true);
|
|
11354
|
+
offset += 2;
|
|
11355
|
+
}
|
|
11356
|
+
return arrayBuffer;
|
|
11357
|
+
}
|
|
11358
|
+
static downloadArrayBuffer(buffer, filename) {
|
|
11359
|
+
const blob = new Blob([buffer], { type: "application/octet-stream" });
|
|
11360
|
+
const url = URL.createObjectURL(blob);
|
|
11361
|
+
const a = document.createElement("a");
|
|
11362
|
+
a.href = url;
|
|
11363
|
+
a.download = filename;
|
|
11364
|
+
document.body.appendChild(a);
|
|
11365
|
+
a.style.display = "none";
|
|
11366
|
+
a.click();
|
|
11367
|
+
setTimeout(() => {
|
|
11368
|
+
document.body.removeChild(a);
|
|
11369
|
+
URL.revokeObjectURL(url);
|
|
11370
|
+
}, 0);
|
|
11371
|
+
}
|
|
11372
|
+
static saveMesh(vertices, indices, filename = ".mz3", compress2 = false) {
|
|
11373
|
+
let buff = new ArrayBuffer(0);
|
|
11374
|
+
if (/\.obj$/i.test(filename)) {
|
|
11375
|
+
buff = this.createOBJ(vertices, indices);
|
|
11376
|
+
} else if (/\.stl$/i.test(filename)) {
|
|
11377
|
+
buff = this.createSTL(vertices, indices);
|
|
11378
|
+
} else {
|
|
11379
|
+
if (!/\.mz3$/i.test(filename)) {
|
|
11380
|
+
filename += ".mz3";
|
|
11381
|
+
}
|
|
11382
|
+
buff = this.createMZ3(vertices, indices, compress2);
|
|
11383
|
+
}
|
|
11384
|
+
if (filename.length > 4) {
|
|
11385
|
+
this.downloadArrayBuffer(buff, filename);
|
|
11386
|
+
}
|
|
11387
|
+
return buff;
|
|
11388
|
+
}
|
|
11282
11389
|
static getClusterBoundary(rgba8, faces) {
|
|
11283
11390
|
const rgba32 = new Uint32Array(rgba8.buffer);
|
|
11284
11391
|
const border = new Array(rgba32.length).fill(false);
|
|
@@ -12669,7 +12776,8 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
12669
12776
|
static readANNOT(buffer, n_vert, isReadColortables = false) {
|
|
12670
12777
|
const view = new DataView(buffer);
|
|
12671
12778
|
const n_vertex = view.getUint32(0, false);
|
|
12672
|
-
|
|
12779
|
+
const n_vertexDecimated = this.decimateLayerVertices(n_vertex, n_vert);
|
|
12780
|
+
if (n_vert !== n_vertexDecimated) {
|
|
12673
12781
|
throw new Error("ANNOT file has different number of vertices than mesh");
|
|
12674
12782
|
}
|
|
12675
12783
|
if (buffer.byteLength < 4 + 8 * n_vertex) {
|
|
@@ -13917,6 +14025,20 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
13917
14025
|
};
|
|
13918
14026
|
}
|
|
13919
14027
|
// readSTL()
|
|
14028
|
+
static decimateLayerVertices(nVertLayer, nVertMesh) {
|
|
14029
|
+
if (nVertLayer % nVertMesh === 0) {
|
|
14030
|
+
return nVertLayer;
|
|
14031
|
+
}
|
|
14032
|
+
const V0 = 12;
|
|
14033
|
+
const orderLayer = Math.round(Math.log((nVertLayer - 2) / (V0 - 2)) / Math.log(4));
|
|
14034
|
+
const orderMesh = Math.round(Math.log((nVertMesh - 2) / (V0 - 2)) / Math.log(4));
|
|
14035
|
+
const nVLayer = Math.pow(4, orderLayer) * (V0 - 2) + 2;
|
|
14036
|
+
const nVMesh = Math.pow(4, orderMesh) * (V0 - 2) + 2;
|
|
14037
|
+
if (nVLayer !== nVertLayer || nVMesh !== nVertMesh) {
|
|
14038
|
+
return nVertLayer;
|
|
14039
|
+
}
|
|
14040
|
+
return nVertMesh;
|
|
14041
|
+
}
|
|
13920
14042
|
// read NIfTI2 format with embedded CIfTI
|
|
13921
14043
|
// this variation very specific to connectome workbench
|
|
13922
14044
|
// https://brainder.org/2015/04/03/the-nifti-2-file-format/
|
|
@@ -14077,8 +14199,9 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
14077
14199
|
);
|
|
14078
14200
|
return scalars2;
|
|
14079
14201
|
}
|
|
14202
|
+
nvert = this.decimateLayerVertices(nvert, n_vert);
|
|
14080
14203
|
if (nvert % n_vert !== 0) {
|
|
14081
|
-
throw new Error("Vertices in
|
|
14204
|
+
throw new Error("Vertices in layer (" + nvert + ") is not a multiple of number of vertices (" + n_vert + ")");
|
|
14082
14205
|
}
|
|
14083
14206
|
if (isLittleEndian) {
|
|
14084
14207
|
if (datatype === 16) {
|
|
@@ -14157,8 +14280,9 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
14157
14280
|
const dim = reader.getUint16(40 + i * 2, isLittleEndian);
|
|
14158
14281
|
nvert *= Math.max(dim, 1);
|
|
14159
14282
|
}
|
|
14283
|
+
nvert = this.decimateLayerVertices(nvert, n_vert);
|
|
14160
14284
|
if (nvert % n_vert !== 0) {
|
|
14161
|
-
throw new Error("Vertices in
|
|
14285
|
+
throw new Error("Vertices in layer (" + nvert + ") is not a multiple of number of vertices (" + n_vert + ")");
|
|
14162
14286
|
}
|
|
14163
14287
|
if (isLittleEndian) {
|
|
14164
14288
|
if (datatype === 16) {
|
|
@@ -14214,10 +14338,11 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
14214
14338
|
if (version2 !== 1 || mtype < 0 || mtype > 4) {
|
|
14215
14339
|
log.warn("Not a valid MGH file");
|
|
14216
14340
|
}
|
|
14217
|
-
|
|
14341
|
+
let nvert = width * height * depth * nframes;
|
|
14218
14342
|
let scalars = [];
|
|
14343
|
+
nvert = this.decimateLayerVertices(nvert, n_vert);
|
|
14219
14344
|
if (nvert % n_vert !== 0) {
|
|
14220
|
-
log.warn("Vertices in
|
|
14345
|
+
log.warn("Vertices in layer (" + nvert + ") is not a multiple of number of vertices (" + n_vert + ")");
|
|
14221
14346
|
return scalars;
|
|
14222
14347
|
}
|
|
14223
14348
|
if (mtype === 3) {
|
|
@@ -15961,6 +16086,91 @@ var NVMesh2 = class _NVMesh {
|
|
|
15961
16086
|
}
|
|
15962
16087
|
this.updateMesh(gl);
|
|
15963
16088
|
}
|
|
16089
|
+
hierarchicalOrder() {
|
|
16090
|
+
const V0 = 12;
|
|
16091
|
+
const F0 = 20;
|
|
16092
|
+
const nF = this.tris.length / 3;
|
|
16093
|
+
const order = Math.log(nF / F0) / Math.log(4);
|
|
16094
|
+
if (nF !== Math.pow(4, order) * F0) {
|
|
16095
|
+
return NaN;
|
|
16096
|
+
}
|
|
16097
|
+
const nV = this.pts.length / 3;
|
|
16098
|
+
if (nV !== Math.pow(4, order) * (V0 - 2) + 2) {
|
|
16099
|
+
return NaN;
|
|
16100
|
+
}
|
|
16101
|
+
for (let i = 0; i < 15; i += 3) {
|
|
16102
|
+
if (this.tris[i] !== 0) {
|
|
16103
|
+
return NaN;
|
|
16104
|
+
}
|
|
16105
|
+
}
|
|
16106
|
+
for (let i = 15; i < 24; i += 3) {
|
|
16107
|
+
if (this.tris[i] !== 3) {
|
|
16108
|
+
return NaN;
|
|
16109
|
+
}
|
|
16110
|
+
}
|
|
16111
|
+
for (let i = 24; i < 30; i += 3) {
|
|
16112
|
+
if (this.tris[i] !== 4) {
|
|
16113
|
+
return NaN;
|
|
16114
|
+
}
|
|
16115
|
+
}
|
|
16116
|
+
return order;
|
|
16117
|
+
}
|
|
16118
|
+
decimateFaces(n, ntarget) {
|
|
16119
|
+
let fac = this.tris;
|
|
16120
|
+
const V0 = 12;
|
|
16121
|
+
const F0 = 20;
|
|
16122
|
+
for (let j = n - 1; j >= ntarget; j--) {
|
|
16123
|
+
const nVjprev = Math.pow(4, j + 1) * (V0 - 2) + 2;
|
|
16124
|
+
const nVj = Math.pow(4, j) * (V0 - 2) + 2;
|
|
16125
|
+
const nFjprev = fac.length / 3;
|
|
16126
|
+
const nFj = Math.pow(4, j) * F0;
|
|
16127
|
+
console.log(`order ${j + 1} -> ${j} vertices ${nVjprev} -> ${nVj} faces ${nFjprev} -> ${nFj}`);
|
|
16128
|
+
const remap = Array.from({ length: nVjprev }, (_, i) => i + 1);
|
|
16129
|
+
for (let i = 0; i < nFjprev; i++) {
|
|
16130
|
+
const v1 = fac[3 * i];
|
|
16131
|
+
const v2 = fac[3 * i + 1];
|
|
16132
|
+
const v3 = fac[3 * i + 2];
|
|
16133
|
+
remap[v1 - 1] = Math.min(remap[v1 - 1], v2, v3);
|
|
16134
|
+
}
|
|
16135
|
+
const facJ = new Uint32Array(nFj * 3);
|
|
16136
|
+
for (let i = 0; i < nFj; i++) {
|
|
16137
|
+
facJ[3 * i] = remap[fac[3 * i] - 1];
|
|
16138
|
+
facJ[3 * i + 1] = remap[fac[3 * i + 1] - 1];
|
|
16139
|
+
facJ[3 * i + 2] = remap[fac[3 * i + 2] - 1];
|
|
16140
|
+
}
|
|
16141
|
+
fac = facJ;
|
|
16142
|
+
}
|
|
16143
|
+
this.tris = new Uint32Array(fac);
|
|
16144
|
+
}
|
|
16145
|
+
// internal function simplifies FreeSurfer triangulated mesh and overlays
|
|
16146
|
+
decimateHierarchicalMesh(gl, order = 4) {
|
|
16147
|
+
const inputOrder = this.hierarchicalOrder();
|
|
16148
|
+
if (isNaN(inputOrder)) {
|
|
16149
|
+
log.warn("Unable to decimate mesh: it does not have a hierarchical structure");
|
|
16150
|
+
return false;
|
|
16151
|
+
}
|
|
16152
|
+
if (order >= inputOrder) {
|
|
16153
|
+
log.warn(`Unable to decimate mesh: input order (${inputOrder}) must be larger than downsampled order (${order})`);
|
|
16154
|
+
return false;
|
|
16155
|
+
}
|
|
16156
|
+
const inputVLength = this.pts.length / 3;
|
|
16157
|
+
const V0 = 12;
|
|
16158
|
+
const nV = Math.pow(4, order) * (V0 - 2) + 2;
|
|
16159
|
+
this.pts = new Float32Array(this.pts.slice(0, nV * 3));
|
|
16160
|
+
this.decimateFaces(inputOrder, order);
|
|
16161
|
+
if (this.layers && this.layers.length > 0) {
|
|
16162
|
+
for (let i = 0; i < this.layers.length; i++) {
|
|
16163
|
+
const layer = this.layers[i];
|
|
16164
|
+
if (layer.values instanceof Float32Array || layer.values.length !== inputVLength) {
|
|
16165
|
+
layer.values = new Float32Array(layer.values.slice(0, nV));
|
|
16166
|
+
} else {
|
|
16167
|
+
log.warn(`decimation logic needs to be updated`);
|
|
16168
|
+
}
|
|
16169
|
+
}
|
|
16170
|
+
}
|
|
16171
|
+
this.updateMesh(gl);
|
|
16172
|
+
return true;
|
|
16173
|
+
}
|
|
15964
16174
|
// adjust attributes of a mesh layer. invoked by niivue.setMeshLayerProperty()
|
|
15965
16175
|
// TODO this method is a bit too generic
|
|
15966
16176
|
setLayerProperty(id, key, val, gl) {
|
|
@@ -18016,7 +18226,7 @@ import { vec3 as vec38, vec4 as vec46 } from "gl-matrix";
|
|
|
18016
18226
|
import * as nifti from "nifti-reader-js";
|
|
18017
18227
|
import daikon from "daikon";
|
|
18018
18228
|
import { mat3, mat4 as mat44, vec3 as vec37, vec4 as vec45 } from "gl-matrix";
|
|
18019
|
-
import { Decompress, decompressSync as decompressSync3, gzipSync } from "fflate/browser";
|
|
18229
|
+
import { Decompress, decompressSync as decompressSync3, gzipSync as gzipSync2 } from "fflate/browser";
|
|
18020
18230
|
import { v4 as uuidv42 } from "@lukeed/uuid";
|
|
18021
18231
|
|
|
18022
18232
|
// src/nvimage/utils.ts
|
|
@@ -20875,7 +21085,7 @@ var NVImage = class _NVImage {
|
|
|
20875
21085
|
let saveData = null;
|
|
20876
21086
|
const compress2 = fnm.endsWith(".gz");
|
|
20877
21087
|
if (compress2) {
|
|
20878
|
-
saveData =
|
|
21088
|
+
saveData = gzipSync2(odata, {
|
|
20879
21089
|
// GZIP-specific: the filename to use when decompressed
|
|
20880
21090
|
filename: fnm,
|
|
20881
21091
|
// GZIP-specific: the modification time. Can be a Date, date string,
|
|
@@ -21338,7 +21548,7 @@ var NVImage = class _NVImage {
|
|
|
21338
21548
|
* read a 3D slab of voxels from a volume
|
|
21339
21549
|
* @param voxStart - first row, column and slice (RAS order) for selection
|
|
21340
21550
|
* @param voxEnd - final row, column and slice (RAS order) for selection
|
|
21341
|
-
* @param dataType - array data type. Options: 'same' (default), 'uint8', 'float32'
|
|
21551
|
+
* @param dataType - array data type. Options: 'same' (default), 'uint8', 'float32', 'scaled', 'normalized', 'windowed'
|
|
21342
21552
|
* @returns the an array where ret[0] is the voxel values and ret[1] is dimension of selection
|
|
21343
21553
|
* @see {@link https://niivue.github.io/niivue/features/slab_selection.html | live demo usage}
|
|
21344
21554
|
*/
|
|
@@ -21362,7 +21572,7 @@ var NVImage = class _NVImage {
|
|
|
21362
21572
|
let dt = this.hdr.datatypeCode;
|
|
21363
21573
|
if (dataType === "uint8") {
|
|
21364
21574
|
dt = 2 /* DT_UINT8 */;
|
|
21365
|
-
} else if (dataType === "float32") {
|
|
21575
|
+
} else if (dataType === "float32" || dataType === "scaled" || dataType === "normalized" || dataType === "windowed") {
|
|
21366
21576
|
dt = 16 /* DT_FLOAT32 */;
|
|
21367
21577
|
}
|
|
21368
21578
|
if (dt === 2 /* DT_UINT8 */) {
|
|
@@ -21392,6 +21602,24 @@ var NVImage = class _NVImage {
|
|
|
21392
21602
|
}
|
|
21393
21603
|
}
|
|
21394
21604
|
}
|
|
21605
|
+
if (dataType === "scaled" || dataType === "normalized" || dataType === "windowed") {
|
|
21606
|
+
for (let i2 = 0; i2 < img.length; i2++) {
|
|
21607
|
+
img[i2] = img[i2] * this.hdr.scl_slope + this.hdr.scl_inter;
|
|
21608
|
+
}
|
|
21609
|
+
}
|
|
21610
|
+
if (dataType === "normalized" || dataType === "windowed") {
|
|
21611
|
+
let mn = this.cal_min;
|
|
21612
|
+
let mx = this.cal_max;
|
|
21613
|
+
if (dataType === "normalized") {
|
|
21614
|
+
mn = this.global_min;
|
|
21615
|
+
mx = this.global_max;
|
|
21616
|
+
}
|
|
21617
|
+
const scale = 1 / (mx - mn);
|
|
21618
|
+
for (let i2 = 0; i2 < img.length; i2++) {
|
|
21619
|
+
img[i2] = (img[i2] - mn) * scale;
|
|
21620
|
+
img[i2] = Math.max(Math.min(img[i2], 1), 0);
|
|
21621
|
+
}
|
|
21622
|
+
}
|
|
21395
21623
|
return [img, slabDims];
|
|
21396
21624
|
}
|
|
21397
21625
|
// getVolumeData()
|
|
@@ -25966,6 +26194,24 @@ var Niivue = class _Niivue {
|
|
|
25966
26194
|
}
|
|
25967
26195
|
return this.meshes[idx].indexNearestXYZmm(Xmm, Ymm, Zmm);
|
|
25968
26196
|
}
|
|
26197
|
+
/**
|
|
26198
|
+
* reduce complexity of FreeSurfer mesh
|
|
26199
|
+
* @param mesh - identity of mesh to change
|
|
26200
|
+
* @param order - decimation order 0..6
|
|
26201
|
+
* @example niivue.decimateHierarchicalMesh(niivue.meshes[0].id, 4)
|
|
26202
|
+
* @returns boolean false if mesh is not hierarchical or of lower order
|
|
26203
|
+
* @see {@link https://niivue.github.io/niivue/features/meshes.html | live demo usage}
|
|
26204
|
+
*/
|
|
26205
|
+
decimateHierarchicalMesh(mesh, order = 3) {
|
|
26206
|
+
const idx = this.getMeshIndexByID(mesh);
|
|
26207
|
+
if (idx < 0) {
|
|
26208
|
+
log.warn("reverseFaces() id not loaded", mesh);
|
|
26209
|
+
return;
|
|
26210
|
+
}
|
|
26211
|
+
const ret = this.meshes[idx].decimateHierarchicalMesh(this.gl, order);
|
|
26212
|
+
this.updateGLVolume();
|
|
26213
|
+
return ret;
|
|
26214
|
+
}
|
|
25969
26215
|
/**
|
|
25970
26216
|
* reverse triangle winding of mesh (swap front and back faces)
|
|
25971
26217
|
* @param id - identity of mesh to change
|
|
@@ -26464,6 +26710,9 @@ var Niivue = class _Niivue {
|
|
|
26464
26710
|
this.renderShader.use(this.gl);
|
|
26465
26711
|
this.gl.uniform3fv(this.renderShader.uniforms.clipLo, this.opts.clipVolumeLow);
|
|
26466
26712
|
this.gl.uniform3fv(this.renderShader.uniforms.clipHi, this.opts.clipVolumeHigh);
|
|
26713
|
+
this.pickingImageShader.use(this.gl);
|
|
26714
|
+
this.gl.uniform3fv(this.pickingImageShader.uniforms.clipLo, this.opts.clipVolumeLow);
|
|
26715
|
+
this.gl.uniform3fv(this.pickingImageShader.uniforms.clipHi, this.opts.clipVolumeHigh);
|
|
26467
26716
|
this.drawScene();
|
|
26468
26717
|
}
|
|
26469
26718
|
/**
|
|
@@ -28765,6 +29014,8 @@ var Niivue = class _Niivue {
|
|
|
28765
29014
|
this.pickingImageShader.use(this.gl);
|
|
28766
29015
|
this.gl.uniform1f(this.pickingImageShader.uniforms.overlays, this.overlays.length);
|
|
28767
29016
|
this.gl.uniform3fv(this.pickingImageShader.uniforms.texVox, vox);
|
|
29017
|
+
this.gl.uniform3fv(this.pickingImageShader.uniforms.clipLo, this.opts.clipVolumeLow);
|
|
29018
|
+
this.gl.uniform3fv(this.pickingImageShader.uniforms.clipHi, this.opts.clipVolumeHigh);
|
|
28768
29019
|
let shader = this.sliceMMShader;
|
|
28769
29020
|
if (this.opts.isV1SliceShader) {
|
|
28770
29021
|
shader = this.sliceV1Shader;
|
|
@@ -32679,6 +32930,7 @@ export {
|
|
|
32679
32930
|
NVMeshFromUrlOptions,
|
|
32680
32931
|
NVMeshLayerDefaults,
|
|
32681
32932
|
NVMeshLoaders,
|
|
32933
|
+
NVMeshUtilities,
|
|
32682
32934
|
NVUtilities,
|
|
32683
32935
|
Niivue,
|
|
32684
32936
|
SHOW_RENDER,
|