@niivue/niivue 0.44.2 → 0.45.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/dist/index.js CHANGED
@@ -95525,7 +95525,7 @@ var forEach3 = function() {
95525
95525
  }();
95526
95526
 
95527
95527
  // package.json
95528
- var version = "0.44.2";
95528
+ var version = "0.45.0";
95529
95529
 
95530
95530
  // src/logger.ts
95531
95531
  var _Log = class _Log {
@@ -109109,6 +109109,12 @@ var NVMeshLoaders = class _NVMeshLoaders {
109109
109109
  while (lines[pos].length < 1) {
109110
109110
  pos++;
109111
109111
  }
109112
+ if (lines[pos].startsWith("METADATA")) {
109113
+ while (lines[pos].length > 1) {
109114
+ pos++;
109115
+ }
109116
+ pos++;
109117
+ }
109112
109118
  items = lines[pos].trim().split(/\s+/);
109113
109119
  pos++;
109114
109120
  if (items[0].includes("LINES")) {
@@ -109287,7 +109293,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
109287
109293
  layer.values = obj;
109288
109294
  }
109289
109295
  }
109290
- } else if (ext === "CRV" || ext === "CURV") {
109296
+ } else if (ext === "CRV" || ext === "CURV" || ext === "THICKNESS" || ext === "AREA") {
109291
109297
  layer.values = _NVMeshLoaders.readCURV(buffer2, n_vert);
109292
109298
  layer.isTransparentBelowCalMin = false;
109293
109299
  } else if (ext === "GII") {
@@ -109312,6 +109318,9 @@ var NVMeshLoaders = class _NVMeshLoaders {
109312
109318
  layer.values = _NVMeshLoaders.readSMP(buffer2, n_vert);
109313
109319
  } else if (ext === "STC") {
109314
109320
  layer.values = _NVMeshLoaders.readSTC(buffer2, n_vert);
109321
+ } else if (_NVMeshLoaders.isCurv(buffer2)) {
109322
+ layer.values = _NVMeshLoaders.readCURV(buffer2, n_vert);
109323
+ layer.isTransparentBelowCalMin = false;
109315
109324
  } else {
109316
109325
  log.warn("Unknown layer overlay format " + name);
109317
109326
  return layer;
@@ -109485,6 +109494,17 @@ var NVMeshLoaders = class _NVMeshLoaders {
109485
109494
  return f32;
109486
109495
  }
109487
109496
  // readSTC()
109497
+ static isCurv(buffer2) {
109498
+ const view = new DataView(buffer2);
109499
+ const sig0 = view.getUint8(0);
109500
+ const sig1 = view.getUint8(1);
109501
+ const sig2 = view.getUint8(2);
109502
+ if (sig0 !== 255 || sig1 !== 255 || sig2 !== 255) {
109503
+ utiltiesLogger.debug("Unable to recognize file type: does not appear to be FreeSurfer format.");
109504
+ return false;
109505
+ }
109506
+ return true;
109507
+ }
109488
109508
  // read freesurfer curv big-endian format
109489
109509
  // https://github.com/bonilhamusclab/MRIcroS/blob/master/%2BfileUtils/%2Bpial/readPial.m
109490
109510
  // http://www.grahamwideman.com/gw/brain/fs/surfacefileformats.htm
@@ -116936,7 +116956,7 @@ var NVImage = class _NVImage {
116936
116956
  hdr.datatypeCode = 64 /* DT_FLOAT64 */;
116937
116957
  break;
116938
116958
  default:
116939
- throw new Error("Unsupported NRRD data type: " + items[0]);
116959
+ throw new Error("Unsupported MHA data type: " + items[0]);
116940
116960
  }
116941
116961
  }
116942
116962
  if (line.startsWith("ObjectType") && !items[0].includes("Image")) {
@@ -116958,6 +116978,9 @@ var NVImage = class _NVImage {
116958
116978
  [mat33[2], mat33[5], mat33[8], offset[2]],
116959
116979
  [0, 0, 0, 1]
116960
116980
  ];
116981
+ while (bytes[pos] === 10) {
116982
+ pos++;
116983
+ }
116961
116984
  hdr.vox_offset = pos;
116962
116985
  if (isDetached && pairedImgData) {
116963
116986
  if (isGz) {
@@ -119165,6 +119188,7 @@ var DEFAULT_OPTIONS = {
119165
119188
  multiplanarPadPixels: 0,
119166
119189
  // @deprecated
119167
119190
  multiplanarForceRender: false,
119191
+ multiplanarEqualSize: false,
119168
119192
  multiplanarShowRender: 2 /* AUTO */,
119169
119193
  // auto is the same behaviour as multiplanarForceRender: false
119170
119194
  isRadiologicalConvention: false,
@@ -119205,6 +119229,7 @@ var DEFAULT_OPTIONS = {
119205
119229
  clickToSegmentBright: true
119206
119230
  };
119207
119231
  var INITIAL_SCENE_DATA = {
119232
+ gamma: 1,
119208
119233
  azimuth: 110,
119209
119234
  elevation: 10,
119210
119235
  crosshairPos: vec3_exports.create(),
@@ -119443,6 +119468,9 @@ var NVDocument = class _NVDocument {
119443
119468
  data.opts.meshThicknessOn2D = "infinity";
119444
119469
  }
119445
119470
  data.labels = [...this.data.labels];
119471
+ for (const label of data.labels) {
119472
+ delete label.onClick;
119473
+ }
119446
119474
  data.customData = this.customData;
119447
119475
  if (this.volumes.length) {
119448
119476
  let imageOptions = this.imageOptionsArray[0];
@@ -119698,6 +119726,19 @@ var LabelLineTerminator = /* @__PURE__ */ ((LabelLineTerminator2) => {
119698
119726
  LabelLineTerminator2["RING"] = "ring";
119699
119727
  return LabelLineTerminator2;
119700
119728
  })(LabelLineTerminator || {});
119729
+ var LabelAnchorPoint = /* @__PURE__ */ ((LabelAnchorPoint2) => {
119730
+ LabelAnchorPoint2[LabelAnchorPoint2["NONE"] = 0 /* NONE */] = "NONE";
119731
+ LabelAnchorPoint2[LabelAnchorPoint2["TOPLEFT"] = 9] = "TOPLEFT";
119732
+ LabelAnchorPoint2[LabelAnchorPoint2["TOPCENTER"] = 10] = "TOPCENTER";
119733
+ LabelAnchorPoint2[LabelAnchorPoint2["TOPRIGHT"] = 12] = "TOPRIGHT";
119734
+ LabelAnchorPoint2[LabelAnchorPoint2["MIDDLELEFT"] = 17] = "MIDDLELEFT";
119735
+ LabelAnchorPoint2[LabelAnchorPoint2["MIDDLECENTER"] = 18] = "MIDDLECENTER";
119736
+ LabelAnchorPoint2[LabelAnchorPoint2["MIDDLERIGHT"] = 20] = "MIDDLERIGHT";
119737
+ LabelAnchorPoint2[LabelAnchorPoint2["BOTTOMLEFT"] = 33] = "BOTTOMLEFT";
119738
+ LabelAnchorPoint2[LabelAnchorPoint2["BOTTOMCENTER"] = 34] = "BOTTOMCENTER";
119739
+ LabelAnchorPoint2[LabelAnchorPoint2["BOTTOMRIGHT"] = 36] = "BOTTOMRIGHT";
119740
+ return LabelAnchorPoint2;
119741
+ })(LabelAnchorPoint || {});
119701
119742
  var NVLabel3DStyle = class {
119702
119743
  /**
119703
119744
  * @param textColor - Color of text
@@ -119735,13 +119776,17 @@ var NVLabel3D = class {
119735
119776
  * @param style - The style of the label
119736
119777
  * @param points - An array of points label for label lines
119737
119778
  */
119738
- constructor(text, style, points) {
119779
+ constructor(text, style, points, anchor, onClick) {
119739
119780
  __publicField(this, "text");
119740
119781
  __publicField(this, "style");
119741
119782
  __publicField(this, "points");
119783
+ __publicField(this, "anchor");
119784
+ __publicField(this, "onClick");
119742
119785
  this.text = text;
119743
119786
  this.style = style;
119744
119787
  this.points = points;
119788
+ this.anchor = anchor || 0 /* NONE */;
119789
+ this.onClick = onClick;
119745
119790
  }
119746
119791
  };
119747
119792
 
@@ -120880,6 +120925,7 @@ var Niivue = class _Niivue {
120880
120925
  * @param options - options object to set modifiable Niivue properties
120881
120926
  */
120882
120927
  constructor(options = DEFAULT_OPTIONS) {
120928
+ __publicField(this, "loaders", {});
120883
120929
  __publicField(this, "canvas", null);
120884
120930
  // the reference to the canvas element on the page
120885
120931
  __publicField(this, "_gl", null);
@@ -121489,6 +121535,11 @@ var Niivue = class _Niivue {
121489
121535
  }
121490
121536
  const thisMM = this.frac2mm(this.scene.crosshairPos);
121491
121537
  if (this.otherNV instanceof _Niivue) {
121538
+ const thisGamma = this.scene.gamma;
121539
+ const otherGamma = this.otherNV.scene.gamma;
121540
+ if (thisGamma !== otherGamma) {
121541
+ this.otherNV.setGamma(thisGamma);
121542
+ }
121492
121543
  if (this.syncOpts["2d"]) {
121493
121544
  this.otherNV.scene.crosshairPos = this.otherNV.mm2frac(thisMM);
121494
121545
  this.otherNV.scene.pan2Dxyzmm = vec4_exports.clone(this.scene.pan2Dxyzmm);
@@ -121505,6 +121556,11 @@ var Niivue = class _Niivue {
121505
121556
  if (this.otherNV[i2] === this) {
121506
121557
  continue;
121507
121558
  }
121559
+ const thisGamma = this.scene.gamma;
121560
+ const otherGamma = this.otherNV[i2].scene.gamma;
121561
+ if (thisGamma !== otherGamma) {
121562
+ this.otherNV[i2].setGamma(thisGamma);
121563
+ }
121508
121564
  if (this.syncOpts["2d"]) {
121509
121565
  this.otherNV[i2].scene.crosshairPos = this.otherNV[i2].mm2frac(thisMM);
121510
121566
  this.otherNV[i2].scene.pan2Dxyzmm = vec4_exports.clone(this.scene.pan2Dxyzmm);
@@ -121620,6 +121676,10 @@ var Niivue = class _Niivue {
121620
121676
  const [x, y] = [pos.x * this.uiData.dpr, pos.y * this.uiData.dpr];
121621
121677
  const label = this.getLabelAtPoint([x, y]);
121622
121678
  if (label) {
121679
+ if (label.onClick) {
121680
+ label.onClick(label);
121681
+ return;
121682
+ }
121623
121683
  for (const mesh of this.meshes) {
121624
121684
  if (mesh.type !== "connectome" /* CONNECTOME */) {
121625
121685
  continue;
@@ -121934,6 +121994,15 @@ var Niivue = class _Niivue {
121934
121994
  this.drawScene();
121935
121995
  this.uiData.prevX = this.uiData.currX;
121936
121996
  this.uiData.prevY = this.uiData.currY;
121997
+ } else if (!this.uiData.mousedown && this.opts.clickToSegment) {
121998
+ const pos = this.getNoPaddingNoBorderCanvasRelativeMousePosition(e, this.gl.canvas);
121999
+ if (!pos) {
122000
+ return;
122001
+ }
122002
+ const x = pos.x * this.uiData.dpr;
122003
+ const y = pos.y * this.uiData.dpr;
122004
+ this.mousePos = [x, y];
122005
+ this.drawScene();
121937
122006
  }
121938
122007
  }
121939
122008
  // not included in public docs
@@ -122171,6 +122240,11 @@ var Niivue = class _Niivue {
122171
122240
  if (ext === "GZ") {
122172
122241
  ext = re.exec(fullname.slice(0, -3))[1];
122173
122242
  ext = ext.toUpperCase();
122243
+ } else if (ext === "CBOR") {
122244
+ const endExt = ext;
122245
+ ext = re.exec(fullname.slice(0, -5))[1];
122246
+ ext = ext.toUpperCase();
122247
+ ext = `${ext}.${endExt}`;
122174
122248
  }
122175
122249
  return upperCase ? ext : ext.toLowerCase();
122176
122250
  }
@@ -122191,6 +122265,28 @@ var Niivue = class _Niivue {
122191
122265
  }
122192
122266
  async addVolumesFromUrl(imageOptionsArray) {
122193
122267
  const promises = imageOptionsArray.map(async (imageItem) => {
122268
+ const ext = this.getFileExt(imageItem.url);
122269
+ if (this.loaders[ext]) {
122270
+ let itemToLoad = imageItem.url;
122271
+ const toExt = this.loaders[ext].toExt;
122272
+ let name = imageItem.name || imageItem.url;
122273
+ name = name.split("/").pop();
122274
+ if (typeof imageItem.url === "string") {
122275
+ const url = imageItem.url;
122276
+ try {
122277
+ const response = await fetch(url);
122278
+ if (!response.ok) {
122279
+ throw new Error(`Failed to load file: ${response.statusText}`);
122280
+ }
122281
+ itemToLoad = await response.arrayBuffer();
122282
+ } catch (error) {
122283
+ throw new Error(`Failed to load url ${url}: ${error}`);
122284
+ }
122285
+ }
122286
+ const buffer2 = await this.loaders[ext].loader(itemToLoad);
122287
+ imageItem.url = buffer2;
122288
+ imageItem.name = `${name}.${toExt}`;
122289
+ }
122194
122290
  const imageOptions = {
122195
122291
  url: imageItem.url,
122196
122292
  headers: imageItem.headers,
@@ -122330,6 +122426,40 @@ var Niivue = class _Niivue {
122330
122426
  this.addVolume(volume);
122331
122427
  });
122332
122428
  }
122429
+ /* useLoader
122430
+
122431
+ registers an external file loader for niivue to use when reading files.
122432
+
122433
+ the loader must return an array buffer of the file contents for niivue
122434
+ to parse and the extension of the file so niivue can infer the file type to load.
122435
+
122436
+ example:
122437
+
122438
+ const myCustomLoader = (File) => {
122439
+ // ... do parsing here ...
122440
+ return {
122441
+ arrayBuffer: ArrayBuffer,
122442
+ fileExt: 'iwi.cbor',
122443
+ positions: Float32Array | [], // allows for mesh loading to mz3
122444
+ indices: Uint32Array | [], // allows for mesh loading to mz3
122445
+ }
122446
+
122447
+ nv.useLoader({
122448
+ loader: myCustomLoader,
122449
+ fileExt: 'iwi.cbor',
122450
+ toExt: 'nii'
122451
+ })
122452
+
122453
+ */
122454
+ useLoader(loader, fileExt, toExt) {
122455
+ this.loaders = {
122456
+ ...this.loaders,
122457
+ [fileExt.toUpperCase()]: {
122458
+ loader,
122459
+ toExt
122460
+ }
122461
+ };
122462
+ }
122333
122463
  // not included in public docs
122334
122464
  dropListener(e) {
122335
122465
  e.stopPropagation();
@@ -124001,6 +124131,37 @@ var Niivue = class _Niivue {
124001
124131
  this.document.meshes = this.meshes;
124002
124132
  return this.document.download(fileName, compress);
124003
124133
  }
124134
+ // generic loadImages that wraps loadVolumes and loadMeshes
124135
+ async loadImages(images) {
124136
+ const volumes = [];
124137
+ const meshes = [];
124138
+ for (const image of images) {
124139
+ if ("url" in image) {
124140
+ const ext = this.getFileExt(image.url);
124141
+ if (this.loaders[ext]) {
124142
+ const toExt = this.loaders[ext].toExt.toUpperCase();
124143
+ if (MESH_EXTENSIONS.includes(toExt)) {
124144
+ meshes.push(image);
124145
+ } else {
124146
+ volumes.push(image);
124147
+ }
124148
+ continue;
124149
+ }
124150
+ if (MESH_EXTENSIONS.includes(ext.toUpperCase())) {
124151
+ meshes.push(image);
124152
+ } else {
124153
+ volumes.push(image);
124154
+ }
124155
+ }
124156
+ }
124157
+ if (volumes.length > 0) {
124158
+ await this.loadVolumes(volumes);
124159
+ }
124160
+ if (meshes.length > 0) {
124161
+ await this.loadMeshes(meshes);
124162
+ }
124163
+ return this;
124164
+ }
124004
124165
  /**
124005
124166
  * load an array of volume objects
124006
124167
  * @param volumeList - the array of objects to load. each object must have a resolvable "url" property at a minimum
@@ -124020,27 +124181,7 @@ var Niivue = class _Niivue {
124020
124181
  this.volumes = [];
124021
124182
  this.gl.clearColor(0, 0, 0, 1);
124022
124183
  this.gl.clear(this.gl.COLOR_BUFFER_BIT);
124023
- if (volumeList.length > 1) {
124024
- await this.addVolumesFromUrl(volumeList);
124025
- return this;
124026
- }
124027
- const imageOptions = {
124028
- url: volumeList[0].url,
124029
- headers: volumeList[0].headers,
124030
- name: volumeList[0].name,
124031
- colormap: volumeList[0].colormap ? volumeList[0].colormap : volumeList[0].colorMap,
124032
- colormapNegative: volumeList[0].colormapNegative ? volumeList[0].colormapNegative : volumeList[0].colorMapNegative,
124033
- opacity: volumeList[0].opacity,
124034
- urlImgData: volumeList[0].urlImgData,
124035
- cal_min: volumeList[0].cal_min,
124036
- cal_max: volumeList[0].cal_max,
124037
- trustCalMinMax: this.opts.trustCalMinMax,
124038
- isManifest: volumeList[0].isManifest,
124039
- frame4D: volumeList[0].frame4D,
124040
- limitFrames4D: volumeList[0].limitFrames4D || this.opts.limitFrames4D,
124041
- colorbarVisible: volumeList[0].colorbarVisible
124042
- };
124043
- await this.addVolumeFromUrl(imageOptions);
124184
+ await this.addVolumesFromUrl(volumeList);
124044
124185
  return this;
124045
124186
  }
124046
124187
  /**
@@ -124071,6 +124212,28 @@ var Niivue = class _Niivue {
124071
124212
  async addMeshesFromUrl(meshOptions) {
124072
124213
  const promises = meshOptions.map(async (meshItem) => {
124073
124214
  const ext = this.getFileExt(meshItem.url);
124215
+ if (this.loaders[ext]) {
124216
+ let itemToLoad = meshItem.url;
124217
+ const toExt = this.loaders[ext].toExt;
124218
+ let name = meshItem.name || meshItem.url;
124219
+ name = name.split("/").pop();
124220
+ if (typeof meshItem.url === "string") {
124221
+ const url = meshItem.url;
124222
+ try {
124223
+ const response = await fetch(url);
124224
+ if (!response.ok) {
124225
+ throw new Error(`Failed to load file: ${response.statusText}`);
124226
+ }
124227
+ itemToLoad = await response.arrayBuffer();
124228
+ } catch (error) {
124229
+ throw new Error(`Failed to load url ${url}: ${error}`);
124230
+ }
124231
+ }
124232
+ const { positions, indices } = await this.loaders[ext].loader(itemToLoad);
124233
+ meshItem.name = `${name}.${toExt}`;
124234
+ const mz3 = NVMeshUtilities.createMZ3(positions, indices, false);
124235
+ meshItem.buffer = mz3;
124236
+ }
124074
124237
  if (ext === "JCON" || ext === "JSON") {
124075
124238
  const response = await fetch(meshItem.url, {});
124076
124239
  const json = await response.json();
@@ -124111,13 +124274,7 @@ var Niivue = class _Niivue {
124111
124274
  this.meshes = [];
124112
124275
  this.gl.clearColor(0, 0, 0, 1);
124113
124276
  this.gl.clear(this.gl.COLOR_BUFFER_BIT);
124114
- if (meshList.length > 1) {
124115
- await this.addMeshesFromUrl(meshList);
124116
- this.updateGLVolume();
124117
- this.drawScene();
124118
- return this;
124119
- }
124120
- await this.addMeshFromUrl(meshList[0]);
124277
+ await this.addMeshesFromUrl(meshList);
124121
124278
  this.updateGLVolume();
124122
124279
  this.drawScene();
124123
124280
  return this;
@@ -126682,6 +126839,7 @@ var Niivue = class _Niivue {
126682
126839
  * @see {@link https://niivue.github.io/niivue/features/colormaps.html | live demo usage}
126683
126840
  */
126684
126841
  setGamma(gamma = 1) {
126842
+ this.scene.gamma = gamma;
126685
126843
  cmapper.gamma = gamma;
126686
126844
  this.updateGLVolume();
126687
126845
  }
@@ -127045,6 +127203,7 @@ var Niivue = class _Niivue {
127045
127203
  }
127046
127204
  if (this.opts.drawingEnabled) {
127047
127205
  const pt = this.frac2vox(this.scene.crosshairPos);
127206
+ const ptMM = this.frac2mm(this.scene.crosshairPos);
127048
127207
  if (this.opts.clickToSegment) {
127049
127208
  const radius = this.opts.clickToSegmentRadius;
127050
127209
  const steps = this.opts.clickToSegmentSteps;
@@ -127053,11 +127212,12 @@ var Niivue = class _Niivue {
127053
127212
  this.drawPenAxCorSag = axCorSag;
127054
127213
  for (let i3 = 1; i3 <= steps; i3++) {
127055
127214
  const angle3 = i3 / steps * 2 * Math.PI;
127056
- let xVox = pt[0] + radius * Math.cos(angle3);
127057
- let yVox = pt[1] + radius * Math.sin(angle3);
127058
- const zVox = pt[2];
127059
- xVox = Math.round(xVox);
127060
- yVox = Math.round(yVox);
127215
+ const xMM = ptMM[0] + radius * Math.cos(angle3);
127216
+ const yMM = ptMM[1] + radius * Math.sin(angle3);
127217
+ const zMM = ptMM[2];
127218
+ const xVox = this.back.mm2vox([xMM, yMM, zMM])[0];
127219
+ const yVox = this.back.mm2vox([xMM, yMM, zMM])[1];
127220
+ const zVox = this.back.mm2vox([xMM, yMM, zMM])[2];
127061
127221
  this.drawPt(xVox, yVox, zVox, this.opts.penValue);
127062
127222
  this.drawPenFillPts.push([xVox, yVox, pt[2]]);
127063
127223
  if (i3 === steps) {
@@ -127341,9 +127501,21 @@ var Niivue = class _Niivue {
127341
127501
  const labels = [...this.document.labels, ...definedMeshLabels];
127342
127502
  return labels;
127343
127503
  }
127504
+ getConnectomeLabels() {
127505
+ const connectomes = this.meshes.filter((m) => m.type === "connectome" /* CONNECTOME */);
127506
+ const meshNodes = connectomes.flatMap((m) => m.nodes);
127507
+ const meshLabels = meshNodes.map((n) => n.label);
127508
+ const definedMeshLabels = meshLabels.filter((l) => l !== void 0);
127509
+ const nonAnchoredLabels = this.document.labels.filter((l) => l.anchor == null || l.anchor === 0 /* NONE */);
127510
+ const nonAnchoredLabelSet = new Set(definedMeshLabels);
127511
+ for (const label of nonAnchoredLabels) {
127512
+ nonAnchoredLabelSet.add(label);
127513
+ }
127514
+ return Array.from(nonAnchoredLabelSet);
127515
+ }
127344
127516
  getBulletMarginWidth() {
127345
127517
  let bulletMargin = 0;
127346
- const labels = this.getAllLabels();
127518
+ const labels = this.getConnectomeLabels();
127347
127519
  if (labels.length === 0) {
127348
127520
  return 0;
127349
127521
  }
@@ -127360,7 +127532,7 @@ var Niivue = class _Niivue {
127360
127532
  return bulletMargin;
127361
127533
  }
127362
127534
  getLegendPanelWidth() {
127363
- const labels = this.getAllLabels();
127535
+ const labels = this.getConnectomeLabels();
127364
127536
  if (!this.opts.showLegend || labels.length === 0) {
127365
127537
  return 0;
127366
127538
  }
@@ -127386,7 +127558,7 @@ var Niivue = class _Niivue {
127386
127558
  return width;
127387
127559
  }
127388
127560
  getLegendPanelHeight() {
127389
- const labels = this.getAllLabels();
127561
+ const labels = this.getConnectomeLabels();
127390
127562
  let height = 0;
127391
127563
  const scale6 = 1;
127392
127564
  const verticalMargin = this.opts.textHeight * this.gl.canvas.height * scale6;
@@ -127828,7 +128000,7 @@ var Niivue = class _Niivue {
127828
128000
  }
127829
128001
  // not included in public docs
127830
128002
  // show text labels for L/R, A/P, I/S dimensions
127831
- drawSliceOrientationText(leftTopWidthHeight, axCorSag) {
128003
+ drawSliceOrientationText(leftTopWidthHeight, axCorSag, padLeftTop = [NaN, NaN]) {
127832
128004
  this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
127833
128005
  let topText = "S";
127834
128006
  if (axCorSag === 0 /* AXIAL */) {
@@ -127842,8 +128014,32 @@ var Niivue = class _Niivue {
127842
128014
  this.drawTextRightBelow([leftTopWidthHeight[0], leftTopWidthHeight[1]], leftText + topText);
127843
128015
  return;
127844
128016
  }
127845
- this.drawTextBelow([leftTopWidthHeight[0] + leftTopWidthHeight[2] * 0.5, leftTopWidthHeight[1]], topText);
127846
- this.drawTextRight([leftTopWidthHeight[0], leftTopWidthHeight[1] + leftTopWidthHeight[3] * 0.5], leftText);
128017
+ let drawBelow = true;
128018
+ let drawRight = true;
128019
+ if (!isNaN(padLeftTop[0])) {
128020
+ const ht = this.opts.textHeight * this.gl.canvas.height + 2;
128021
+ if (padLeftTop[1] > ht) {
128022
+ this.drawTextBelow(
128023
+ [leftTopWidthHeight[0] + leftTopWidthHeight[2] * 0.5, leftTopWidthHeight[1] + padLeftTop[1] - ht],
128024
+ topText
128025
+ );
128026
+ drawBelow = false;
128027
+ }
128028
+ const wid = this.textWidth(ht, leftText) + 2;
128029
+ if (padLeftTop[0] > wid) {
128030
+ this.drawTextRight(
128031
+ [leftTopWidthHeight[0] + padLeftTop[0] - wid, leftTopWidthHeight[1] + leftTopWidthHeight[3] * 0.5],
128032
+ leftText
128033
+ );
128034
+ drawRight = false;
128035
+ }
128036
+ }
128037
+ if (drawBelow) {
128038
+ this.drawTextBelow([leftTopWidthHeight[0] + leftTopWidthHeight[2] * 0.5, leftTopWidthHeight[1]], topText);
128039
+ }
128040
+ if (drawRight) {
128041
+ this.drawTextRight([leftTopWidthHeight[0], leftTopWidthHeight[1] + leftTopWidthHeight[3] * 0.5], leftText);
128042
+ }
127847
128043
  }
127848
128044
  // not included in public docs
127849
128045
  xyMM2xyzMM(axCorSag, sliceFrac) {
@@ -127881,7 +128077,7 @@ var Niivue = class _Niivue {
127881
128077
  }
127882
128078
  // not included in public docs
127883
128079
  // draw 2D tile
127884
- draw2D(leftTopWidthHeight, axCorSag, customMM = NaN) {
128080
+ draw2DMain(leftTopWidthHeight, axCorSag, customMM = NaN) {
127885
128081
  let frac2mmTexture = this.volumes[0].frac2mm.slice();
127886
128082
  let screen2 = this.screenFieldOfViewExtendedMM(axCorSag);
127887
128083
  let mesh2ortho = mat4_exports.create();
@@ -127910,7 +128106,9 @@ var Niivue = class _Niivue {
127910
128106
  elevation = isRadiolgical ? -90 : 90;
127911
128107
  }
127912
128108
  const gl = this.gl;
128109
+ let isStretchToScreen = false;
127913
128110
  if (leftTopWidthHeight[2] === 0 || leftTopWidthHeight[3] === 0) {
128111
+ isStretchToScreen = true;
127914
128112
  const pixPerMMw = gl.canvas.width / screen2.fovMM[0];
127915
128113
  const pixPerMMh = gl.canvas.height / screen2.fovMM[1];
127916
128114
  const pixPerMMmin = Math.min(pixPerMMw, pixPerMMh);
@@ -128057,9 +128255,32 @@ var Niivue = class _Niivue {
128057
128255
  if (isNaN(customMM)) {
128058
128256
  this.drawCrosshairs3D(false, 0.15, obj.modelViewProjectionMatrix, true, this.opts.isSliceMM);
128059
128257
  }
128060
- this.drawSliceOrientationText(leftTopWidthHeight, axCorSag);
128258
+ if (isStretchToScreen) {
128259
+ this.drawSliceOrientationText(leftTopWidthHeight, axCorSag);
128260
+ }
128061
128261
  this.readyForSync = true;
128062
128262
  }
128263
+ draw2D(leftTopWidthHeight, axCorSag, customMM = NaN, imageWidthHeight = [NaN, NaN]) {
128264
+ const padLeftTop = [NaN, NaN];
128265
+ if (isNaN(imageWidthHeight[0])) {
128266
+ this.draw2DMain(leftTopWidthHeight, axCorSag, customMM);
128267
+ } else {
128268
+ const ltwh = leftTopWidthHeight.slice();
128269
+ padLeftTop[0] = Math.floor(0.5 * (ltwh[2] - imageWidthHeight[0]));
128270
+ padLeftTop[1] = Math.floor(0.5 * (ltwh[3] - imageWidthHeight[1]));
128271
+ ltwh[0] += padLeftTop[0];
128272
+ ltwh[1] += padLeftTop[1];
128273
+ ltwh[2] = imageWidthHeight[0];
128274
+ ltwh[3] = imageWidthHeight[1];
128275
+ this.draw2DMain(ltwh, axCorSag, customMM);
128276
+ }
128277
+ if (customMM === Infinity || customMM === -Infinity || axCorSag === 4 /* RENDER */) {
128278
+ return;
128279
+ }
128280
+ if (leftTopWidthHeight[2] !== 0 && leftTopWidthHeight[3] !== 0) {
128281
+ this.drawSliceOrientationText(leftTopWidthHeight, axCorSag, padLeftTop);
128282
+ }
128283
+ }
128063
128284
  // not included in public docs
128064
128285
  // determine 3D model view projection matrix
128065
128286
  calculateMvpMatrix(_unused, leftTopWidthHeight = [0, 0, 0, 0], azimuth, elevation) {
@@ -128670,7 +128891,7 @@ var Niivue = class _Niivue {
128670
128891
  * @param style - label style
128671
128892
  * @param point - 3D point on the model
128672
128893
  */
128673
- addLabel(text, style, points) {
128894
+ addLabel(text, style, points, anchor, onClick) {
128674
128895
  const defaultStyle = {
128675
128896
  textColor: this.opts.legendTextColor,
128676
128897
  textScale: 1,
@@ -128682,7 +128903,7 @@ var Niivue = class _Niivue {
128682
128903
  bulletColor: this.opts.legendTextColor
128683
128904
  };
128684
128905
  const labelStyle = style ? { ...defaultStyle, ...style } : { ...defaultStyle };
128685
- const label = new NVLabel3D(text, labelStyle, points);
128906
+ const label = new NVLabel3D(text, { ...labelStyle }, points, anchor, onClick);
128686
128907
  this.document.labels.push(label);
128687
128908
  return label;
128688
128909
  }
@@ -128700,6 +128921,60 @@ var Niivue = class _Niivue {
128700
128921
  return screenPoint;
128701
128922
  }
128702
128923
  getLabelAtPoint(screenPoint) {
128924
+ const scale6 = 1;
128925
+ const size = this.opts.textHeight * Math.min(this.gl.canvas.height, this.gl.canvas.width) * scale6;
128926
+ const verticalMargin = this.opts.textHeight * this.gl.canvas.height * scale6;
128927
+ for (const label of this.document.labels) {
128928
+ if (label.anchor == null || label.anchor === 0 /* NONE */) {
128929
+ continue;
128930
+ }
128931
+ const labelSize = this.opts.textHeight * this.gl.canvas.height * label.style.textScale;
128932
+ const textHeight = this.textHeight(labelSize, label.text);
128933
+ const textWidth = this.textWidth(labelSize, label.text);
128934
+ if (label.anchor & 1 /* LEFT */) {
128935
+ if (screenPoint[0] > textWidth) {
128936
+ continue;
128937
+ }
128938
+ }
128939
+ if (label.anchor & 2 /* CENTER */) {
128940
+ if (screenPoint[0] < (this.gl.canvas.width - textWidth) / 2) {
128941
+ continue;
128942
+ }
128943
+ if (screenPoint[0] > (this.gl.canvas.width + textWidth) / 2) {
128944
+ continue;
128945
+ }
128946
+ }
128947
+ if (label.anchor & 4 /* RIGHT */) {
128948
+ if (screenPoint[0] < this.gl.canvas.width - textWidth) {
128949
+ continue;
128950
+ }
128951
+ }
128952
+ if (label.anchor & 8 /* TOP */) {
128953
+ if (screenPoint[1] < verticalMargin / 2) {
128954
+ continue;
128955
+ }
128956
+ if (screenPoint[1] > textHeight + verticalMargin / 2) {
128957
+ continue;
128958
+ }
128959
+ }
128960
+ if (label.anchor & 16 /* MIDDLE */) {
128961
+ if (screenPoint[1] < (this.gl.canvas.height - textHeight - verticalMargin) / 2) {
128962
+ continue;
128963
+ }
128964
+ if (screenPoint[1] > (this.gl.canvas.height + textHeight - verticalMargin / 2) / 2) {
128965
+ continue;
128966
+ }
128967
+ }
128968
+ if (label.anchor & 32 /* BOTTOM */) {
128969
+ if (screenPoint[1] < this.gl.canvas.height - textHeight - verticalMargin) {
128970
+ continue;
128971
+ }
128972
+ if (screenPoint[1] > this.gl.canvas.height - verticalMargin / 2) {
128973
+ continue;
128974
+ }
128975
+ }
128976
+ return label;
128977
+ }
128703
128978
  log.debug("screenPoint", screenPoint);
128704
128979
  const panelHeight = this.getLegendPanelHeight();
128705
128980
  const panelWidth = this.getLegendPanelWidth();
@@ -128709,9 +128984,7 @@ var Niivue = class _Niivue {
128709
128984
  if (screenPoint[0] < left || screenPoint[1] < top || screenPoint[0] > left + panelWidth || screenPoint[1] > top + panelHeight) {
128710
128985
  return null;
128711
128986
  }
128712
- const scale6 = 1;
128713
- const size = this.opts.textHeight * Math.min(this.gl.canvas.height, this.gl.canvas.width) * scale6;
128714
- const labels = this.getAllLabels();
128987
+ const labels = this.getConnectomeLabels();
128715
128988
  for (const label of labels) {
128716
128989
  const labelSize = this.opts.textHeight * this.gl.canvas.height * label.style.textScale;
128717
128990
  const textHeight = this.textHeight(labelSize, label.text);
@@ -128740,7 +129013,7 @@ var Niivue = class _Niivue {
128740
129013
  }
128741
129014
  }
128742
129015
  // not included in public docs
128743
- draw3DLabel(label, pos, mvpMatrix, leftTopWidthHeight, bulletMargin = 0, legendWidth, secondPass = false) {
129016
+ draw3DLabel(label, pos, mvpMatrix, leftTopWidthHeight, bulletMargin, legendWidth, secondPass) {
128744
129017
  const text = label.text;
128745
129018
  const left = pos[0];
128746
129019
  const top = pos[1];
@@ -128757,22 +129030,24 @@ var Niivue = class _Niivue {
128757
129030
  this.drawCircle([rectLeft, rectTop, bulletSize, bulletSize], label.style.bulletColor);
128758
129031
  }
128759
129032
  let textLeft = left;
128760
- if (label.style.textAlignment !== "left" /* LEFT */) {
128761
- const textWidth = this.textWidth(label.style.textScale, label.text) * size;
128762
- if (label.style.textAlignment === "right" /* RIGHT */) {
128763
- textLeft = left + legendWidth - size * 1.5 - textWidth;
129033
+ if (legendWidth) {
129034
+ if (label.style.textAlignment !== "left" /* LEFT */) {
129035
+ const textWidth = this.textWidth(label.style.textScale, label.text) * size;
129036
+ if (label.style.textAlignment === "right" /* RIGHT */) {
129037
+ textLeft = left + legendWidth - size * 1.5 - textWidth;
129038
+ } else {
129039
+ const remaining = legendWidth - (bulletMargin || size);
129040
+ textLeft += (remaining - textWidth) / 2;
129041
+ }
128764
129042
  } else {
128765
- const remaining = legendWidth - (bulletMargin || size);
128766
- textLeft += (remaining - textWidth) / 2;
129043
+ textLeft += bulletMargin;
128767
129044
  }
128768
- } else {
128769
- textLeft += bulletMargin;
128770
129045
  }
128771
129046
  this.drawText([textLeft, top], text, label.style.textScale, label.style.textColor);
128772
129047
  }
128773
129048
  // not included in public docs
128774
129049
  draw3DLabels(mvpMatrix, leftTopWidthHeight, secondPass = false) {
128775
- const labels = this.getAllLabels();
129050
+ const labels = this.getConnectomeLabels();
128776
129051
  if (!this.opts.showLegend || labels.length === 0) {
128777
129052
  return;
128778
129053
  }
@@ -128810,6 +129085,51 @@ var Niivue = class _Niivue {
128810
129085
  }
128811
129086
  }
128812
129087
  }
129088
+ drawAnchoredLabels() {
129089
+ const size = this.opts.textHeight * Math.min(this.gl.canvas.height, this.gl.canvas.width) * 1;
129090
+ const anchoredLabels = this.document.labels.filter((l) => l.anchor != null && l.anchor !== 0 /* NONE */);
129091
+ for (const label of anchoredLabels) {
129092
+ const text = label.text;
129093
+ const textHeight = this.textHeight(label.style.textScale, text) * size;
129094
+ const textWidth = this.textWidth(label.style.textScale, text) * size;
129095
+ let left;
129096
+ let top;
129097
+ const scale6 = 1;
129098
+ const verticalMargin = this.opts.textHeight * this.gl.canvas.height * scale6;
129099
+ const rectHeightDiff = verticalMargin;
129100
+ let rectWidthDiff = verticalMargin / 4;
129101
+ let rectHorizontalOffset = 0;
129102
+ let rectVerticalOffset = 0;
129103
+ if (label.anchor & 1 /* LEFT */) {
129104
+ left = 0;
129105
+ }
129106
+ if (label.anchor & 4 /* RIGHT */) {
129107
+ left = this.canvas.width - textWidth;
129108
+ rectHorizontalOffset -= verticalMargin / 4;
129109
+ }
129110
+ if (label.anchor & 2 /* CENTER */) {
129111
+ left = (this.canvas.width - textWidth) / 2;
129112
+ rectHorizontalOffset -= verticalMargin / 4;
129113
+ rectWidthDiff += verticalMargin / 4;
129114
+ }
129115
+ if (label.anchor & 8 /* TOP */) {
129116
+ top = 0;
129117
+ }
129118
+ if (label.anchor & 16 /* MIDDLE */) {
129119
+ top = (this.canvas.height - textHeight - verticalMargin) / 2;
129120
+ rectVerticalOffset -= verticalMargin / 4;
129121
+ }
129122
+ if (label.anchor & 32 /* BOTTOM */) {
129123
+ top = this.canvas.height - textHeight - verticalMargin;
129124
+ rectVerticalOffset -= verticalMargin / 4;
129125
+ }
129126
+ this.drawRect(
129127
+ [left + rectHorizontalOffset, top + rectVerticalOffset, textWidth + rectWidthDiff, textHeight + rectHeightDiff],
129128
+ label.style.backgroundColor
129129
+ );
129130
+ this.draw3DLabel(label, [left, top]);
129131
+ }
129132
+ }
128813
129133
  // not included in public docs
128814
129134
  draw3D(leftTopWidthHeight = [0, 0, 0, 0], mvpMatrix = null, modelMatrix = null, normalMatrix = null, azimuth = null, elevation = 0) {
128815
129135
  const isMosaic = azimuth !== null;
@@ -129685,6 +130005,12 @@ var Niivue = class _Niivue {
129685
130005
  }
129686
130006
  const isDrawPenDown = isFinite(this.drawPenLocation[0]) && this.opts.drawingEnabled;
129687
130007
  const { volScale } = this.sliceScale();
130008
+ const actualScale = volScale.slice();
130009
+ if (this.opts.multiplanarEqualSize) {
130010
+ volScale[0] = 1;
130011
+ volScale[1] = 1;
130012
+ volScale[2] = 1;
130013
+ }
129688
130014
  if (typeof this.opts.multiplanarPadPixels !== "number") {
129689
130015
  log.debug("multiplanarPadPixels must be numeric");
129690
130016
  }
@@ -129722,38 +130048,43 @@ var Niivue = class _Niivue {
129722
130048
  isDrawGrid = true;
129723
130049
  }
129724
130050
  }
130051
+ let ltwh = ltwh2x2;
129725
130052
  if (isDrawColumn) {
129726
- let ltwh = ltwh1x3;
130053
+ ltwh = ltwh1x3;
129727
130054
  if (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh1x4[4] >= ltwh1x3[4]) {
129728
130055
  ltwh = ltwh1x4;
129729
130056
  } else {
129730
130057
  isDraw3D = false;
129731
130058
  }
129732
- const sX = volScale[0] * ltwh[4];
129733
- const sY = volScale[1] * ltwh[4];
129734
- const sZ = volScale[2] * ltwh[4];
129735
- const sMx = mx * ltwh[4];
129736
- this.draw2D([ltwh[0], ltwh[1], sX, sY], 0);
129737
- this.draw2D([ltwh[0], ltwh[1] + sY + pad, sX, sZ], 1);
129738
- this.draw2D([ltwh[0], ltwh[1] + sY + pad + sZ + pad, sY, sZ], 2);
129739
- if (isDraw3D) {
129740
- this.draw3D([ltwh[0], ltwh[1] + sY + sZ + sZ + pad * 3, sMx, sMx]);
129741
- }
129742
130059
  } else if (isDrawRow) {
129743
- let ltwh = ltwh3x1;
130060
+ ltwh = ltwh3x1;
129744
130061
  if (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh4x1[4] >= ltwh3x1[4]) {
129745
130062
  ltwh = ltwh4x1;
129746
130063
  } else {
129747
130064
  isDraw3D = false;
129748
130065
  }
129749
- const sX = volScale[0] * ltwh[4];
129750
- const sY = volScale[1] * ltwh[4];
129751
- const sZ = volScale[2] * ltwh[4];
129752
- this.draw2D([ltwh[0], ltwh[1], sX, sY], 0);
129753
- this.draw2D([ltwh[0] + sX + pad, ltwh[1], sX, sZ], 1);
129754
- this.draw2D([ltwh[0] + sX + sX + pad * 2, ltwh[1], sY, sZ], 2);
130066
+ }
130067
+ const sX = volScale[0] * ltwh[4];
130068
+ const sY = volScale[1] * ltwh[4];
130069
+ const sZ = volScale[2] * ltwh[4];
130070
+ const actualX = actualScale[0] * ltwh[4];
130071
+ const actualY = actualScale[1] * ltwh[4];
130072
+ const actualZ = actualScale[2] * ltwh[4];
130073
+ if (isDrawColumn) {
130074
+ this.draw2D([ltwh[0], ltwh[1], sX, sY], 0, NaN, [actualX, actualY]);
130075
+ this.draw2D([ltwh[0], ltwh[1] + sY + pad, sX, sZ], 1, NaN, [actualX, actualZ]);
130076
+ this.draw2D([ltwh[0], ltwh[1] + sY + pad + sZ + pad, sY, sZ], 2, NaN, [actualY, actualZ]);
129755
130077
  if (isDraw3D) {
129756
- this.draw3D([ltwh[0] + sX + sX + sY + pad * 3, ltwh[1], ltwh[3], ltwh[3]]);
130078
+ const sMx = mx * ltwh[4];
130079
+ this.draw3D([ltwh[0], ltwh[1] + sY + sZ + sZ + pad * 3, sMx, sMx]);
130080
+ }
130081
+ } else if (isDrawRow) {
130082
+ this.draw2D([ltwh[0], ltwh[1], sX, sY], 0, NaN, [actualX, actualY]);
130083
+ this.draw2D([ltwh[0] + sX + pad, ltwh[1], sX, sZ], 1, NaN, [actualX, actualZ]);
130084
+ this.draw2D([ltwh[0] + sX + sX + pad * 2, ltwh[1], sY, sZ], 2, NaN, [actualY, actualZ]);
130085
+ if (isDraw3D) {
130086
+ const sMx = mx * ltwh[4];
130087
+ this.draw3D([ltwh[0] + sX + sX + sY + pad * 3, ltwh[1], sMx, sMx]);
129757
130088
  }
129758
130089
  } else if (isDrawGrid) {
129759
130090
  if (!isShowRender) {
@@ -129762,13 +130093,9 @@ var Niivue = class _Niivue {
129762
130093
  if (this.opts.multiplanarShowRender === 2 /* AUTO */) {
129763
130094
  isDraw3D = true;
129764
130095
  }
129765
- const ltwh = ltwh2x2;
129766
- const sX = volScale[0] * ltwh[4];
129767
- const sY = volScale[1] * ltwh[4];
129768
- const sZ = volScale[2] * ltwh[4];
129769
- this.draw2D([ltwh[0], ltwh[1] + sZ + pad, sX, sY], 0);
129770
- this.draw2D([ltwh[0], ltwh[1], sX, sZ], 1);
129771
- this.draw2D([ltwh[0] + sX + pad, ltwh[1], sY, sZ], 2);
130096
+ this.draw2D([ltwh[0], ltwh[1] + sZ + pad, sX, sY], 0, NaN, [actualX, actualY]);
130097
+ this.draw2D([ltwh[0], ltwh[1], sX, sZ], 1, NaN, [actualX, actualZ]);
130098
+ this.draw2D([ltwh[0] + sX + pad, ltwh[1], sY, sZ], 2, NaN, [actualY, actualZ]);
129772
130099
  if (isDraw3D) {
129773
130100
  this.draw3D([ltwh[0] + sX + pad, ltwh[1] + sZ + pad, sY, sY]);
129774
130101
  }
@@ -129833,10 +130160,25 @@ var Niivue = class _Niivue {
129833
130160
  height
129834
130161
  ]);
129835
130162
  }
130163
+ if (this.opts.clickToSegment) {
130164
+ const x = this.mousePos[0];
130165
+ const y = this.mousePos[1];
130166
+ const tileIdx = this.tileIndex(x, y);
130167
+ if (tileIdx > -1) {
130168
+ const fovMM = this.screenSlices[tileIdx].fovMM;
130169
+ const ltwh = this.screenSlices[tileIdx].leftTopWidthHeight;
130170
+ const pixPerMM = ltwh[2] / fovMM[0];
130171
+ const color = this.opts.crosshairColor;
130172
+ const segmentCursorColor = [color[0], color[1], color[2], 0.4];
130173
+ const radius = this.opts.clickToSegmentRadius * pixPerMM;
130174
+ this.drawCircle([x - radius, y - radius, radius * 2, radius * 2], segmentCursorColor, 1);
130175
+ }
130176
+ }
129836
130177
  const pos = this.frac2mm([this.scene.crosshairPos[0], this.scene.crosshairPos[1], this.scene.crosshairPos[2]]);
129837
130178
  posString = pos[0].toFixed(2) + "\xD7" + pos[1].toFixed(2) + "\xD7" + pos[2].toFixed(2);
129838
130179
  this.readyForSync = true;
129839
130180
  this.sync();
130181
+ this.drawAnchoredLabels();
129840
130182
  return posString;
129841
130183
  }
129842
130184
  // not included in public docs
@@ -129871,6 +130213,7 @@ export {
129871
130213
  DEFAULT_OPTIONS,
129872
130214
  DRAG_MODE,
129873
130215
  INITIAL_SCENE_DATA,
130216
+ LabelAnchorPoint,
129874
130217
  LabelLineTerminator,
129875
130218
  LabelTextAlignment,
129876
130219
  MULTIPLANAR_TYPE,