@niivue/niivue 0.43.7 → 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.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.7";
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
- if (n_vert !== n_vertex) {
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 NIfTI (" + nvert + ") is not a multiple of number of vertices (" + n_vert + ")");
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 NIfTI (" + nvert + ") is not a multiple of number of vertices (" + n_vert + ")");
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
- const nvert = width * height * depth * nframes;
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 NIfTI (" + nvert + ") is not a multiple of number of vertices (" + n_vert + ")");
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 = gzipSync(odata, {
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()
@@ -21929,6 +22157,12 @@ var SLICE_TYPE = /* @__PURE__ */ ((SLICE_TYPE2) => {
21929
22157
  SLICE_TYPE2[SLICE_TYPE2["RENDER"] = 4] = "RENDER";
21930
22158
  return SLICE_TYPE2;
21931
22159
  })(SLICE_TYPE || {});
22160
+ var SHOW_RENDER = /* @__PURE__ */ ((SHOW_RENDER2) => {
22161
+ SHOW_RENDER2[SHOW_RENDER2["NEVER"] = 0] = "NEVER";
22162
+ SHOW_RENDER2[SHOW_RENDER2["ALWAYS"] = 1] = "ALWAYS";
22163
+ SHOW_RENDER2[SHOW_RENDER2["AUTO"] = 2] = "AUTO";
22164
+ return SHOW_RENDER2;
22165
+ })(SHOW_RENDER || {});
21932
22166
  var MULTIPLANAR_TYPE = /* @__PURE__ */ ((MULTIPLANAR_TYPE2) => {
21933
22167
  MULTIPLANAR_TYPE2[MULTIPLANAR_TYPE2["AUTO"] = 0] = "AUTO";
21934
22168
  MULTIPLANAR_TYPE2[MULTIPLANAR_TYPE2["COLUMN"] = 1] = "COLUMN";
@@ -21975,7 +22209,10 @@ var DEFAULT_OPTIONS = {
21975
22209
  isColorbar: false,
21976
22210
  isOrientCube: false,
21977
22211
  multiplanarPadPixels: 0,
22212
+ // @deprecated
21978
22213
  multiplanarForceRender: false,
22214
+ multiplanarShowRender: 2 /* AUTO */,
22215
+ // auto is the same behaviour as multiplanarForceRender: false
21979
22216
  isRadiologicalConvention: false,
21980
22217
  meshThicknessOn2D: Infinity,
21981
22218
  dragMode: 1 /* contrast */,
@@ -24997,6 +25234,39 @@ var Niivue = class _Niivue {
24997
25234
  this.addVolume(volume);
24998
25235
  return volume;
24999
25236
  }
25237
+ async addVolumesFromUrl(imageOptionsArray) {
25238
+ const promises = imageOptionsArray.map(async (imageItem) => {
25239
+ const imageOptions = {
25240
+ url: imageItem.url,
25241
+ headers: imageItem.headers,
25242
+ name: imageItem.name,
25243
+ colormap: imageItem.colormap ? imageItem.colormap : imageItem.colorMap,
25244
+ colormapNegative: imageItem.colormapNegative ? imageItem.colormapNegative : imageItem.colorMapNegative,
25245
+ opacity: imageItem.opacity,
25246
+ urlImgData: imageItem.urlImgData,
25247
+ cal_min: imageItem.cal_min,
25248
+ cal_max: imageItem.cal_max,
25249
+ trustCalMinMax: this.opts.trustCalMinMax,
25250
+ isManifest: imageItem.isManifest,
25251
+ frame4D: imageItem.frame4D,
25252
+ limitFrames4D: imageItem.limitFrames4D || this.opts.limitFrames4D,
25253
+ colorbarVisible: imageItem.colorbarVisible
25254
+ };
25255
+ const volume = await NVImage.loadFromUrl(imageOptions);
25256
+ this.document.addImageOptions(volume, imageOptions);
25257
+ volume.onColormapChange = this.onColormapChange;
25258
+ this.mediaUrlMap.set(volume, imageOptions.url);
25259
+ if (this.onVolumeAddedFromUrl) {
25260
+ this.onVolumeAddedFromUrl(imageOptions, volume);
25261
+ }
25262
+ return volume;
25263
+ });
25264
+ const volumes = await Promise.all(promises);
25265
+ for (let i = 0; i < volumes.length; i++) {
25266
+ this.addVolume(volumes[i]);
25267
+ }
25268
+ return volumes;
25269
+ }
25000
25270
  /**
25001
25271
  * Find media by url
25002
25272
  */
@@ -25924,6 +26194,24 @@ var Niivue = class _Niivue {
25924
26194
  }
25925
26195
  return this.meshes[idx].indexNearestXYZmm(Xmm, Ymm, Zmm);
25926
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
+ }
25927
26215
  /**
25928
26216
  * reverse triangle winding of mesh (swap front and back faces)
25929
26217
  * @param id - identity of mesh to change
@@ -26422,6 +26710,9 @@ var Niivue = class _Niivue {
26422
26710
  this.renderShader.use(this.gl);
26423
26711
  this.gl.uniform3fv(this.renderShader.uniforms.clipLo, this.opts.clipVolumeLow);
26424
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);
26425
26716
  this.drawScene();
26426
26717
  }
26427
26718
  /**
@@ -26775,28 +27066,27 @@ var Niivue = class _Niivue {
26775
27066
  this.volumes = [];
26776
27067
  this.gl.clearColor(0, 0, 0, 1);
26777
27068
  this.gl.clear(this.gl.COLOR_BUFFER_BIT);
26778
- for (let i = 0; i < volumeList.length; i++) {
26779
- if (volumeList[i].colorMap !== void 0) {
26780
- volumeList[i].colormap = volumeList[i].colorMap;
26781
- }
26782
- const imageOptions = {
26783
- url: volumeList[i].url,
26784
- headers: volumeList[i].headers,
26785
- name: volumeList[i].name,
26786
- colormap: volumeList[i].colormap,
26787
- colormapNegative: volumeList[i].colormapNegative,
26788
- opacity: volumeList[i].opacity,
26789
- urlImgData: volumeList[i].urlImgData,
26790
- cal_min: volumeList[i].cal_min,
26791
- cal_max: volumeList[i].cal_max,
26792
- trustCalMinMax: this.opts.trustCalMinMax,
26793
- isManifest: volumeList[i].isManifest,
26794
- frame4D: volumeList[i].frame4D,
26795
- limitFrames4D: volumeList[i].limitFrames4D || this.opts.limitFrames4D,
26796
- colorbarVisible: volumeList[i].colorbarVisible
26797
- };
26798
- await this.addVolumeFromUrl(imageOptions);
27069
+ if (volumeList.length > 1) {
27070
+ await this.addVolumesFromUrl(volumeList);
27071
+ return this;
26799
27072
  }
27073
+ const imageOptions = {
27074
+ url: volumeList[0].url,
27075
+ headers: volumeList[0].headers,
27076
+ name: volumeList[0].name,
27077
+ colormap: volumeList[0].colormap ? volumeList[0].colormap : volumeList[0].colorMap,
27078
+ colormapNegative: volumeList[0].colormapNegative ? volumeList[0].colormapNegative : volumeList[0].colorMapNegative,
27079
+ opacity: volumeList[0].opacity,
27080
+ urlImgData: volumeList[0].urlImgData,
27081
+ cal_min: volumeList[0].cal_min,
27082
+ cal_max: volumeList[0].cal_max,
27083
+ trustCalMinMax: this.opts.trustCalMinMax,
27084
+ isManifest: volumeList[0].isManifest,
27085
+ frame4D: volumeList[0].frame4D,
27086
+ limitFrames4D: volumeList[0].limitFrames4D || this.opts.limitFrames4D,
27087
+ colorbarVisible: volumeList[0].colorbarVisible
27088
+ };
27089
+ await this.addVolumeFromUrl(imageOptions);
26800
27090
  return this;
26801
27091
  }
26802
27092
  /**
@@ -26820,6 +27110,32 @@ var Niivue = class _Niivue {
26820
27110
  this.addMesh(mesh);
26821
27111
  return mesh;
26822
27112
  }
27113
+ /**
27114
+ * Add mesh and notify subscribers
27115
+ * @see {@link https://niivue.github.io/niivue/features/multiuser.meshes.html | live demo usage}
27116
+ */
27117
+ async addMeshesFromUrl(meshOptions) {
27118
+ const promises = meshOptions.map(async (meshItem) => {
27119
+ const ext = this.getFileExt(meshItem.url);
27120
+ if (ext === "JCON" || ext === "JSON") {
27121
+ const response = await fetch(meshItem.url, {});
27122
+ const json = await response.json();
27123
+ const mesh2 = this.loadConnectomeAsMesh(json);
27124
+ this.mediaUrlMap.set(mesh2, meshItem.url);
27125
+ this.onMeshAddedFromUrl(meshItem, mesh2);
27126
+ return mesh2;
27127
+ }
27128
+ const mesh = await NVMesh2.loadFromUrl({ ...meshItem, gl: this.gl });
27129
+ this.mediaUrlMap.set(mesh, meshItem.url);
27130
+ this.onMeshAddedFromUrl(meshItem, mesh);
27131
+ return mesh;
27132
+ });
27133
+ const meshes = await Promise.all(promises);
27134
+ for (let i = 0; i < meshes.length; i++) {
27135
+ this.addMesh(meshes[i]);
27136
+ }
27137
+ return meshes;
27138
+ }
26823
27139
  /**
26824
27140
  * load an array of meshes
26825
27141
  * @param meshList - the array of objects to load. each object must have a resolvable "url" property at a minimum
@@ -26841,9 +27157,13 @@ var Niivue = class _Niivue {
26841
27157
  this.meshes = [];
26842
27158
  this.gl.clearColor(0, 0, 0, 1);
26843
27159
  this.gl.clear(this.gl.COLOR_BUFFER_BIT);
26844
- for (let i = 0; i < meshList.length; i++) {
26845
- await this.addMeshFromUrl(meshList[i]);
27160
+ if (meshList.length > 1) {
27161
+ await this.addMeshesFromUrl(meshList);
27162
+ this.updateGLVolume();
27163
+ this.drawScene();
27164
+ return this;
26846
27165
  }
27166
+ await this.addMeshFromUrl(meshList[0]);
26847
27167
  this.updateGLVolume();
26848
27168
  this.drawScene();
26849
27169
  return this;
@@ -28694,6 +29014,8 @@ var Niivue = class _Niivue {
28694
29014
  this.pickingImageShader.use(this.gl);
28695
29015
  this.gl.uniform1f(this.pickingImageShader.uniforms.overlays, this.overlays.length);
28696
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);
28697
29019
  let shader = this.sliceMMShader;
28698
29020
  if (this.opts.isV1SliceShader) {
28699
29021
  shader = this.sliceV1Shader;
@@ -30104,6 +30426,9 @@ var Niivue = class _Niivue {
30104
30426
  width = bulletMargin + longestTextLength;
30105
30427
  width += horizontalMargin * 2;
30106
30428
  }
30429
+ if (width >= this.gl.canvas.width) {
30430
+ return 0;
30431
+ }
30107
30432
  return width;
30108
30433
  }
30109
30434
  getLegendPanelHeight() {
@@ -32390,6 +32715,20 @@ var Niivue = class _Niivue {
32390
32715
  } else if (this.opts.sliceType === 2 /* SAGITTAL */) {
32391
32716
  this.draw2D([0, 0, 0, 0], 2);
32392
32717
  } else {
32718
+ let isShowRender = false;
32719
+ if (this.opts.multiplanarForceRender) {
32720
+ isShowRender = true;
32721
+ if (this.opts.multiplanarForceRender) {
32722
+ this.opts.multiplanarShowRender = 1 /* ALWAYS */;
32723
+ } else {
32724
+ this.opts.multiplanarShowRender = 2 /* AUTO */;
32725
+ }
32726
+ delete this.opts.multiplanarForceRender;
32727
+ } else {
32728
+ if (this.opts.multiplanarShowRender === 1 /* ALWAYS */) {
32729
+ isShowRender = true;
32730
+ }
32731
+ }
32393
32732
  const isDrawPenDown = isFinite(this.drawPenLocation[0]) && this.opts.drawingEnabled;
32394
32733
  const { volScale } = this.sliceScale();
32395
32734
  if (typeof this.opts.multiplanarPadPixels !== "number") {
@@ -32431,7 +32770,7 @@ var Niivue = class _Niivue {
32431
32770
  }
32432
32771
  if (isDrawColumn) {
32433
32772
  let ltwh = ltwh1x3;
32434
- if (this.opts.multiplanarForceRender || ltwh1x4[4] >= ltwh1x3[4]) {
32773
+ if (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh1x4[4] >= ltwh1x3[4]) {
32435
32774
  ltwh = ltwh1x4;
32436
32775
  } else {
32437
32776
  isDraw3D = false;
@@ -32448,7 +32787,7 @@ var Niivue = class _Niivue {
32448
32787
  }
32449
32788
  } else if (isDrawRow) {
32450
32789
  let ltwh = ltwh3x1;
32451
- if (this.opts.multiplanarForceRender || ltwh4x1[4] >= ltwh3x1[4]) {
32790
+ if (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh4x1[4] >= ltwh3x1[4]) {
32452
32791
  ltwh = ltwh4x1;
32453
32792
  } else {
32454
32793
  isDraw3D = false;
@@ -32463,6 +32802,12 @@ var Niivue = class _Niivue {
32463
32802
  this.draw3D([ltwh[0] + sX + sX + sY + pad * 3, ltwh[1], ltwh[3], ltwh[3]]);
32464
32803
  }
32465
32804
  } else if (isDrawGrid) {
32805
+ if (!isShowRender) {
32806
+ isDraw3D = false;
32807
+ }
32808
+ if (this.opts.multiplanarShowRender === 2 /* AUTO */) {
32809
+ isDraw3D = true;
32810
+ }
32466
32811
  const ltwh = ltwh2x2;
32467
32812
  const sX = volScale[0] * ltwh[4];
32468
32813
  const sY = volScale[1] * ltwh[4];
@@ -32585,8 +32930,10 @@ export {
32585
32930
  NVMeshFromUrlOptions,
32586
32931
  NVMeshLayerDefaults,
32587
32932
  NVMeshLoaders,
32933
+ NVMeshUtilities,
32588
32934
  NVUtilities,
32589
32935
  Niivue,
32936
+ SHOW_RENDER,
32590
32937
  SLICE_TYPE,
32591
32938
  cmapper,
32592
32939
  ColorTables as colortables