@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 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.60.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 readTag() {
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
- readTag();
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 readFirstTag1String(view, offset, footerLength) {
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 (tag !== 1) {
16874
+ if (length <= 0 || pos + length > end) {
16875
+ break;
16876
+ }
16877
+ if (tag !== tagToRead) {
16872
16878
  pos += length;
16873
16879
  continue;
16874
16880
  }
16875
- if (pos + 4 > end) {
16876
- return "";
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
- const strLen = view.getInt32(pos, false);
16879
- pos += 4;
16880
- if (strLen <= 1 || pos + strLen > end) {
16881
- return "";
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
- const raw = new Uint8Array(view.buffer, pos, strLen);
16884
- return new TextDecoder("utf-8").decode(raw.slice(0, -1));
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
- return "";
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.warn("readMgh called before nvImage.hdr was initialized. Creating default.");
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
- const bytes = new Uint8Array(buffer);
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
- const faceStrip = [0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0];
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("framebuffer status: ", status);
31767
+ log.error("blur shader: ", status);
31692
31768
  }
31693
31769
  gl.clear(gl.DEPTH_BUFFER_BIT);
31694
- gl.drawArrays(gl.TRIANGLE_STRIP, 0, faceStrip.length / 3);
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("framebuffer status: ", status);
31799
+ log.error("sobel shader: ", status);
31723
31800
  }
31724
31801
  gl.clear(gl.DEPTH_BUFFER_BIT);
31725
- gl.drawArrays(gl.TRIANGLE_STRIP, 0, faceStrip.length / 3);
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);