@niivue/niivue 0.46.0 → 0.48.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.d.ts +20 -3
- package/build/index.js +311 -79
- package/build/index.js.map +1 -1
- package/build/index.min.js +1 -1
- package/dist/index.d.ts +20 -3
- package/dist/index.js +540 -254
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/niivue.umd.js +24 -24
- package/package.json +2 -2
- package/src/index.html +82 -3
- package/src/niivue/index.ts +210 -45
- package/src/nvconnectome.ts +81 -39
- package/src/nvdocument.ts +20 -1
- package/src/nvimage/index.ts +55 -3
- package/src/nvmesh-loaders.ts +1 -1
- package/LICENSE +0 -25
- package/README.md +0 -72
package/build/index.js
CHANGED
|
@@ -10,7 +10,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
10
10
|
import { mat4 as mat46, vec2, vec3 as vec311, vec4 as vec48 } from "gl-matrix";
|
|
11
11
|
|
|
12
12
|
// package.json
|
|
13
|
-
var version = "0.
|
|
13
|
+
var version = "0.48.0";
|
|
14
14
|
|
|
15
15
|
// src/logger.ts
|
|
16
16
|
var _Log = class _Log {
|
|
@@ -12368,10 +12368,10 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
12368
12368
|
});
|
|
12369
12369
|
}
|
|
12370
12370
|
const dpv32 = [];
|
|
12371
|
-
for (let
|
|
12371
|
+
for (let i2 = 0; i2 < dpv.length; i2++) {
|
|
12372
12372
|
dpv32.push({
|
|
12373
|
-
id: dpv[
|
|
12374
|
-
vals: Float32Array.from(dpv[
|
|
12373
|
+
id: dpv[i2].id,
|
|
12374
|
+
vals: Float32Array.from(dpv[i2].vals)
|
|
12375
12375
|
});
|
|
12376
12376
|
}
|
|
12377
12377
|
offsetPt0[noffset++] = npt;
|
|
@@ -14485,6 +14485,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
14485
14485
|
plen = reader.getInt32(voxoffset += 4, isLittleEndian) - 1;
|
|
14486
14486
|
break;
|
|
14487
14487
|
case TAG_OLD_SURF_GEOM:
|
|
14488
|
+
// these don't take lengths at all
|
|
14488
14489
|
case TAG_OLD_USEREALRAS:
|
|
14489
14490
|
plen = 0;
|
|
14490
14491
|
break;
|
|
@@ -20261,6 +20262,11 @@ var NVImage = class _NVImage {
|
|
|
20261
20262
|
case "comments":
|
|
20262
20263
|
hdr.description = items[0].substring(0, Math.min(79, items[0].length));
|
|
20263
20264
|
break;
|
|
20265
|
+
/* case 'command_history':
|
|
20266
|
+
if (items[0].startsWith('dwi2tensor')) {
|
|
20267
|
+
isTensor = true
|
|
20268
|
+
}
|
|
20269
|
+
break */
|
|
20264
20270
|
case "RepetitionTime":
|
|
20265
20271
|
TR = parseFloat(items[0]);
|
|
20266
20272
|
break;
|
|
@@ -20861,9 +20867,48 @@ var NVImage = class _NVImage {
|
|
|
20861
20867
|
this.img2RASstart = outStart;
|
|
20862
20868
|
this.calculateOblique();
|
|
20863
20869
|
}
|
|
20870
|
+
// Reorient raw header data to RAS
|
|
20871
|
+
// assume single volume, use nVolumes to specify, set nVolumes = 0 for same as input
|
|
20872
|
+
hdr2RAS(nVolumes = 1) {
|
|
20873
|
+
if (!this.permRAS) {
|
|
20874
|
+
throw new Error("permRAS undefined");
|
|
20875
|
+
}
|
|
20876
|
+
if (!this.hdr) {
|
|
20877
|
+
throw new Error("hdr undefined");
|
|
20878
|
+
}
|
|
20879
|
+
const hdrBytes = hdrToArrayBuffer({ ...this.hdr, vox_offset: 352 }, false);
|
|
20880
|
+
const hdr = nifti.readHeader(hdrBytes.buffer, true);
|
|
20881
|
+
if (nVolumes === 1) {
|
|
20882
|
+
hdr.dims[0] = 3;
|
|
20883
|
+
hdr.dims[4] = 1;
|
|
20884
|
+
} else if (nVolumes > 1) {
|
|
20885
|
+
hdr.dims[0] = 4;
|
|
20886
|
+
hdr.dims[4] = nVolumes;
|
|
20887
|
+
}
|
|
20888
|
+
const perm = this.permRAS.slice();
|
|
20889
|
+
if (perm[0] === 1 && perm[1] === 2 && perm[2] === 3) {
|
|
20890
|
+
return hdr;
|
|
20891
|
+
}
|
|
20892
|
+
hdr.qform_code = 0;
|
|
20893
|
+
for (let i = 1; i < 4; i++) {
|
|
20894
|
+
hdr.dims[i] = this.dimsRAS[i];
|
|
20895
|
+
}
|
|
20896
|
+
for (let i = 0; i < this.pixDimsRAS.length; i++) {
|
|
20897
|
+
hdr.pixDims[i] = this.pixDimsRAS[i];
|
|
20898
|
+
}
|
|
20899
|
+
let k = 0;
|
|
20900
|
+
for (let i = 0; i < 4; i++) {
|
|
20901
|
+
for (let j = 0; j < 4; j++) {
|
|
20902
|
+
hdr.affine[i][j] = this.matRAS[k];
|
|
20903
|
+
k++;
|
|
20904
|
+
}
|
|
20905
|
+
}
|
|
20906
|
+
return hdr;
|
|
20907
|
+
}
|
|
20864
20908
|
// Reorient raw image data to RAS
|
|
20865
20909
|
// note that GPU-based orient shader is much faster
|
|
20866
|
-
|
|
20910
|
+
// returns single 3D volume even for 4D input. Use nVolume to select volume (0 indexed)
|
|
20911
|
+
img2RAS(nVolume = 0) {
|
|
20867
20912
|
if (!this.permRAS) {
|
|
20868
20913
|
throw new Error("permRAS undefined");
|
|
20869
20914
|
}
|
|
@@ -20878,7 +20923,13 @@ var NVImage = class _NVImage {
|
|
|
20878
20923
|
return this.img;
|
|
20879
20924
|
}
|
|
20880
20925
|
const hdr = this.hdr;
|
|
20881
|
-
const
|
|
20926
|
+
const nVox = hdr.dims[1] * hdr.dims[2] * hdr.dims[3];
|
|
20927
|
+
let volSkip = nVolume * nVox;
|
|
20928
|
+
if (volSkip + nVox > this.img.length || volSkip < 0) {
|
|
20929
|
+
volSkip = 0;
|
|
20930
|
+
log.warn(`img2RAS nVolume (${nVolume}) out of bounds (${nVolume}+1)\xD7${nVox} > ${this.img.length}`);
|
|
20931
|
+
}
|
|
20932
|
+
const imgRAS = this.img.slice(0, nVox);
|
|
20882
20933
|
const aperm = [Math.abs(perm[0]), Math.abs(perm[1]), Math.abs(perm[2])];
|
|
20883
20934
|
const outdim = [hdr.dims[aperm[0]], hdr.dims[aperm[1]], hdr.dims[aperm[2]]];
|
|
20884
20935
|
const inStep = [1, hdr.dims[1], hdr.dims[1] * hdr.dims[2]];
|
|
@@ -20897,7 +20948,7 @@ var NVImage = class _NVImage {
|
|
|
20897
20948
|
const yi = outStart[1] + y * outStep[1];
|
|
20898
20949
|
for (let x = 0; x < outdim[0]; x++) {
|
|
20899
20950
|
const xi = outStart[0] + x * outStep[0];
|
|
20900
|
-
imgRAS[j] = this.img[xi + yi + zi];
|
|
20951
|
+
imgRAS[j] = this.img[xi + yi + zi + volSkip];
|
|
20901
20952
|
j++;
|
|
20902
20953
|
}
|
|
20903
20954
|
}
|
|
@@ -22309,6 +22360,7 @@ var DEFAULT_OPTIONS = {
|
|
|
22309
22360
|
textHeight: 0.06,
|
|
22310
22361
|
colorbarHeight: 0.05,
|
|
22311
22362
|
crosshairWidth: 1,
|
|
22363
|
+
crosshairWidthUnit: "voxels",
|
|
22312
22364
|
crosshairGap: 0,
|
|
22313
22365
|
rulerWidth: 4,
|
|
22314
22366
|
show3Dcrosshair: false,
|
|
@@ -22334,6 +22386,7 @@ var DEFAULT_OPTIONS = {
|
|
|
22334
22386
|
isRuler: false,
|
|
22335
22387
|
isColorbar: false,
|
|
22336
22388
|
isOrientCube: false,
|
|
22389
|
+
tileMargin: 0,
|
|
22337
22390
|
multiplanarPadPixels: 0,
|
|
22338
22391
|
// @deprecated
|
|
22339
22392
|
multiplanarForceRender: false,
|
|
@@ -22346,6 +22399,8 @@ var DEFAULT_OPTIONS = {
|
|
|
22346
22399
|
yoke3Dto2DZoom: false,
|
|
22347
22400
|
isDepthPickMesh: false,
|
|
22348
22401
|
isCornerOrientationText: false,
|
|
22402
|
+
heroImageFraction: 0,
|
|
22403
|
+
heroSliceType: 4 /* RENDER */,
|
|
22349
22404
|
sagittalNoseLeft: false,
|
|
22350
22405
|
isSliceMM: false,
|
|
22351
22406
|
isV1SliceShader: false,
|
|
@@ -22394,8 +22449,17 @@ var DEFAULT_OPTIONS = {
|
|
|
22394
22449
|
clickToSegmentIs2D: false,
|
|
22395
22450
|
selectionBoxLineThickness: 4,
|
|
22396
22451
|
selectionBoxIsOutline: false,
|
|
22397
|
-
scrollRequiresFocus: false
|
|
22452
|
+
scrollRequiresFocus: false,
|
|
22398
22453
|
// determines if the cavas need to be focused to scroll
|
|
22454
|
+
showMeasureUnits: true,
|
|
22455
|
+
// e.g. 20.2 vs 20.2 mm
|
|
22456
|
+
measureTextJustify: "center",
|
|
22457
|
+
// start, center, end
|
|
22458
|
+
measureTextColor: [1, 0, 0, 1],
|
|
22459
|
+
// red
|
|
22460
|
+
measureLineColor: [1, 0, 0, 1],
|
|
22461
|
+
// red
|
|
22462
|
+
measureTextHeight: 0.03
|
|
22399
22463
|
};
|
|
22400
22464
|
var INITIAL_SCENE_DATA = {
|
|
22401
22465
|
gamma: 1,
|
|
@@ -23053,8 +23117,27 @@ var NVConnectome = class _NVConnectome extends NVMesh2 {
|
|
|
23053
23117
|
const nodes = this.nodes;
|
|
23054
23118
|
if (nodes && nodes.length > 0) {
|
|
23055
23119
|
const largest = nodes.reduce((a, b) => a.sizeValue > b.sizeValue ? a : b).sizeValue;
|
|
23056
|
-
|
|
23057
|
-
|
|
23120
|
+
let min, max;
|
|
23121
|
+
if (typeof this.nodeMinColor !== "undefined" && isFinite(this.nodeMinColor)) {
|
|
23122
|
+
min = this.nodeMinColor;
|
|
23123
|
+
} else {
|
|
23124
|
+
min = nodes[0].colorValue;
|
|
23125
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
23126
|
+
if (nodes[i].colorValue < min) {
|
|
23127
|
+
min = nodes[i].colorValue;
|
|
23128
|
+
}
|
|
23129
|
+
}
|
|
23130
|
+
}
|
|
23131
|
+
if (typeof this.nodeMaxColor !== "undefined" && isFinite(this.nodeMaxColor)) {
|
|
23132
|
+
max = this.nodeMaxColor;
|
|
23133
|
+
} else {
|
|
23134
|
+
max = nodes[0].colorValue;
|
|
23135
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
23136
|
+
if (nodes[i].colorValue > max) {
|
|
23137
|
+
max = nodes[i].colorValue;
|
|
23138
|
+
}
|
|
23139
|
+
}
|
|
23140
|
+
}
|
|
23058
23141
|
const lut = cmapper.colormap(this.nodeColormap, this.colormapInvert);
|
|
23059
23142
|
const lutNeg = cmapper.colormap(this.nodeColormapNegative, this.colormapInvert);
|
|
23060
23143
|
const hasNeg = "nodeColormapNegative" in this;
|
|
@@ -23185,14 +23268,29 @@ var NVConnectome = class _NVConnectome extends NVMesh2 {
|
|
|
23185
23268
|
let lut = cmapper.colormap(this.nodeColormap, this.colormapInvert);
|
|
23186
23269
|
let lutNeg = cmapper.colormap(this.nodeColormapNegative, this.colormapInvert);
|
|
23187
23270
|
let hasNeg = "nodeColormapNegative" in this;
|
|
23188
|
-
if (this.nodeMinColor === void 0
|
|
23189
|
-
|
|
23271
|
+
if (this.nodeMinColor === void 0) {
|
|
23272
|
+
this.nodeMinColor = NaN;
|
|
23273
|
+
}
|
|
23274
|
+
if (this.nodeMaxColor === void 0) {
|
|
23275
|
+
this.nodeMaxColor = NaN;
|
|
23190
23276
|
}
|
|
23191
|
-
if (this.edgeMin === void 0
|
|
23192
|
-
|
|
23277
|
+
if (this.edgeMin === void 0) {
|
|
23278
|
+
this.edgeMin = NaN;
|
|
23279
|
+
}
|
|
23280
|
+
if (this.edgeMax === void 0) {
|
|
23281
|
+
this.edgeMax = NaN;
|
|
23193
23282
|
}
|
|
23194
23283
|
let min = this.nodeMinColor;
|
|
23195
23284
|
let max = this.nodeMaxColor;
|
|
23285
|
+
if (!isFinite(min) || !isFinite(min)) {
|
|
23286
|
+
const nodes2 = this.nodes;
|
|
23287
|
+
min = nodes2[0].colorValue;
|
|
23288
|
+
max = nodes2[0].colorValue;
|
|
23289
|
+
for (let i = 0; i < nodes2.length; i++) {
|
|
23290
|
+
min = Math.min(min, nodes2[i].colorValue);
|
|
23291
|
+
max = Math.max(max, nodes2[i].colorValue);
|
|
23292
|
+
}
|
|
23293
|
+
}
|
|
23196
23294
|
const nodes = this.nodes;
|
|
23197
23295
|
const nNode = nodes.length;
|
|
23198
23296
|
for (let i = 0; i < nNode; i++) {
|
|
@@ -23225,35 +23323,45 @@ var NVConnectome = class _NVConnectome extends NVMesh2 {
|
|
|
23225
23323
|
lut = cmapper.colormap(this.edgeColormap, this.colormapInvert);
|
|
23226
23324
|
lutNeg = cmapper.colormap(this.edgeColormapNegative, this.colormapInvert);
|
|
23227
23325
|
hasNeg = "edgeColormapNegative" in this;
|
|
23228
|
-
min = this.edgeMin;
|
|
23229
|
-
max = this.edgeMax;
|
|
23230
23326
|
const edges = this.edges;
|
|
23231
|
-
|
|
23232
|
-
|
|
23233
|
-
|
|
23234
|
-
if (
|
|
23235
|
-
|
|
23236
|
-
|
|
23237
|
-
|
|
23238
|
-
|
|
23239
|
-
|
|
23240
|
-
|
|
23241
|
-
|
|
23242
|
-
|
|
23327
|
+
if (edges !== void 0 && edges.length > 0) {
|
|
23328
|
+
min = this.edgeMin;
|
|
23329
|
+
max = this.edgeMax;
|
|
23330
|
+
if (!isFinite(min) || !isFinite(min)) {
|
|
23331
|
+
min = edges[0].colorValue;
|
|
23332
|
+
max = edges[0].colorValue;
|
|
23333
|
+
for (let i = 0; i < edges.length; i++) {
|
|
23334
|
+
min = Math.min(min, edges[i].colorValue);
|
|
23335
|
+
max = Math.max(max, edges[i].colorValue);
|
|
23336
|
+
}
|
|
23337
|
+
}
|
|
23338
|
+
for (const edge of edges) {
|
|
23339
|
+
let color = edge.colorValue;
|
|
23340
|
+
const isNeg = hasNeg && color < 0;
|
|
23341
|
+
if (isNeg) {
|
|
23342
|
+
color = -color;
|
|
23343
|
+
}
|
|
23344
|
+
const radius = color * this.edgeScale;
|
|
23345
|
+
if (radius <= 0) {
|
|
23243
23346
|
continue;
|
|
23244
23347
|
}
|
|
23245
|
-
|
|
23246
|
-
|
|
23247
|
-
|
|
23248
|
-
|
|
23249
|
-
|
|
23250
|
-
|
|
23251
|
-
|
|
23252
|
-
|
|
23348
|
+
if (min < max) {
|
|
23349
|
+
if (color < min) {
|
|
23350
|
+
continue;
|
|
23351
|
+
}
|
|
23352
|
+
color = (color - min) / (max - min);
|
|
23353
|
+
} else {
|
|
23354
|
+
color = 1;
|
|
23355
|
+
}
|
|
23356
|
+
color = Math.round(Math.max(Math.min(255, color * 255))) * 4;
|
|
23357
|
+
let rgba = [lut[color], lut[color + 1], lut[color + 2], 255];
|
|
23358
|
+
if (isNeg) {
|
|
23359
|
+
rgba = [lutNeg[color], lutNeg[color + 1], lutNeg[color + 2], 255];
|
|
23360
|
+
}
|
|
23361
|
+
const pti = vec39.fromValues(nodes[edge.first].x, nodes[edge.first].y, nodes[edge.first].z);
|
|
23362
|
+
const ptj = vec39.fromValues(nodes[edge.second].x, nodes[edge.second].y, nodes[edge.second].z);
|
|
23363
|
+
NiivueObject3D.makeColoredCylinder(pts, tris, rgba255, pti, ptj, radius, rgba);
|
|
23253
23364
|
}
|
|
23254
|
-
const pti = vec39.fromValues(nodes[edge.first].x, nodes[edge.first].y, nodes[edge.first].z);
|
|
23255
|
-
const ptj = vec39.fromValues(nodes[edge.second].x, nodes[edge.second].y, nodes[edge.second].z);
|
|
23256
|
-
NiivueObject3D.makeColoredCylinder(pts, tris, rgba255, pti, ptj, radius, rgba);
|
|
23257
23365
|
}
|
|
23258
23366
|
const pts32 = new Float32Array(pts);
|
|
23259
23367
|
const tris32 = new Uint32Array(tris);
|
|
@@ -25922,6 +26030,16 @@ var Niivue = class {
|
|
|
25922
26030
|
this.opts.isCornerOrientationText = isCornerOrientationText;
|
|
25923
26031
|
this.updateGLVolume();
|
|
25924
26032
|
}
|
|
26033
|
+
/**
|
|
26034
|
+
* determine proportion of screen real estate devoted to rendering in multiplanar view.
|
|
26035
|
+
* @param fraction - proportion of screen devoted to primary (hero) image (0 to disable)
|
|
26036
|
+
* @example niivue.setHeroImage(0.5)
|
|
26037
|
+
* @see {@link https://niivue.github.io/niivue/features/layout.html | live demo usage}
|
|
26038
|
+
*/
|
|
26039
|
+
setHeroImage(fraction) {
|
|
26040
|
+
this.opts.heroImageFraction = fraction;
|
|
26041
|
+
this.drawScene();
|
|
26042
|
+
}
|
|
25925
26043
|
/**
|
|
25926
26044
|
* control whether 2D slices use radiological or neurological convention.
|
|
25927
26045
|
* @param isRadiologicalConvention - new display convention
|
|
@@ -30887,30 +31005,39 @@ var Niivue = class {
|
|
|
30887
31005
|
}
|
|
30888
31006
|
const frac10cm = 100 / fovMM[0];
|
|
30889
31007
|
const pix10cm = frac10cm * ltwh[2];
|
|
30890
|
-
const
|
|
30891
|
-
const
|
|
31008
|
+
const pix1cm = Math.max(Math.round(pix10cm * 0.1), 2);
|
|
31009
|
+
const pixLeft = Math.floor(ltwh[0] + 0.5 * ltwh[2] - 0.5 * pix10cm);
|
|
31010
|
+
const thick = Number(this.opts.rulerWidth);
|
|
31011
|
+
const pixTop = Math.floor(ltwh[1] + ltwh[3] - pix1cm) + 0.5 * thick;
|
|
30892
31012
|
const startXYendXY = [pixLeft, pixTop, pixLeft + pix10cm, pixTop];
|
|
30893
|
-
|
|
31013
|
+
let outlineColor = [0, 0, 0, 1];
|
|
31014
|
+
if (this.opts.rulerColor[0] + this.opts.rulerColor[1] + this.opts.rulerColor[2] < 0.8) {
|
|
31015
|
+
outlineColor = [1, 1, 1, 1];
|
|
31016
|
+
}
|
|
31017
|
+
this.drawRuler10cm(startXYendXY, outlineColor, thick + 1);
|
|
31018
|
+
this.drawRuler10cm(startXYendXY, this.opts.rulerColor, thick);
|
|
30894
31019
|
}
|
|
30895
31020
|
// not included in public docs
|
|
30896
31021
|
// draw 10cm ruler at desired coordinates
|
|
30897
|
-
drawRuler10cm(startXYendXY) {
|
|
31022
|
+
drawRuler10cm(startXYendXY, rulerColor, rulerWidth = 1) {
|
|
30898
31023
|
if (!this.lineShader) {
|
|
30899
31024
|
throw new Error("lineShader undefined");
|
|
30900
31025
|
}
|
|
30901
31026
|
this.gl.bindVertexArray(this.genericVAO);
|
|
30902
31027
|
this.lineShader.use(this.gl);
|
|
30903
|
-
this.gl.uniform4fv(this.lineShader.uniforms.lineColor,
|
|
31028
|
+
this.gl.uniform4fv(this.lineShader.uniforms.lineColor, rulerColor);
|
|
30904
31029
|
this.gl.uniform2fv(this.lineShader.uniforms.canvasWidthHeight, [this.gl.canvas.width, this.gl.canvas.height]);
|
|
30905
|
-
this.gl.uniform1f(this.lineShader.uniforms.thickness,
|
|
31030
|
+
this.gl.uniform1f(this.lineShader.uniforms.thickness, rulerWidth);
|
|
30906
31031
|
this.gl.uniform4fv(this.lineShader.uniforms.startXYendXY, startXYendXY);
|
|
30907
31032
|
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
30908
31033
|
const w1cm = -0.1 * (startXYendXY[0] - startXYendXY[2]);
|
|
30909
|
-
const b = startXYendXY[1];
|
|
30910
|
-
const t = b -
|
|
30911
|
-
const t2 = b -
|
|
31034
|
+
const b = startXYendXY[1] - Math.floor(0.5 * this.opts.rulerWidth);
|
|
31035
|
+
const t = Math.floor(b - 0.35 * w1cm);
|
|
31036
|
+
const t2 = Math.floor(b - 0.7 * w1cm);
|
|
30912
31037
|
for (let i = 0; i < 11; i++) {
|
|
30913
|
-
|
|
31038
|
+
let l = startXYendXY[0] + i * w1cm;
|
|
31039
|
+
l = Math.max(l, startXYendXY[0] + 0.5 * rulerWidth);
|
|
31040
|
+
l = Math.min(l, startXYendXY[2] - 0.5 * rulerWidth);
|
|
30914
31041
|
const xyxy = [l, b, l, t];
|
|
30915
31042
|
if (i % 5 === 0) {
|
|
30916
31043
|
xyxy[3] = t2;
|
|
@@ -30988,6 +31115,24 @@ var Niivue = class {
|
|
|
30988
31115
|
// not included in public docs
|
|
30989
31116
|
// draw line between start/end points and text to report length
|
|
30990
31117
|
drawMeasurementTool(startXYendXY) {
|
|
31118
|
+
function extendTo(x0, y0, x1, y1, distance) {
|
|
31119
|
+
const x = x0 - x1;
|
|
31120
|
+
const y = y0 - y1;
|
|
31121
|
+
if (x === 0 && y === 0) {
|
|
31122
|
+
return {
|
|
31123
|
+
origin: [x1 + distance, y1],
|
|
31124
|
+
terminus: [x1 + distance, y1]
|
|
31125
|
+
};
|
|
31126
|
+
}
|
|
31127
|
+
const c = Math.sqrt(x * x + y * y);
|
|
31128
|
+
const dX = distance * x / c;
|
|
31129
|
+
const dY = distance * y / c;
|
|
31130
|
+
return {
|
|
31131
|
+
origin: [x0 + dX, y0 + dY],
|
|
31132
|
+
// next to start point
|
|
31133
|
+
terminus: [x1 - dX, y1 - dY]
|
|
31134
|
+
};
|
|
31135
|
+
}
|
|
30991
31136
|
const gl = this.gl;
|
|
30992
31137
|
gl.bindVertexArray(this.genericVAO);
|
|
30993
31138
|
gl.depthFunc(gl.ALWAYS);
|
|
@@ -31002,9 +31147,9 @@ var Niivue = class {
|
|
|
31002
31147
|
gl.uniform1f(this.lineShader.uniforms.thickness, this.opts.rulerWidth);
|
|
31003
31148
|
gl.uniform4fv(this.lineShader.uniforms.startXYendXY, startXYendXY);
|
|
31004
31149
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
31005
|
-
const
|
|
31006
|
-
|
|
31007
|
-
gl.uniform4fv(this.lineShader.uniforms.lineColor,
|
|
31150
|
+
const measureLineColor = this.opts.measureLineColor;
|
|
31151
|
+
measureLineColor[3] = 1;
|
|
31152
|
+
gl.uniform4fv(this.lineShader.uniforms.lineColor, measureLineColor);
|
|
31008
31153
|
const w = this.opts.rulerWidth;
|
|
31009
31154
|
gl.uniform1f(this.lineShader.uniforms.thickness, w * 2);
|
|
31010
31155
|
let sXYeXY = [startXYendXY[0], startXYendXY[1] - w, startXYendXY[0], startXYendXY[1] + w];
|
|
@@ -31030,8 +31175,30 @@ var Niivue = class {
|
|
|
31030
31175
|
if (lenMM > 99) {
|
|
31031
31176
|
decimals = 0;
|
|
31032
31177
|
}
|
|
31033
|
-
|
|
31034
|
-
this.
|
|
31178
|
+
let stringMM = lenMM.toFixed(decimals);
|
|
31179
|
+
if (this.opts.showMeasureUnits) {
|
|
31180
|
+
stringMM = `${stringMM} mm`;
|
|
31181
|
+
}
|
|
31182
|
+
let textCoords = startXYendXY;
|
|
31183
|
+
const [x0, y0, x1, y1] = startXYendXY;
|
|
31184
|
+
const { origin, terminus } = extendTo(x0, y0, x1, y1, 30);
|
|
31185
|
+
switch (this.opts.measureTextJustify) {
|
|
31186
|
+
case "start":
|
|
31187
|
+
textCoords = [...origin, ...origin.map((point) => point + 1)];
|
|
31188
|
+
break;
|
|
31189
|
+
case "end":
|
|
31190
|
+
textCoords = textCoords = [...terminus, ...terminus.map((point) => point + 1)];
|
|
31191
|
+
break;
|
|
31192
|
+
default:
|
|
31193
|
+
textCoords = startXYendXY;
|
|
31194
|
+
break;
|
|
31195
|
+
}
|
|
31196
|
+
this.drawTextBetween(
|
|
31197
|
+
textCoords,
|
|
31198
|
+
stringMM,
|
|
31199
|
+
this.opts.measureTextHeight / this.opts.textHeight,
|
|
31200
|
+
this.opts.measureTextColor
|
|
31201
|
+
);
|
|
31035
31202
|
}
|
|
31036
31203
|
gl.bindVertexArray(this.unusedVAO);
|
|
31037
31204
|
}
|
|
@@ -31889,6 +32056,18 @@ var Niivue = class {
|
|
|
31889
32056
|
}
|
|
31890
32057
|
draw2D(leftTopWidthHeight, axCorSag, customMM = NaN, imageWidthHeight = [NaN, NaN]) {
|
|
31891
32058
|
const padLeftTop = [NaN, NaN];
|
|
32059
|
+
if (imageWidthHeight[0] === Infinity) {
|
|
32060
|
+
const volScale = this.sliceScale().volScale;
|
|
32061
|
+
let scale = this.scaleSlice(volScale[0], volScale[1], [0, 0], [leftTopWidthHeight[2], leftTopWidthHeight[3]]);
|
|
32062
|
+
if (axCorSag === 1 /* CORONAL */) {
|
|
32063
|
+
scale = this.scaleSlice(volScale[0], volScale[2], [0, 0], [leftTopWidthHeight[2], leftTopWidthHeight[3]]);
|
|
32064
|
+
}
|
|
32065
|
+
if (axCorSag === 2 /* SAGITTAL */) {
|
|
32066
|
+
scale = this.scaleSlice(volScale[1], volScale[2], [0, 0], [leftTopWidthHeight[2], leftTopWidthHeight[3]]);
|
|
32067
|
+
}
|
|
32068
|
+
imageWidthHeight[0] = scale[2];
|
|
32069
|
+
imageWidthHeight[1] = scale[3];
|
|
32070
|
+
}
|
|
31892
32071
|
if (isNaN(imageWidthHeight[0])) {
|
|
31893
32072
|
this.draw2DMain(leftTopWidthHeight, axCorSag, customMM);
|
|
31894
32073
|
} else {
|
|
@@ -32934,22 +33113,28 @@ var Niivue = class {
|
|
|
32934
33113
|
}
|
|
32935
33114
|
const gl = this.gl;
|
|
32936
33115
|
const mm = this.frac2mm(this.scene.crosshairPos, 0, isSliceMM);
|
|
33116
|
+
let radius = 1;
|
|
33117
|
+
const [mn, mx, range] = this.sceneExtentsMinMax(isSliceMM);
|
|
33118
|
+
if (this.volumes.length > 0) {
|
|
33119
|
+
if (!this.back) {
|
|
33120
|
+
throw new Error("back undefined");
|
|
33121
|
+
}
|
|
33122
|
+
radius = 0.5 * Math.min(Math.min(this.back.pixDims[1], this.back.pixDims[2]), this.back.pixDims[3]);
|
|
33123
|
+
} else if (range[0] < 50 || range[0] > 1e3) {
|
|
33124
|
+
radius = range[0] * 0.02;
|
|
33125
|
+
}
|
|
33126
|
+
radius *= this.opts.crosshairWidth;
|
|
33127
|
+
if (this.opts?.crosshairWidthUnit === "percent") {
|
|
33128
|
+
radius = range[0] * this.opts.crosshairWidth * 0.5 * 0.01;
|
|
33129
|
+
}
|
|
33130
|
+
if (this.opts?.crosshairWidthUnit === "mm") {
|
|
33131
|
+
radius = this.opts.crosshairWidth * 0.5;
|
|
33132
|
+
}
|
|
32937
33133
|
if (this.crosshairs3D === null || this.crosshairs3D.mm[0] !== mm[0] || this.crosshairs3D.mm[1] !== mm[1] || this.crosshairs3D.mm[2] !== mm[2]) {
|
|
32938
33134
|
if (this.crosshairs3D !== null) {
|
|
32939
33135
|
gl.deleteBuffer(this.crosshairs3D.indexBuffer);
|
|
32940
33136
|
gl.deleteBuffer(this.crosshairs3D.vertexBuffer);
|
|
32941
33137
|
}
|
|
32942
|
-
const [mn, mx, range] = this.sceneExtentsMinMax(isSliceMM);
|
|
32943
|
-
let radius = 1;
|
|
32944
|
-
if (this.volumes.length > 0) {
|
|
32945
|
-
if (!this.back) {
|
|
32946
|
-
throw new Error("back undefined");
|
|
32947
|
-
}
|
|
32948
|
-
radius = 0.5 * Math.min(Math.min(this.back.pixDims[1], this.back.pixDims[2]), this.back.pixDims[3]);
|
|
32949
|
-
} else if (range[0] < 50 || range[0] > 1e3) {
|
|
32950
|
-
radius = range[0] * 0.02;
|
|
32951
|
-
}
|
|
32952
|
-
radius *= this.opts.crosshairWidth;
|
|
32953
33138
|
this.crosshairs3D = NiivueObject3D.generateCrosshairs(this.gl, 1, mm, mn, mx, radius, 20, this.opts.crosshairGap);
|
|
32954
33139
|
this.crosshairs3D.mm = mm;
|
|
32955
33140
|
}
|
|
@@ -32968,6 +33153,7 @@ var Niivue = class {
|
|
|
32968
33153
|
);
|
|
32969
33154
|
}
|
|
32970
33155
|
gl.uniformMatrix4fv(crosshairsShader.uniforms.mvpMtx, false, mvpMtx);
|
|
33156
|
+
gl.disable(gl.CULL_FACE);
|
|
32971
33157
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.crosshairs3D.indexBuffer);
|
|
32972
33158
|
gl.enable(gl.DEPTH_TEST);
|
|
32973
33159
|
const color = [...this.opts.crosshairColor];
|
|
@@ -33118,9 +33304,9 @@ var Niivue = class {
|
|
|
33118
33304
|
}
|
|
33119
33305
|
// not included in public docs
|
|
33120
33306
|
// note: we also have a "sliceScale" method, which could be confusing
|
|
33121
|
-
scaleSlice(w, h,
|
|
33122
|
-
const canvasW = this.effectiveCanvasWidth() -
|
|
33123
|
-
const canvasH = this.effectiveCanvasHeight() -
|
|
33307
|
+
scaleSlice(w, h, padPixelsWH = [0, 0], canvasWH = [0, 0]) {
|
|
33308
|
+
const canvasW = canvasWH[0] === 0 ? this.effectiveCanvasWidth() - padPixelsWH[0] : canvasWH[0] - padPixelsWH[0];
|
|
33309
|
+
const canvasH = canvasWH[1] === 0 ? this.effectiveCanvasHeight() - padPixelsWH[1] : canvasWH[1] - padPixelsWH[1];
|
|
33124
33310
|
let scalePix = canvasW / w;
|
|
33125
33311
|
if (h * scalePix > canvasH) {
|
|
33126
33312
|
scalePix = canvasH / h;
|
|
@@ -33569,7 +33755,6 @@ var Niivue = class {
|
|
|
33569
33755
|
if (this.volumes.length === 0 || typeof this.volumes[0].dims === "undefined") {
|
|
33570
33756
|
if (this.meshes.length > 0) {
|
|
33571
33757
|
this.screenSlices = [];
|
|
33572
|
-
this.opts.sliceType = 4 /* RENDER */;
|
|
33573
33758
|
this.draw3D();
|
|
33574
33759
|
if (this.opts.isColorbar) {
|
|
33575
33760
|
this.drawColorbar();
|
|
@@ -33613,6 +33798,8 @@ var Niivue = class {
|
|
|
33613
33798
|
if (this.sliceMosaicString.length > 0) {
|
|
33614
33799
|
this.drawMosaic(this.sliceMosaicString);
|
|
33615
33800
|
} else {
|
|
33801
|
+
const heroImageWH = [0, 0];
|
|
33802
|
+
let isHeroImage = false;
|
|
33616
33803
|
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
|
|
33617
33804
|
this.screenSlices = [];
|
|
33618
33805
|
if (this.opts.sliceType === 0 /* AXIAL */) {
|
|
@@ -33622,6 +33809,9 @@ var Niivue = class {
|
|
|
33622
33809
|
} else if (this.opts.sliceType === 2 /* SAGITTAL */) {
|
|
33623
33810
|
this.draw2D([0, 0, 0, 0], 2);
|
|
33624
33811
|
} else {
|
|
33812
|
+
let padPixelsWH = function(cols, rows) {
|
|
33813
|
+
return [(cols - 1) * pad + cols * innerPad, (rows - 1) * pad + rows * innerPad];
|
|
33814
|
+
};
|
|
33625
33815
|
let isShowRender = false;
|
|
33626
33816
|
if (this.opts.multiplanarForceRender) {
|
|
33627
33817
|
isShowRender = true;
|
|
@@ -33648,20 +33838,42 @@ var Niivue = class {
|
|
|
33648
33838
|
log.debug("multiplanarPadPixels must be numeric");
|
|
33649
33839
|
}
|
|
33650
33840
|
const pad = parseFloat(`${this.opts.multiplanarPadPixels}`);
|
|
33651
|
-
|
|
33841
|
+
let innerPad = this.opts.tileMargin;
|
|
33842
|
+
if (innerPad < 0) {
|
|
33843
|
+
innerPad = 2 * (2 + Math.ceil(Math.max(this.opts.textHeight, 0.01) * Math.min(this.gl.canvas.height, this.gl.canvas.width)));
|
|
33844
|
+
}
|
|
33845
|
+
let canvasWH = [this.effectiveCanvasWidth(), this.effectiveCanvasHeight()];
|
|
33846
|
+
if (this.opts.heroImageFraction > 0 && this.opts.heroImageFraction < 1) {
|
|
33847
|
+
isShowRender = false;
|
|
33848
|
+
isHeroImage = true;
|
|
33849
|
+
if (canvasWH[0] > canvasWH[1] && this.opts.multiplanarLayout !== 3 /* ROW */) {
|
|
33850
|
+
heroImageWH[0] = canvasWH[0] * this.opts.heroImageFraction;
|
|
33851
|
+
} else {
|
|
33852
|
+
heroImageWH[1] = canvasWH[1] * this.opts.heroImageFraction;
|
|
33853
|
+
}
|
|
33854
|
+
canvasWH = [canvasWH[0] - heroImageWH[0], canvasWH[1] - heroImageWH[1]];
|
|
33855
|
+
}
|
|
33856
|
+
const ltwh2x2 = this.scaleSlice(
|
|
33857
|
+
volScale[0] + volScale[1],
|
|
33858
|
+
volScale[1] + volScale[2],
|
|
33859
|
+
padPixelsWH(2, 2),
|
|
33860
|
+
canvasWH
|
|
33861
|
+
);
|
|
33652
33862
|
const mx = Math.max(Math.max(volScale[1], volScale[2]), volScale[0]);
|
|
33653
33863
|
const ltwh3x1 = this.scaleSlice(
|
|
33654
33864
|
volScale[0] + volScale[0] + volScale[1],
|
|
33655
33865
|
Math.max(volScale[1], volScale[2]),
|
|
33656
|
-
|
|
33866
|
+
padPixelsWH(3, 1),
|
|
33867
|
+
canvasWH
|
|
33657
33868
|
);
|
|
33658
33869
|
const ltwh4x1 = this.scaleSlice(
|
|
33659
33870
|
volScale[0] + volScale[0] + volScale[1] + mx,
|
|
33660
33871
|
Math.max(volScale[1], volScale[2]),
|
|
33661
|
-
|
|
33872
|
+
padPixelsWH(4, 1),
|
|
33873
|
+
canvasWH
|
|
33662
33874
|
);
|
|
33663
|
-
const ltwh1x3 = this.scaleSlice(mx, volScale[1] + volScale[2] + volScale[2],
|
|
33664
|
-
const ltwh1x4 = this.scaleSlice(mx, volScale[1] + volScale[2] + volScale[2] + mx,
|
|
33875
|
+
const ltwh1x3 = this.scaleSlice(mx, volScale[1] + volScale[2] + volScale[2], padPixelsWH(1, 3), canvasWH);
|
|
33876
|
+
const ltwh1x4 = this.scaleSlice(mx, volScale[1] + volScale[2] + volScale[2] + mx, padPixelsWH(1, 4), canvasWH);
|
|
33665
33877
|
let isDraw3D = !isDrawPenDown && (maxVols < 2 || !isDrawGraph);
|
|
33666
33878
|
let isDrawColumn = false;
|
|
33667
33879
|
let isDrawGrid = false;
|
|
@@ -33684,22 +33896,39 @@ var Niivue = class {
|
|
|
33684
33896
|
let ltwh = ltwh2x2;
|
|
33685
33897
|
if (isDrawColumn) {
|
|
33686
33898
|
ltwh = ltwh1x3;
|
|
33687
|
-
if (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh1x4[4] >= ltwh1x3[4]) {
|
|
33899
|
+
if (!isHeroImage && (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh1x4[4] >= ltwh1x3[4])) {
|
|
33688
33900
|
ltwh = ltwh1x4;
|
|
33689
33901
|
} else {
|
|
33690
33902
|
isDraw3D = false;
|
|
33691
33903
|
}
|
|
33692
33904
|
} else if (isDrawRow) {
|
|
33693
33905
|
ltwh = ltwh3x1;
|
|
33694
|
-
if (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh4x1[4] >= ltwh3x1[4]) {
|
|
33906
|
+
if (!isHeroImage && (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh4x1[4] >= ltwh3x1[4])) {
|
|
33695
33907
|
ltwh = ltwh4x1;
|
|
33696
33908
|
} else {
|
|
33697
33909
|
isDraw3D = false;
|
|
33698
33910
|
}
|
|
33699
33911
|
}
|
|
33700
|
-
|
|
33701
|
-
|
|
33702
|
-
|
|
33912
|
+
if (isHeroImage) {
|
|
33913
|
+
const heroW = heroImageWH[0] === 0 ? this.effectiveCanvasWidth() : heroImageWH[0];
|
|
33914
|
+
const heroH = heroImageWH[1] === 0 ? this.effectiveCanvasHeight() : heroImageWH[1];
|
|
33915
|
+
if (this.opts?.heroSliceType === 0 /* AXIAL */ || this.opts?.heroSliceType === 1 /* CORONAL */ || this.opts?.heroSliceType === 2 /* SAGITTAL */) {
|
|
33916
|
+
this.draw2D([0, 0, heroW, heroH], this.opts.heroSliceType, NaN, [Infinity, Infinity]);
|
|
33917
|
+
} else {
|
|
33918
|
+
const ltwh2 = ltwh.slice();
|
|
33919
|
+
const canvasW = this.effectiveCanvasWidth();
|
|
33920
|
+
if (heroW === canvasW) {
|
|
33921
|
+
ltwh2[0] = 0;
|
|
33922
|
+
}
|
|
33923
|
+
this.draw3D([ltwh2[0], 0, heroW, heroH]);
|
|
33924
|
+
}
|
|
33925
|
+
ltwh[0] += heroImageWH[0];
|
|
33926
|
+
ltwh[1] += heroImageWH[1];
|
|
33927
|
+
isDraw3D = false;
|
|
33928
|
+
}
|
|
33929
|
+
const sX = volScale[0] * ltwh[4] + innerPad;
|
|
33930
|
+
const sY = volScale[1] * ltwh[4] + innerPad;
|
|
33931
|
+
const sZ = volScale[2] * ltwh[4] + innerPad;
|
|
33703
33932
|
const actualX = actualScale[0] * ltwh[4];
|
|
33704
33933
|
const actualY = actualScale[1] * ltwh[4];
|
|
33705
33934
|
const actualZ = actualScale[2] * ltwh[4];
|
|
@@ -33726,6 +33955,9 @@ var Niivue = class {
|
|
|
33726
33955
|
if (this.opts.multiplanarShowRender === 2 /* AUTO */) {
|
|
33727
33956
|
isDraw3D = true;
|
|
33728
33957
|
}
|
|
33958
|
+
if (isHeroImage) {
|
|
33959
|
+
isDraw3D = false;
|
|
33960
|
+
}
|
|
33729
33961
|
this.draw2D([ltwh[0], ltwh[1] + sZ + pad, sX, sY], 0, NaN, [actualX, actualY]);
|
|
33730
33962
|
this.draw2D([ltwh[0], ltwh[1], sX, sZ], 1, NaN, [actualX, actualZ]);
|
|
33731
33963
|
this.draw2D([ltwh[0] + sX + pad, ltwh[1], sY, sZ], 2, NaN, [actualY, actualZ]);
|