@niivue/niivue 0.60.0 → 0.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.js +130 -52
- package/build/index.js.map +1 -1
- package/build/index.min.js +1 -1
- package/build/metafile-esm.json +1 -1
- package/dist/index.js +130 -52
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/niivue.umd.js +99 -97
- package/package.json +1 -1
- package/src/niivue/index.ts +14 -23
- package/src/nvimage/ImageReaders/mgh.ts +166 -41
- package/src/nvimage/index.ts +2 -1
package/build/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import { mat4 as mat49, vec2, vec3 as vec314, vec4 as vec410 } from "gl-matrix";
|
|
|
19
19
|
// package.json
|
|
20
20
|
var package_default = {
|
|
21
21
|
name: "@niivue/niivue",
|
|
22
|
-
version: "0.
|
|
22
|
+
version: "0.61.0",
|
|
23
23
|
description: "minimal webgl2 nifti image viewer",
|
|
24
24
|
types: "./build/index.d.ts",
|
|
25
25
|
main: "./build/index.js",
|
|
@@ -12224,7 +12224,7 @@ var NVUtilities = class _NVUtilities {
|
|
|
12224
12224
|
}
|
|
12225
12225
|
return new Float64Array(byteArray.buffer);
|
|
12226
12226
|
}
|
|
12227
|
-
function
|
|
12227
|
+
function readTag2() {
|
|
12228
12228
|
const mtype = reader.getUint32(pos, true);
|
|
12229
12229
|
const mrows = reader.getUint32(pos + 4, true);
|
|
12230
12230
|
const ncols = reader.getUint32(pos + 8, true);
|
|
@@ -12263,7 +12263,7 @@ var NVUtilities = class _NVUtilities {
|
|
|
12263
12263
|
pos = posEnd;
|
|
12264
12264
|
}
|
|
12265
12265
|
while (pos + 20 < len) {
|
|
12266
|
-
|
|
12266
|
+
readTag2();
|
|
12267
12267
|
}
|
|
12268
12268
|
return mat;
|
|
12269
12269
|
}
|
|
@@ -16857,37 +16857,133 @@ function setVolumeData(nvImage, voxStartRAS = [-1, 0, 0], voxEndRAS = [0, 0, 0],
|
|
|
16857
16857
|
// src/nvimage/ImageReaders/mgh.ts
|
|
16858
16858
|
var mgh_exports = {};
|
|
16859
16859
|
__export(mgh_exports, {
|
|
16860
|
+
isFreeSurferLabelImage: () => isFreeSurferLabelImage,
|
|
16861
|
+
optimizeFreeSurferLabels: () => optimizeFreeSurferLabels,
|
|
16860
16862
|
readMgh: () => readMgh
|
|
16861
16863
|
});
|
|
16862
16864
|
import { NIFTI1 as NIFTI12 } from "nifti-reader-js";
|
|
16863
16865
|
import { mat4 as mat44, vec4 as vec45, vec3 as vec36 } from "gl-matrix";
|
|
16864
|
-
function
|
|
16866
|
+
function readTag(view, offset, footerLength, tagToRead = 1) {
|
|
16865
16867
|
const end = offset + footerLength;
|
|
16866
16868
|
let pos = offset;
|
|
16869
|
+
const results = [];
|
|
16867
16870
|
while (pos + 12 <= end) {
|
|
16868
16871
|
const tag = view.getInt32(pos, false);
|
|
16869
16872
|
const length = view.getInt32(pos + 8, false);
|
|
16870
16873
|
pos += 12;
|
|
16871
|
-
if (
|
|
16874
|
+
if (length <= 0 || pos + length > end) {
|
|
16875
|
+
break;
|
|
16876
|
+
}
|
|
16877
|
+
if (tag !== tagToRead) {
|
|
16872
16878
|
pos += length;
|
|
16873
16879
|
continue;
|
|
16874
16880
|
}
|
|
16875
|
-
|
|
16876
|
-
|
|
16881
|
+
let strLen = length;
|
|
16882
|
+
let contentPos = pos;
|
|
16883
|
+
if (tagToRead === 1) {
|
|
16884
|
+
if (pos + 4 > end) {
|
|
16885
|
+
break;
|
|
16886
|
+
}
|
|
16887
|
+
strLen = view.getInt32(pos, false);
|
|
16888
|
+
contentPos += 4;
|
|
16889
|
+
}
|
|
16890
|
+
if (strLen > 1 && contentPos + strLen <= end) {
|
|
16891
|
+
const raw = new Uint8Array(view.buffer, contentPos, strLen);
|
|
16892
|
+
const str = new TextDecoder("utf-8").decode(raw.slice(0, -1));
|
|
16893
|
+
results.push(str);
|
|
16877
16894
|
}
|
|
16878
|
-
|
|
16879
|
-
|
|
16880
|
-
|
|
16881
|
-
|
|
16895
|
+
pos += length;
|
|
16896
|
+
}
|
|
16897
|
+
return results.join("\n\n");
|
|
16898
|
+
}
|
|
16899
|
+
function optimizeFreeSurferLabels(hdr, imgRaw) {
|
|
16900
|
+
hdr.intent_code = 1002;
|
|
16901
|
+
if (hdr.datatypeCode !== 16 /* DT_FLOAT32 */ && hdr.datatypeCode !== 8 /* DT_INT32 */) {
|
|
16902
|
+
return imgRaw;
|
|
16903
|
+
}
|
|
16904
|
+
let img = new Float32Array(imgRaw);
|
|
16905
|
+
if (hdr.datatypeCode === 8 /* DT_INT32 */) {
|
|
16906
|
+
img = new Int32Array(imgRaw);
|
|
16907
|
+
}
|
|
16908
|
+
if (isPlatformLittleEndian()) {
|
|
16909
|
+
const u32 = new Uint32Array(imgRaw);
|
|
16910
|
+
for (let i = 0; i < u32.length; i++) {
|
|
16911
|
+
const val = u32[i];
|
|
16912
|
+
u32[i] = (val & 255) << 24 | (val & 65280) << 8 | (val & 16711680) >>> 8 | (val & 4278190080) >>> 24;
|
|
16913
|
+
}
|
|
16914
|
+
}
|
|
16915
|
+
hdr.littleEndian = isPlatformLittleEndian();
|
|
16916
|
+
let isInteger = true;
|
|
16917
|
+
let mn = Infinity;
|
|
16918
|
+
let mx = -Infinity;
|
|
16919
|
+
for (let i = 0; i < img.length; i++) {
|
|
16920
|
+
const v = img[i];
|
|
16921
|
+
if (!Number.isFinite(v)) {
|
|
16922
|
+
continue;
|
|
16923
|
+
}
|
|
16924
|
+
if (!Number.isInteger(v)) {
|
|
16925
|
+
isInteger = false;
|
|
16926
|
+
}
|
|
16927
|
+
if (v < mn) {
|
|
16928
|
+
mn = v;
|
|
16882
16929
|
}
|
|
16883
|
-
|
|
16884
|
-
|
|
16930
|
+
if (v > mx) {
|
|
16931
|
+
mx = v;
|
|
16932
|
+
}
|
|
16933
|
+
}
|
|
16934
|
+
if (!isInteger || mn < 0 || mx > 2147483647) {
|
|
16935
|
+
log.warn(`FreeSurfer Labels must be integers in INT32 range. range ${mn}..${mx}`);
|
|
16936
|
+
return imgRaw;
|
|
16937
|
+
}
|
|
16938
|
+
if (mx > 32767) {
|
|
16939
|
+
hdr.datatypeCode = 8 /* DT_INT32 */;
|
|
16940
|
+
const out = new Int32Array(img.length);
|
|
16941
|
+
for (let i = 0; i < img.length; i++) {
|
|
16942
|
+
out[i] = Math.trunc(img[i]);
|
|
16943
|
+
}
|
|
16944
|
+
return out.buffer;
|
|
16945
|
+
} else if (mx > 255) {
|
|
16946
|
+
hdr.datatypeCode = 4 /* DT_INT16 */;
|
|
16947
|
+
hdr.numBitsPerVoxel = 16;
|
|
16948
|
+
const out = new Int16Array(img.length);
|
|
16949
|
+
for (let i = 0; i < img.length; i++) {
|
|
16950
|
+
out[i] = Math.trunc(img[i]);
|
|
16951
|
+
}
|
|
16952
|
+
return out.buffer;
|
|
16953
|
+
} else {
|
|
16954
|
+
hdr.datatypeCode = 2 /* DT_UINT8 */;
|
|
16955
|
+
hdr.numBitsPerVoxel = 8;
|
|
16956
|
+
const out = new Uint8Array(img.length);
|
|
16957
|
+
for (let i = 0; i < img.length; i++) {
|
|
16958
|
+
out[i] = Math.trunc(img[i]);
|
|
16959
|
+
}
|
|
16960
|
+
return out.buffer;
|
|
16885
16961
|
}
|
|
16886
|
-
|
|
16962
|
+
}
|
|
16963
|
+
function isFreeSurferLabelImage(raw, hdr, expectedBytes) {
|
|
16964
|
+
const remainingBytes = raw.byteLength - hdr.vox_offset;
|
|
16965
|
+
if (remainingBytes < expectedBytes) {
|
|
16966
|
+
log.error(`MGH image data size mismatch: expected ${expectedBytes}, found ${remainingBytes}`);
|
|
16967
|
+
return false;
|
|
16968
|
+
}
|
|
16969
|
+
if (remainingBytes === expectedBytes) {
|
|
16970
|
+
return false;
|
|
16971
|
+
}
|
|
16972
|
+
const footerStart = hdr.vox_offset + expectedBytes + 20;
|
|
16973
|
+
const footerLength = raw.byteLength - footerStart;
|
|
16974
|
+
if (footerLength <= 12) {
|
|
16975
|
+
return false;
|
|
16976
|
+
}
|
|
16977
|
+
const tag1 = readTag(new DataView(raw), footerStart, footerLength);
|
|
16978
|
+
if (tag1.toLowerCase().endsWith("lut.txt")) {
|
|
16979
|
+
return true;
|
|
16980
|
+
}
|
|
16981
|
+
const tag3 = readTag(new DataView(raw), footerStart, footerLength, 3);
|
|
16982
|
+
return tag3.includes("mri_label2vol");
|
|
16887
16983
|
}
|
|
16888
16984
|
async function readMgh(nvImage, buffer) {
|
|
16889
16985
|
if (!nvImage.hdr) {
|
|
16890
|
-
log.
|
|
16986
|
+
log.debug("readMgh called before nvImage.hdr was initialized. Creating default.");
|
|
16891
16987
|
nvImage.hdr = new NIFTI12();
|
|
16892
16988
|
}
|
|
16893
16989
|
const hdr = nvImage.hdr;
|
|
@@ -17000,24 +17096,10 @@ async function readMgh(nvImage, buffer) {
|
|
|
17000
17096
|
const nBytesPerVoxel = hdr.numBitsPerVoxel / 8;
|
|
17001
17097
|
const nVoxels = width * height * depth * hdr.dims[4];
|
|
17002
17098
|
const expectedBytes = nVoxels * nBytesPerVoxel;
|
|
17003
|
-
const remainingBytes = raw.byteLength - hdr.vox_offset;
|
|
17004
|
-
if (remainingBytes < expectedBytes) {
|
|
17005
|
-
log.error(`MGH image data size mismatch: expected ${expectedBytes}, found ${remainingBytes}`);
|
|
17006
|
-
return null;
|
|
17007
|
-
} else if (remainingBytes > expectedBytes) {
|
|
17008
|
-
log.warn(`MGH file has extra ${remainingBytes - expectedBytes} bytes after image data. Truncating.`);
|
|
17009
|
-
const footerStart = hdr.vox_offset + expectedBytes + 20;
|
|
17010
|
-
const footerLength = raw.byteLength - footerStart;
|
|
17011
|
-
if (footerLength > 12) {
|
|
17012
|
-
const firstTag1String = readFirstTag1String(reader, footerStart, footerLength);
|
|
17013
|
-
const isLUT = firstTag1String.toLowerCase().endsWith("lut.txt");
|
|
17014
|
-
if (isLUT) {
|
|
17015
|
-
hdr.intent_code = 1002;
|
|
17016
|
-
}
|
|
17017
|
-
log.debug(`First tag 1 string: ${firstTag1String} isLUT: ${isLUT}`);
|
|
17018
|
-
}
|
|
17019
|
-
}
|
|
17020
17099
|
const imgRaw = raw.slice(hdr.vox_offset, hdr.vox_offset + expectedBytes);
|
|
17100
|
+
if (isFreeSurferLabelImage(raw, hdr, expectedBytes)) {
|
|
17101
|
+
return optimizeFreeSurferLabels(hdr, imgRaw);
|
|
17102
|
+
}
|
|
17021
17103
|
return imgRaw;
|
|
17022
17104
|
}
|
|
17023
17105
|
|
|
@@ -19075,11 +19157,12 @@ var NVImage = class _NVImage {
|
|
|
19075
19157
|
if (len < 20) {
|
|
19076
19158
|
throw new Error("File too small to be MIF: bytes = " + len);
|
|
19077
19159
|
}
|
|
19078
|
-
|
|
19160
|
+
let bytes = new Uint8Array(buffer);
|
|
19079
19161
|
if (bytes[0] === 31 && bytes[1] === 139) {
|
|
19080
19162
|
log.debug("MIF with GZ decompression");
|
|
19081
19163
|
buffer = await NVUtilities.decompressToBuffer(new Uint8Array(buffer));
|
|
19082
19164
|
len = buffer.byteLength;
|
|
19165
|
+
bytes = new Uint8Array(buffer);
|
|
19083
19166
|
}
|
|
19084
19167
|
let pos = 0;
|
|
19085
19168
|
function readStr() {
|
|
@@ -29370,6 +29453,7 @@ var Niivue = class {
|
|
|
29370
29453
|
this.renderShader = this.renderSliceShader;
|
|
29371
29454
|
}
|
|
29372
29455
|
}
|
|
29456
|
+
await this.refreshLayers(this.volumes[0], 0);
|
|
29373
29457
|
this.initRenderShader(this.renderShader, gradientAmount);
|
|
29374
29458
|
this.renderShader.use(this.gl);
|
|
29375
29459
|
this.setClipPlaneColor(this.opts.clipPlaneColor);
|
|
@@ -29381,7 +29465,6 @@ var Niivue = class {
|
|
|
29381
29465
|
if (this.volumes.length < 1) {
|
|
29382
29466
|
return;
|
|
29383
29467
|
}
|
|
29384
|
-
this.refreshLayers(this.volumes[0], 0);
|
|
29385
29468
|
this.drawScene();
|
|
29386
29469
|
}
|
|
29387
29470
|
/**
|
|
@@ -31658,17 +31741,9 @@ var Niivue = class {
|
|
|
31658
31741
|
*/
|
|
31659
31742
|
gradientGL(hdr) {
|
|
31660
31743
|
const gl = this.gl;
|
|
31661
|
-
|
|
31662
|
-
const vao2 = gl.createVertexArray();
|
|
31663
|
-
gl.bindVertexArray(vao2);
|
|
31664
|
-
const vbo2 = gl.createBuffer();
|
|
31665
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, vbo2);
|
|
31666
|
-
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(faceStrip), gl.STATIC_DRAW);
|
|
31667
|
-
gl.enableVertexAttribArray(0);
|
|
31668
|
-
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
|
|
31744
|
+
gl.bindVertexArray(this.genericVAO);
|
|
31669
31745
|
const fb = gl.createFramebuffer();
|
|
31670
31746
|
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
|
|
31671
|
-
gl.disable(gl.CULL_FACE);
|
|
31672
31747
|
gl.viewport(0, 0, hdr.dims[1], hdr.dims[2]);
|
|
31673
31748
|
gl.disable(gl.BLEND);
|
|
31674
31749
|
const tempTex3D = this.rgbaTex(null, TEXTURE8_GRADIENT_TEMP, hdr.dims, true);
|
|
@@ -31676,27 +31751,30 @@ var Niivue = class {
|
|
|
31676
31751
|
blurShader.use(gl);
|
|
31677
31752
|
gl.activeTexture(TEXTURE0_BACK_VOL);
|
|
31678
31753
|
gl.bindTexture(gl.TEXTURE_3D, this.volumeTexture);
|
|
31754
|
+
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
31755
|
+
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
31679
31756
|
const blurRadius = 0.7;
|
|
31680
31757
|
gl.uniform1i(blurShader.uniforms.intensityVol, 0);
|
|
31681
31758
|
gl.uniform1f(blurShader.uniforms.dX, blurRadius / hdr.dims[1]);
|
|
31682
31759
|
gl.uniform1f(blurShader.uniforms.dY, blurRadius / hdr.dims[2]);
|
|
31683
31760
|
gl.uniform1f(blurShader.uniforms.dZ, blurRadius / hdr.dims[3]);
|
|
31684
|
-
gl.bindVertexArray(vao2);
|
|
31685
31761
|
for (let i = 0; i < hdr.dims[3] - 1; i++) {
|
|
31686
31762
|
const coordZ = 1 / hdr.dims[3] * (i + 0.5);
|
|
31687
31763
|
gl.uniform1f(blurShader.uniforms.coordZ, coordZ);
|
|
31688
31764
|
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, tempTex3D, 0, i);
|
|
31689
31765
|
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
|
|
31690
31766
|
if (status !== gl.FRAMEBUFFER_COMPLETE) {
|
|
31691
|
-
log.error("
|
|
31767
|
+
log.error("blur shader: ", status);
|
|
31692
31768
|
}
|
|
31693
31769
|
gl.clear(gl.DEPTH_BUFFER_BIT);
|
|
31694
|
-
gl.drawArrays(gl.TRIANGLE_STRIP, 0,
|
|
31770
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
31695
31771
|
}
|
|
31696
31772
|
const sobelShader = this.opts.gradientOrder === 2 ? this.sobelSecondOrderShader : this.sobelFirstOrderShader;
|
|
31697
31773
|
sobelShader.use(gl);
|
|
31698
31774
|
gl.activeTexture(TEXTURE8_GRADIENT_TEMP);
|
|
31699
31775
|
gl.bindTexture(gl.TEXTURE_3D, tempTex3D);
|
|
31776
|
+
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
31777
|
+
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
31700
31778
|
gl.uniform1i(sobelShader.uniforms.intensityVol, 8);
|
|
31701
31779
|
const sobelRadius = 0.7;
|
|
31702
31780
|
gl.uniform1f(sobelShader.uniforms.dX, sobelRadius / hdr.dims[1]);
|
|
@@ -31708,7 +31786,6 @@ var Niivue = class {
|
|
|
31708
31786
|
gl.uniform1f(sobelShader.uniforms.dZ2, 2 * sobelRadius / hdr.dims[3]);
|
|
31709
31787
|
}
|
|
31710
31788
|
gl.uniform1f(sobelShader.uniforms.coordZ, 0.5);
|
|
31711
|
-
gl.bindVertexArray(vao2);
|
|
31712
31789
|
if (this.gradientTexture !== null) {
|
|
31713
31790
|
gl.deleteTexture(this.gradientTexture);
|
|
31714
31791
|
}
|
|
@@ -31719,15 +31796,15 @@ var Niivue = class {
|
|
|
31719
31796
|
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.gradientTexture, 0, i);
|
|
31720
31797
|
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
|
|
31721
31798
|
if (status !== gl.FRAMEBUFFER_COMPLETE) {
|
|
31722
|
-
log.error("
|
|
31799
|
+
log.error("sobel shader: ", status);
|
|
31723
31800
|
}
|
|
31724
31801
|
gl.clear(gl.DEPTH_BUFFER_BIT);
|
|
31725
|
-
gl.drawArrays(gl.TRIANGLE_STRIP, 0,
|
|
31802
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
31726
31803
|
}
|
|
31727
31804
|
gl.deleteFramebuffer(fb);
|
|
31728
31805
|
gl.deleteTexture(tempTex3D);
|
|
31729
|
-
gl.deleteBuffer(vbo2);
|
|
31730
31806
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
31807
|
+
this.gl.bindVertexArray(this.unusedVAO);
|
|
31731
31808
|
}
|
|
31732
31809
|
/**
|
|
31733
31810
|
* Get the gradient texture produced by gradientGL as a TypedArray
|
|
@@ -32594,6 +32671,7 @@ var Niivue = class {
|
|
|
32594
32671
|
this.volumeTexture = outTexture;
|
|
32595
32672
|
if (this.gradientTextureAmount > 0 && !this.useCustomGradientTexture) {
|
|
32596
32673
|
this.gradientGL(hdr);
|
|
32674
|
+
this.gl.bindVertexArray(this.genericVAO);
|
|
32597
32675
|
} else if (this.gradientTextureAmount <= 0) {
|
|
32598
32676
|
if (this.gradientTexture !== null) {
|
|
32599
32677
|
this.gl.deleteTexture(this.gradientTexture);
|
|
@@ -32608,7 +32686,7 @@ var Niivue = class {
|
|
|
32608
32686
|
const slicescl = this.sliceScale(true);
|
|
32609
32687
|
const vox = slicescl.vox;
|
|
32610
32688
|
const volScale = slicescl.volScale;
|
|
32611
|
-
this.gl.uniform1f(this.renderShader.uniforms.overlays, this.overlays);
|
|
32689
|
+
this.gl.uniform1f(this.renderShader.uniforms.overlays, this.overlays.length);
|
|
32612
32690
|
this.gl.uniform4fv(this.renderShader.uniforms.clipPlaneColor, this.opts.clipPlaneColor);
|
|
32613
32691
|
this.gl.uniform1f(this.renderShader.uniforms.clipThick, this.opts.clipThick);
|
|
32614
32692
|
this.gl.uniform3fv(this.renderShader.uniforms.clipLo, this.opts.clipVolumeLow);
|
|
@@ -35950,7 +36028,7 @@ var Niivue = class {
|
|
|
35950
36028
|
}
|
|
35951
36029
|
shader.use(this.gl);
|
|
35952
36030
|
gl.uniform1i(shader.uniforms.backgroundMasksOverlays, this.backgroundMasksOverlays);
|
|
35953
|
-
if (this.gradientTextureAmount > 0) {
|
|
36031
|
+
if (this.gradientTextureAmount > 0 && shader.uniforms.normMtx && this.gradientTexture) {
|
|
35954
36032
|
gl.activeTexture(TEXTURE6_GRADIENT);
|
|
35955
36033
|
gl.bindTexture(gl.TEXTURE_3D, this.gradientTexture);
|
|
35956
36034
|
const modelMatrix = this.calculateModelMatrix(azimuth, elevation);
|