@niivue/niivue 0.46.0 → 0.47.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 +18 -2
- package/build/index.js +263 -76
- package/build/index.js.map +1 -1
- package/build/index.min.js +1 -1
- package/dist/index.d.ts +18 -2
- package/dist/index.js +492 -251
- 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/nvmesh-loaders.ts +1 -1
- package/LICENSE +0 -25
- package/README.md +0 -72
package/build/index.d.ts
CHANGED
|
@@ -614,6 +614,7 @@ type NVConfigOptions = {
|
|
|
614
614
|
textHeight: number;
|
|
615
615
|
colorbarHeight: number;
|
|
616
616
|
crosshairWidth: number;
|
|
617
|
+
crosshairWidthUnit: 'voxels' | 'mm' | 'percent';
|
|
617
618
|
crosshairGap: number;
|
|
618
619
|
rulerWidth: number;
|
|
619
620
|
show3Dcrosshair: boolean;
|
|
@@ -638,6 +639,7 @@ type NVConfigOptions = {
|
|
|
638
639
|
isRuler: boolean;
|
|
639
640
|
isColorbar: boolean;
|
|
640
641
|
isOrientCube: boolean;
|
|
642
|
+
tileMargin: number;
|
|
641
643
|
multiplanarPadPixels: number;
|
|
642
644
|
multiplanarForceRender: boolean;
|
|
643
645
|
multiplanarEqualSize: boolean;
|
|
@@ -648,6 +650,8 @@ type NVConfigOptions = {
|
|
|
648
650
|
yoke3Dto2DZoom: boolean;
|
|
649
651
|
isDepthPickMesh: boolean;
|
|
650
652
|
isCornerOrientationText: boolean;
|
|
653
|
+
heroImageFraction: number;
|
|
654
|
+
heroSliceType: SLICE_TYPE;
|
|
651
655
|
sagittalNoseLeft: boolean;
|
|
652
656
|
isSliceMM: boolean;
|
|
653
657
|
isV1SliceShader: boolean;
|
|
@@ -688,6 +692,11 @@ type NVConfigOptions = {
|
|
|
688
692
|
selectionBoxLineThickness: number;
|
|
689
693
|
selectionBoxIsOutline: boolean;
|
|
690
694
|
scrollRequiresFocus: boolean;
|
|
695
|
+
showMeasureUnits: boolean;
|
|
696
|
+
measureTextJustify: 'start' | 'center' | 'end';
|
|
697
|
+
measureTextColor: number[];
|
|
698
|
+
measureLineColor: number[];
|
|
699
|
+
measureTextHeight: number;
|
|
691
700
|
};
|
|
692
701
|
declare const DEFAULT_OPTIONS: NVConfigOptions;
|
|
693
702
|
type SceneData = {
|
|
@@ -2205,6 +2214,13 @@ declare class Niivue {
|
|
|
2205
2214
|
* @see {@link https://niivue.github.io/niivue/features/worldspace2.html | live demo usage}
|
|
2206
2215
|
*/
|
|
2207
2216
|
setCornerOrientationText(isCornerOrientationText: boolean): void;
|
|
2217
|
+
/**
|
|
2218
|
+
* determine proportion of screen real estate devoted to rendering in multiplanar view.
|
|
2219
|
+
* @param fraction - proportion of screen devoted to primary (hero) image (0 to disable)
|
|
2220
|
+
* @example niivue.setHeroImage(0.5)
|
|
2221
|
+
* @see {@link https://niivue.github.io/niivue/features/layout.html | live demo usage}
|
|
2222
|
+
*/
|
|
2223
|
+
setHeroImage(fraction: number): void;
|
|
2208
2224
|
/**
|
|
2209
2225
|
* control whether 2D slices use radiological or neurological convention.
|
|
2210
2226
|
* @param isRadiologicalConvention - new display convention
|
|
@@ -3028,7 +3044,7 @@ declare class Niivue {
|
|
|
3028
3044
|
sumBitmap(img: Uint8Array): number;
|
|
3029
3045
|
mouseClick(x: number, y: number, posChange?: number, isDelta?: boolean): void;
|
|
3030
3046
|
drawRuler(): void;
|
|
3031
|
-
drawRuler10cm(startXYendXY: number[]): void;
|
|
3047
|
+
drawRuler10cm(startXYendXY: number[], rulerColor: number[], rulerWidth?: number): void;
|
|
3032
3048
|
screenXY2mm(x: number, y: number, forceSlice?: number): vec4;
|
|
3033
3049
|
dragForPanZoom(startXYendXY: number[]): void;
|
|
3034
3050
|
dragForCenterButton(startXYendXY: number[]): void;
|
|
@@ -3119,7 +3135,7 @@ declare class Niivue {
|
|
|
3119
3135
|
frac2mm(frac: vec3, volIdx?: number, isForceSliceMM?: boolean): vec4;
|
|
3120
3136
|
screenXY2TextureFrac(x: number, y: number, i: number, restrict0to1?: boolean): vec3;
|
|
3121
3137
|
canvasPos2frac(canvasPos: number[]): vec3;
|
|
3122
|
-
scaleSlice(w: number, h: number,
|
|
3138
|
+
scaleSlice(w: number, h: number, padPixelsWH?: [number, number], canvasWH?: [number, number]): number[];
|
|
3123
3139
|
drawThumbnail(): void;
|
|
3124
3140
|
drawLine(startXYendXY: number[], thickness?: number, lineColor?: number[]): void;
|
|
3125
3141
|
draw3DLine(startXY: vec2, endXYZ: vec3, thickness?: number, lineColor?: number[]): void;
|
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.47.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;
|
|
@@ -22309,6 +22315,7 @@ var DEFAULT_OPTIONS = {
|
|
|
22309
22315
|
textHeight: 0.06,
|
|
22310
22316
|
colorbarHeight: 0.05,
|
|
22311
22317
|
crosshairWidth: 1,
|
|
22318
|
+
crosshairWidthUnit: "voxels",
|
|
22312
22319
|
crosshairGap: 0,
|
|
22313
22320
|
rulerWidth: 4,
|
|
22314
22321
|
show3Dcrosshair: false,
|
|
@@ -22334,6 +22341,7 @@ var DEFAULT_OPTIONS = {
|
|
|
22334
22341
|
isRuler: false,
|
|
22335
22342
|
isColorbar: false,
|
|
22336
22343
|
isOrientCube: false,
|
|
22344
|
+
tileMargin: 0,
|
|
22337
22345
|
multiplanarPadPixels: 0,
|
|
22338
22346
|
// @deprecated
|
|
22339
22347
|
multiplanarForceRender: false,
|
|
@@ -22346,6 +22354,8 @@ var DEFAULT_OPTIONS = {
|
|
|
22346
22354
|
yoke3Dto2DZoom: false,
|
|
22347
22355
|
isDepthPickMesh: false,
|
|
22348
22356
|
isCornerOrientationText: false,
|
|
22357
|
+
heroImageFraction: 0,
|
|
22358
|
+
heroSliceType: 4 /* RENDER */,
|
|
22349
22359
|
sagittalNoseLeft: false,
|
|
22350
22360
|
isSliceMM: false,
|
|
22351
22361
|
isV1SliceShader: false,
|
|
@@ -22394,8 +22404,17 @@ var DEFAULT_OPTIONS = {
|
|
|
22394
22404
|
clickToSegmentIs2D: false,
|
|
22395
22405
|
selectionBoxLineThickness: 4,
|
|
22396
22406
|
selectionBoxIsOutline: false,
|
|
22397
|
-
scrollRequiresFocus: false
|
|
22407
|
+
scrollRequiresFocus: false,
|
|
22398
22408
|
// determines if the cavas need to be focused to scroll
|
|
22409
|
+
showMeasureUnits: true,
|
|
22410
|
+
// e.g. 20.2 vs 20.2 mm
|
|
22411
|
+
measureTextJustify: "center",
|
|
22412
|
+
// start, center, end
|
|
22413
|
+
measureTextColor: [1, 0, 0, 1],
|
|
22414
|
+
// red
|
|
22415
|
+
measureLineColor: [1, 0, 0, 1],
|
|
22416
|
+
// red
|
|
22417
|
+
measureTextHeight: 0.03
|
|
22399
22418
|
};
|
|
22400
22419
|
var INITIAL_SCENE_DATA = {
|
|
22401
22420
|
gamma: 1,
|
|
@@ -23053,8 +23072,27 @@ var NVConnectome = class _NVConnectome extends NVMesh2 {
|
|
|
23053
23072
|
const nodes = this.nodes;
|
|
23054
23073
|
if (nodes && nodes.length > 0) {
|
|
23055
23074
|
const largest = nodes.reduce((a, b) => a.sizeValue > b.sizeValue ? a : b).sizeValue;
|
|
23056
|
-
|
|
23057
|
-
|
|
23075
|
+
let min, max;
|
|
23076
|
+
if (typeof this.nodeMinColor !== "undefined" && isFinite(this.nodeMinColor)) {
|
|
23077
|
+
min = this.nodeMinColor;
|
|
23078
|
+
} else {
|
|
23079
|
+
min = nodes[0].colorValue;
|
|
23080
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
23081
|
+
if (nodes[i].colorValue < min) {
|
|
23082
|
+
min = nodes[i].colorValue;
|
|
23083
|
+
}
|
|
23084
|
+
}
|
|
23085
|
+
}
|
|
23086
|
+
if (typeof this.nodeMaxColor !== "undefined" && isFinite(this.nodeMaxColor)) {
|
|
23087
|
+
max = this.nodeMaxColor;
|
|
23088
|
+
} else {
|
|
23089
|
+
max = nodes[0].colorValue;
|
|
23090
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
23091
|
+
if (nodes[i].colorValue > max) {
|
|
23092
|
+
max = nodes[i].colorValue;
|
|
23093
|
+
}
|
|
23094
|
+
}
|
|
23095
|
+
}
|
|
23058
23096
|
const lut = cmapper.colormap(this.nodeColormap, this.colormapInvert);
|
|
23059
23097
|
const lutNeg = cmapper.colormap(this.nodeColormapNegative, this.colormapInvert);
|
|
23060
23098
|
const hasNeg = "nodeColormapNegative" in this;
|
|
@@ -23185,14 +23223,29 @@ var NVConnectome = class _NVConnectome extends NVMesh2 {
|
|
|
23185
23223
|
let lut = cmapper.colormap(this.nodeColormap, this.colormapInvert);
|
|
23186
23224
|
let lutNeg = cmapper.colormap(this.nodeColormapNegative, this.colormapInvert);
|
|
23187
23225
|
let hasNeg = "nodeColormapNegative" in this;
|
|
23188
|
-
if (this.nodeMinColor === void 0
|
|
23189
|
-
|
|
23226
|
+
if (this.nodeMinColor === void 0) {
|
|
23227
|
+
this.nodeMinColor = NaN;
|
|
23228
|
+
}
|
|
23229
|
+
if (this.nodeMaxColor === void 0) {
|
|
23230
|
+
this.nodeMaxColor = NaN;
|
|
23231
|
+
}
|
|
23232
|
+
if (this.edgeMin === void 0) {
|
|
23233
|
+
this.edgeMin = NaN;
|
|
23190
23234
|
}
|
|
23191
|
-
if (this.
|
|
23192
|
-
|
|
23235
|
+
if (this.edgeMax === void 0) {
|
|
23236
|
+
this.edgeMax = NaN;
|
|
23193
23237
|
}
|
|
23194
23238
|
let min = this.nodeMinColor;
|
|
23195
23239
|
let max = this.nodeMaxColor;
|
|
23240
|
+
if (!isFinite(min) || !isFinite(min)) {
|
|
23241
|
+
const nodes2 = this.nodes;
|
|
23242
|
+
min = nodes2[0].colorValue;
|
|
23243
|
+
max = nodes2[0].colorValue;
|
|
23244
|
+
for (let i = 0; i < nodes2.length; i++) {
|
|
23245
|
+
min = Math.min(min, nodes2[i].colorValue);
|
|
23246
|
+
max = Math.max(max, nodes2[i].colorValue);
|
|
23247
|
+
}
|
|
23248
|
+
}
|
|
23196
23249
|
const nodes = this.nodes;
|
|
23197
23250
|
const nNode = nodes.length;
|
|
23198
23251
|
for (let i = 0; i < nNode; i++) {
|
|
@@ -23225,35 +23278,45 @@ var NVConnectome = class _NVConnectome extends NVMesh2 {
|
|
|
23225
23278
|
lut = cmapper.colormap(this.edgeColormap, this.colormapInvert);
|
|
23226
23279
|
lutNeg = cmapper.colormap(this.edgeColormapNegative, this.colormapInvert);
|
|
23227
23280
|
hasNeg = "edgeColormapNegative" in this;
|
|
23228
|
-
min = this.edgeMin;
|
|
23229
|
-
max = this.edgeMax;
|
|
23230
23281
|
const edges = this.edges;
|
|
23231
|
-
|
|
23232
|
-
|
|
23233
|
-
|
|
23234
|
-
if (
|
|
23235
|
-
|
|
23236
|
-
|
|
23237
|
-
|
|
23238
|
-
|
|
23239
|
-
|
|
23240
|
-
|
|
23241
|
-
|
|
23242
|
-
|
|
23282
|
+
if (edges !== void 0 && edges.length > 0) {
|
|
23283
|
+
min = this.edgeMin;
|
|
23284
|
+
max = this.edgeMax;
|
|
23285
|
+
if (!isFinite(min) || !isFinite(min)) {
|
|
23286
|
+
min = edges[0].colorValue;
|
|
23287
|
+
max = edges[0].colorValue;
|
|
23288
|
+
for (let i = 0; i < edges.length; i++) {
|
|
23289
|
+
min = Math.min(min, edges[i].colorValue);
|
|
23290
|
+
max = Math.max(max, edges[i].colorValue);
|
|
23291
|
+
}
|
|
23292
|
+
}
|
|
23293
|
+
for (const edge of edges) {
|
|
23294
|
+
let color = edge.colorValue;
|
|
23295
|
+
const isNeg = hasNeg && color < 0;
|
|
23296
|
+
if (isNeg) {
|
|
23297
|
+
color = -color;
|
|
23298
|
+
}
|
|
23299
|
+
const radius = color * this.edgeScale;
|
|
23300
|
+
if (radius <= 0) {
|
|
23243
23301
|
continue;
|
|
23244
23302
|
}
|
|
23245
|
-
|
|
23246
|
-
|
|
23247
|
-
|
|
23248
|
-
|
|
23249
|
-
|
|
23250
|
-
|
|
23251
|
-
|
|
23252
|
-
|
|
23303
|
+
if (min < max) {
|
|
23304
|
+
if (color < min) {
|
|
23305
|
+
continue;
|
|
23306
|
+
}
|
|
23307
|
+
color = (color - min) / (max - min);
|
|
23308
|
+
} else {
|
|
23309
|
+
color = 1;
|
|
23310
|
+
}
|
|
23311
|
+
color = Math.round(Math.max(Math.min(255, color * 255))) * 4;
|
|
23312
|
+
let rgba = [lut[color], lut[color + 1], lut[color + 2], 255];
|
|
23313
|
+
if (isNeg) {
|
|
23314
|
+
rgba = [lutNeg[color], lutNeg[color + 1], lutNeg[color + 2], 255];
|
|
23315
|
+
}
|
|
23316
|
+
const pti = vec39.fromValues(nodes[edge.first].x, nodes[edge.first].y, nodes[edge.first].z);
|
|
23317
|
+
const ptj = vec39.fromValues(nodes[edge.second].x, nodes[edge.second].y, nodes[edge.second].z);
|
|
23318
|
+
NiivueObject3D.makeColoredCylinder(pts, tris, rgba255, pti, ptj, radius, rgba);
|
|
23253
23319
|
}
|
|
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
23320
|
}
|
|
23258
23321
|
const pts32 = new Float32Array(pts);
|
|
23259
23322
|
const tris32 = new Uint32Array(tris);
|
|
@@ -25922,6 +25985,16 @@ var Niivue = class {
|
|
|
25922
25985
|
this.opts.isCornerOrientationText = isCornerOrientationText;
|
|
25923
25986
|
this.updateGLVolume();
|
|
25924
25987
|
}
|
|
25988
|
+
/**
|
|
25989
|
+
* determine proportion of screen real estate devoted to rendering in multiplanar view.
|
|
25990
|
+
* @param fraction - proportion of screen devoted to primary (hero) image (0 to disable)
|
|
25991
|
+
* @example niivue.setHeroImage(0.5)
|
|
25992
|
+
* @see {@link https://niivue.github.io/niivue/features/layout.html | live demo usage}
|
|
25993
|
+
*/
|
|
25994
|
+
setHeroImage(fraction) {
|
|
25995
|
+
this.opts.heroImageFraction = fraction;
|
|
25996
|
+
this.drawScene();
|
|
25997
|
+
}
|
|
25925
25998
|
/**
|
|
25926
25999
|
* control whether 2D slices use radiological or neurological convention.
|
|
25927
26000
|
* @param isRadiologicalConvention - new display convention
|
|
@@ -30887,30 +30960,39 @@ var Niivue = class {
|
|
|
30887
30960
|
}
|
|
30888
30961
|
const frac10cm = 100 / fovMM[0];
|
|
30889
30962
|
const pix10cm = frac10cm * ltwh[2];
|
|
30890
|
-
const
|
|
30891
|
-
const
|
|
30963
|
+
const pix1cm = Math.max(Math.round(pix10cm * 0.1), 2);
|
|
30964
|
+
const pixLeft = Math.floor(ltwh[0] + 0.5 * ltwh[2] - 0.5 * pix10cm);
|
|
30965
|
+
const thick = Number(this.opts.rulerWidth);
|
|
30966
|
+
const pixTop = Math.floor(ltwh[1] + ltwh[3] - pix1cm) + 0.5 * thick;
|
|
30892
30967
|
const startXYendXY = [pixLeft, pixTop, pixLeft + pix10cm, pixTop];
|
|
30893
|
-
|
|
30968
|
+
let outlineColor = [0, 0, 0, 1];
|
|
30969
|
+
if (this.opts.rulerColor[0] + this.opts.rulerColor[1] + this.opts.rulerColor[2] < 0.8) {
|
|
30970
|
+
outlineColor = [1, 1, 1, 1];
|
|
30971
|
+
}
|
|
30972
|
+
this.drawRuler10cm(startXYendXY, outlineColor, thick + 1);
|
|
30973
|
+
this.drawRuler10cm(startXYendXY, this.opts.rulerColor, thick);
|
|
30894
30974
|
}
|
|
30895
30975
|
// not included in public docs
|
|
30896
30976
|
// draw 10cm ruler at desired coordinates
|
|
30897
|
-
drawRuler10cm(startXYendXY) {
|
|
30977
|
+
drawRuler10cm(startXYendXY, rulerColor, rulerWidth = 1) {
|
|
30898
30978
|
if (!this.lineShader) {
|
|
30899
30979
|
throw new Error("lineShader undefined");
|
|
30900
30980
|
}
|
|
30901
30981
|
this.gl.bindVertexArray(this.genericVAO);
|
|
30902
30982
|
this.lineShader.use(this.gl);
|
|
30903
|
-
this.gl.uniform4fv(this.lineShader.uniforms.lineColor,
|
|
30983
|
+
this.gl.uniform4fv(this.lineShader.uniforms.lineColor, rulerColor);
|
|
30904
30984
|
this.gl.uniform2fv(this.lineShader.uniforms.canvasWidthHeight, [this.gl.canvas.width, this.gl.canvas.height]);
|
|
30905
|
-
this.gl.uniform1f(this.lineShader.uniforms.thickness,
|
|
30985
|
+
this.gl.uniform1f(this.lineShader.uniforms.thickness, rulerWidth);
|
|
30906
30986
|
this.gl.uniform4fv(this.lineShader.uniforms.startXYendXY, startXYendXY);
|
|
30907
30987
|
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
30908
30988
|
const w1cm = -0.1 * (startXYendXY[0] - startXYendXY[2]);
|
|
30909
|
-
const b = startXYendXY[1];
|
|
30910
|
-
const t = b -
|
|
30911
|
-
const t2 = b -
|
|
30989
|
+
const b = startXYendXY[1] - Math.floor(0.5 * this.opts.rulerWidth);
|
|
30990
|
+
const t = Math.floor(b - 0.35 * w1cm);
|
|
30991
|
+
const t2 = Math.floor(b - 0.7 * w1cm);
|
|
30912
30992
|
for (let i = 0; i < 11; i++) {
|
|
30913
|
-
|
|
30993
|
+
let l = startXYendXY[0] + i * w1cm;
|
|
30994
|
+
l = Math.max(l, startXYendXY[0] + 0.5 * rulerWidth);
|
|
30995
|
+
l = Math.min(l, startXYendXY[2] - 0.5 * rulerWidth);
|
|
30914
30996
|
const xyxy = [l, b, l, t];
|
|
30915
30997
|
if (i % 5 === 0) {
|
|
30916
30998
|
xyxy[3] = t2;
|
|
@@ -30988,6 +31070,24 @@ var Niivue = class {
|
|
|
30988
31070
|
// not included in public docs
|
|
30989
31071
|
// draw line between start/end points and text to report length
|
|
30990
31072
|
drawMeasurementTool(startXYendXY) {
|
|
31073
|
+
function extendTo(x0, y0, x1, y1, distance) {
|
|
31074
|
+
const x = x0 - x1;
|
|
31075
|
+
const y = y0 - y1;
|
|
31076
|
+
if (x === 0 && y === 0) {
|
|
31077
|
+
return {
|
|
31078
|
+
origin: [x1 + distance, y1],
|
|
31079
|
+
terminus: [x1 + distance, y1]
|
|
31080
|
+
};
|
|
31081
|
+
}
|
|
31082
|
+
const c = Math.sqrt(x * x + y * y);
|
|
31083
|
+
const dX = distance * x / c;
|
|
31084
|
+
const dY = distance * y / c;
|
|
31085
|
+
return {
|
|
31086
|
+
origin: [x0 + dX, y0 + dY],
|
|
31087
|
+
// next to start point
|
|
31088
|
+
terminus: [x1 - dX, y1 - dY]
|
|
31089
|
+
};
|
|
31090
|
+
}
|
|
30991
31091
|
const gl = this.gl;
|
|
30992
31092
|
gl.bindVertexArray(this.genericVAO);
|
|
30993
31093
|
gl.depthFunc(gl.ALWAYS);
|
|
@@ -31002,9 +31102,9 @@ var Niivue = class {
|
|
|
31002
31102
|
gl.uniform1f(this.lineShader.uniforms.thickness, this.opts.rulerWidth);
|
|
31003
31103
|
gl.uniform4fv(this.lineShader.uniforms.startXYendXY, startXYendXY);
|
|
31004
31104
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
31005
|
-
const
|
|
31006
|
-
|
|
31007
|
-
gl.uniform4fv(this.lineShader.uniforms.lineColor,
|
|
31105
|
+
const measureLineColor = this.opts.measureLineColor;
|
|
31106
|
+
measureLineColor[3] = 1;
|
|
31107
|
+
gl.uniform4fv(this.lineShader.uniforms.lineColor, measureLineColor);
|
|
31008
31108
|
const w = this.opts.rulerWidth;
|
|
31009
31109
|
gl.uniform1f(this.lineShader.uniforms.thickness, w * 2);
|
|
31010
31110
|
let sXYeXY = [startXYendXY[0], startXYendXY[1] - w, startXYendXY[0], startXYendXY[1] + w];
|
|
@@ -31030,8 +31130,30 @@ var Niivue = class {
|
|
|
31030
31130
|
if (lenMM > 99) {
|
|
31031
31131
|
decimals = 0;
|
|
31032
31132
|
}
|
|
31033
|
-
|
|
31034
|
-
this.
|
|
31133
|
+
let stringMM = lenMM.toFixed(decimals);
|
|
31134
|
+
if (this.opts.showMeasureUnits) {
|
|
31135
|
+
stringMM = `${stringMM} mm`;
|
|
31136
|
+
}
|
|
31137
|
+
let textCoords = startXYendXY;
|
|
31138
|
+
const [x0, y0, x1, y1] = startXYendXY;
|
|
31139
|
+
const { origin, terminus } = extendTo(x0, y0, x1, y1, 30);
|
|
31140
|
+
switch (this.opts.measureTextJustify) {
|
|
31141
|
+
case "start":
|
|
31142
|
+
textCoords = [...origin, ...origin.map((point) => point + 1)];
|
|
31143
|
+
break;
|
|
31144
|
+
case "end":
|
|
31145
|
+
textCoords = textCoords = [...terminus, ...terminus.map((point) => point + 1)];
|
|
31146
|
+
break;
|
|
31147
|
+
default:
|
|
31148
|
+
textCoords = startXYendXY;
|
|
31149
|
+
break;
|
|
31150
|
+
}
|
|
31151
|
+
this.drawTextBetween(
|
|
31152
|
+
textCoords,
|
|
31153
|
+
stringMM,
|
|
31154
|
+
this.opts.measureTextHeight / this.opts.textHeight,
|
|
31155
|
+
this.opts.measureTextColor
|
|
31156
|
+
);
|
|
31035
31157
|
}
|
|
31036
31158
|
gl.bindVertexArray(this.unusedVAO);
|
|
31037
31159
|
}
|
|
@@ -31889,6 +32011,18 @@ var Niivue = class {
|
|
|
31889
32011
|
}
|
|
31890
32012
|
draw2D(leftTopWidthHeight, axCorSag, customMM = NaN, imageWidthHeight = [NaN, NaN]) {
|
|
31891
32013
|
const padLeftTop = [NaN, NaN];
|
|
32014
|
+
if (imageWidthHeight[0] === Infinity) {
|
|
32015
|
+
const volScale = this.sliceScale().volScale;
|
|
32016
|
+
let scale = this.scaleSlice(volScale[0], volScale[1], [0, 0], [leftTopWidthHeight[2], leftTopWidthHeight[3]]);
|
|
32017
|
+
if (axCorSag === 1 /* CORONAL */) {
|
|
32018
|
+
scale = this.scaleSlice(volScale[0], volScale[2], [0, 0], [leftTopWidthHeight[2], leftTopWidthHeight[3]]);
|
|
32019
|
+
}
|
|
32020
|
+
if (axCorSag === 2 /* SAGITTAL */) {
|
|
32021
|
+
scale = this.scaleSlice(volScale[1], volScale[2], [0, 0], [leftTopWidthHeight[2], leftTopWidthHeight[3]]);
|
|
32022
|
+
}
|
|
32023
|
+
imageWidthHeight[0] = scale[2];
|
|
32024
|
+
imageWidthHeight[1] = scale[3];
|
|
32025
|
+
}
|
|
31892
32026
|
if (isNaN(imageWidthHeight[0])) {
|
|
31893
32027
|
this.draw2DMain(leftTopWidthHeight, axCorSag, customMM);
|
|
31894
32028
|
} else {
|
|
@@ -32934,22 +33068,28 @@ var Niivue = class {
|
|
|
32934
33068
|
}
|
|
32935
33069
|
const gl = this.gl;
|
|
32936
33070
|
const mm = this.frac2mm(this.scene.crosshairPos, 0, isSliceMM);
|
|
33071
|
+
let radius = 1;
|
|
33072
|
+
const [mn, mx, range] = this.sceneExtentsMinMax(isSliceMM);
|
|
33073
|
+
if (this.volumes.length > 0) {
|
|
33074
|
+
if (!this.back) {
|
|
33075
|
+
throw new Error("back undefined");
|
|
33076
|
+
}
|
|
33077
|
+
radius = 0.5 * Math.min(Math.min(this.back.pixDims[1], this.back.pixDims[2]), this.back.pixDims[3]);
|
|
33078
|
+
} else if (range[0] < 50 || range[0] > 1e3) {
|
|
33079
|
+
radius = range[0] * 0.02;
|
|
33080
|
+
}
|
|
33081
|
+
radius *= this.opts.crosshairWidth;
|
|
33082
|
+
if (this.opts?.crosshairWidthUnit === "percent") {
|
|
33083
|
+
radius = range[0] * this.opts.crosshairWidth * 0.5 * 0.01;
|
|
33084
|
+
}
|
|
33085
|
+
if (this.opts?.crosshairWidthUnit === "mm") {
|
|
33086
|
+
radius = this.opts.crosshairWidth * 0.5;
|
|
33087
|
+
}
|
|
32937
33088
|
if (this.crosshairs3D === null || this.crosshairs3D.mm[0] !== mm[0] || this.crosshairs3D.mm[1] !== mm[1] || this.crosshairs3D.mm[2] !== mm[2]) {
|
|
32938
33089
|
if (this.crosshairs3D !== null) {
|
|
32939
33090
|
gl.deleteBuffer(this.crosshairs3D.indexBuffer);
|
|
32940
33091
|
gl.deleteBuffer(this.crosshairs3D.vertexBuffer);
|
|
32941
33092
|
}
|
|
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
33093
|
this.crosshairs3D = NiivueObject3D.generateCrosshairs(this.gl, 1, mm, mn, mx, radius, 20, this.opts.crosshairGap);
|
|
32954
33094
|
this.crosshairs3D.mm = mm;
|
|
32955
33095
|
}
|
|
@@ -32968,6 +33108,7 @@ var Niivue = class {
|
|
|
32968
33108
|
);
|
|
32969
33109
|
}
|
|
32970
33110
|
gl.uniformMatrix4fv(crosshairsShader.uniforms.mvpMtx, false, mvpMtx);
|
|
33111
|
+
gl.disable(gl.CULL_FACE);
|
|
32971
33112
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.crosshairs3D.indexBuffer);
|
|
32972
33113
|
gl.enable(gl.DEPTH_TEST);
|
|
32973
33114
|
const color = [...this.opts.crosshairColor];
|
|
@@ -33118,9 +33259,9 @@ var Niivue = class {
|
|
|
33118
33259
|
}
|
|
33119
33260
|
// not included in public docs
|
|
33120
33261
|
// 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() -
|
|
33262
|
+
scaleSlice(w, h, padPixelsWH = [0, 0], canvasWH = [0, 0]) {
|
|
33263
|
+
const canvasW = canvasWH[0] === 0 ? this.effectiveCanvasWidth() - padPixelsWH[0] : canvasWH[0] - padPixelsWH[0];
|
|
33264
|
+
const canvasH = canvasWH[1] === 0 ? this.effectiveCanvasHeight() - padPixelsWH[1] : canvasWH[1] - padPixelsWH[1];
|
|
33124
33265
|
let scalePix = canvasW / w;
|
|
33125
33266
|
if (h * scalePix > canvasH) {
|
|
33126
33267
|
scalePix = canvasH / h;
|
|
@@ -33569,7 +33710,6 @@ var Niivue = class {
|
|
|
33569
33710
|
if (this.volumes.length === 0 || typeof this.volumes[0].dims === "undefined") {
|
|
33570
33711
|
if (this.meshes.length > 0) {
|
|
33571
33712
|
this.screenSlices = [];
|
|
33572
|
-
this.opts.sliceType = 4 /* RENDER */;
|
|
33573
33713
|
this.draw3D();
|
|
33574
33714
|
if (this.opts.isColorbar) {
|
|
33575
33715
|
this.drawColorbar();
|
|
@@ -33613,6 +33753,8 @@ var Niivue = class {
|
|
|
33613
33753
|
if (this.sliceMosaicString.length > 0) {
|
|
33614
33754
|
this.drawMosaic(this.sliceMosaicString);
|
|
33615
33755
|
} else {
|
|
33756
|
+
const heroImageWH = [0, 0];
|
|
33757
|
+
let isHeroImage = false;
|
|
33616
33758
|
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
|
|
33617
33759
|
this.screenSlices = [];
|
|
33618
33760
|
if (this.opts.sliceType === 0 /* AXIAL */) {
|
|
@@ -33622,6 +33764,9 @@ var Niivue = class {
|
|
|
33622
33764
|
} else if (this.opts.sliceType === 2 /* SAGITTAL */) {
|
|
33623
33765
|
this.draw2D([0, 0, 0, 0], 2);
|
|
33624
33766
|
} else {
|
|
33767
|
+
let padPixelsWH = function(cols, rows) {
|
|
33768
|
+
return [(cols - 1) * pad + cols * innerPad, (rows - 1) * pad + rows * innerPad];
|
|
33769
|
+
};
|
|
33625
33770
|
let isShowRender = false;
|
|
33626
33771
|
if (this.opts.multiplanarForceRender) {
|
|
33627
33772
|
isShowRender = true;
|
|
@@ -33648,20 +33793,42 @@ var Niivue = class {
|
|
|
33648
33793
|
log.debug("multiplanarPadPixels must be numeric");
|
|
33649
33794
|
}
|
|
33650
33795
|
const pad = parseFloat(`${this.opts.multiplanarPadPixels}`);
|
|
33651
|
-
|
|
33796
|
+
let innerPad = this.opts.tileMargin;
|
|
33797
|
+
if (innerPad < 0) {
|
|
33798
|
+
innerPad = 2 * (2 + Math.ceil(Math.max(this.opts.textHeight, 0.01) * Math.min(this.gl.canvas.height, this.gl.canvas.width)));
|
|
33799
|
+
}
|
|
33800
|
+
let canvasWH = [this.effectiveCanvasWidth(), this.effectiveCanvasHeight()];
|
|
33801
|
+
if (this.opts.heroImageFraction > 0 && this.opts.heroImageFraction < 1) {
|
|
33802
|
+
isShowRender = false;
|
|
33803
|
+
isHeroImage = true;
|
|
33804
|
+
if (canvasWH[0] > canvasWH[1] && this.opts.multiplanarLayout !== 3 /* ROW */) {
|
|
33805
|
+
heroImageWH[0] = canvasWH[0] * this.opts.heroImageFraction;
|
|
33806
|
+
} else {
|
|
33807
|
+
heroImageWH[1] = canvasWH[1] * this.opts.heroImageFraction;
|
|
33808
|
+
}
|
|
33809
|
+
canvasWH = [canvasWH[0] - heroImageWH[0], canvasWH[1] - heroImageWH[1]];
|
|
33810
|
+
}
|
|
33811
|
+
const ltwh2x2 = this.scaleSlice(
|
|
33812
|
+
volScale[0] + volScale[1],
|
|
33813
|
+
volScale[1] + volScale[2],
|
|
33814
|
+
padPixelsWH(2, 2),
|
|
33815
|
+
canvasWH
|
|
33816
|
+
);
|
|
33652
33817
|
const mx = Math.max(Math.max(volScale[1], volScale[2]), volScale[0]);
|
|
33653
33818
|
const ltwh3x1 = this.scaleSlice(
|
|
33654
33819
|
volScale[0] + volScale[0] + volScale[1],
|
|
33655
33820
|
Math.max(volScale[1], volScale[2]),
|
|
33656
|
-
|
|
33821
|
+
padPixelsWH(3, 1),
|
|
33822
|
+
canvasWH
|
|
33657
33823
|
);
|
|
33658
33824
|
const ltwh4x1 = this.scaleSlice(
|
|
33659
33825
|
volScale[0] + volScale[0] + volScale[1] + mx,
|
|
33660
33826
|
Math.max(volScale[1], volScale[2]),
|
|
33661
|
-
|
|
33827
|
+
padPixelsWH(4, 1),
|
|
33828
|
+
canvasWH
|
|
33662
33829
|
);
|
|
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,
|
|
33830
|
+
const ltwh1x3 = this.scaleSlice(mx, volScale[1] + volScale[2] + volScale[2], padPixelsWH(1, 3), canvasWH);
|
|
33831
|
+
const ltwh1x4 = this.scaleSlice(mx, volScale[1] + volScale[2] + volScale[2] + mx, padPixelsWH(1, 4), canvasWH);
|
|
33665
33832
|
let isDraw3D = !isDrawPenDown && (maxVols < 2 || !isDrawGraph);
|
|
33666
33833
|
let isDrawColumn = false;
|
|
33667
33834
|
let isDrawGrid = false;
|
|
@@ -33684,22 +33851,39 @@ var Niivue = class {
|
|
|
33684
33851
|
let ltwh = ltwh2x2;
|
|
33685
33852
|
if (isDrawColumn) {
|
|
33686
33853
|
ltwh = ltwh1x3;
|
|
33687
|
-
if (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh1x4[4] >= ltwh1x3[4]) {
|
|
33854
|
+
if (!isHeroImage && (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh1x4[4] >= ltwh1x3[4])) {
|
|
33688
33855
|
ltwh = ltwh1x4;
|
|
33689
33856
|
} else {
|
|
33690
33857
|
isDraw3D = false;
|
|
33691
33858
|
}
|
|
33692
33859
|
} else if (isDrawRow) {
|
|
33693
33860
|
ltwh = ltwh3x1;
|
|
33694
|
-
if (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh4x1[4] >= ltwh3x1[4]) {
|
|
33861
|
+
if (!isHeroImage && (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh4x1[4] >= ltwh3x1[4])) {
|
|
33695
33862
|
ltwh = ltwh4x1;
|
|
33696
33863
|
} else {
|
|
33697
33864
|
isDraw3D = false;
|
|
33698
33865
|
}
|
|
33699
33866
|
}
|
|
33700
|
-
|
|
33701
|
-
|
|
33702
|
-
|
|
33867
|
+
if (isHeroImage) {
|
|
33868
|
+
const heroW = heroImageWH[0] === 0 ? this.effectiveCanvasWidth() : heroImageWH[0];
|
|
33869
|
+
const heroH = heroImageWH[1] === 0 ? this.effectiveCanvasHeight() : heroImageWH[1];
|
|
33870
|
+
if (this.opts?.heroSliceType === 0 /* AXIAL */ || this.opts?.heroSliceType === 1 /* CORONAL */ || this.opts?.heroSliceType === 2 /* SAGITTAL */) {
|
|
33871
|
+
this.draw2D([0, 0, heroW, heroH], this.opts.heroSliceType, NaN, [Infinity, Infinity]);
|
|
33872
|
+
} else {
|
|
33873
|
+
const ltwh2 = ltwh.slice();
|
|
33874
|
+
const canvasW = this.effectiveCanvasWidth();
|
|
33875
|
+
if (heroW === canvasW) {
|
|
33876
|
+
ltwh2[0] = 0;
|
|
33877
|
+
}
|
|
33878
|
+
this.draw3D([ltwh2[0], 0, heroW, heroH]);
|
|
33879
|
+
}
|
|
33880
|
+
ltwh[0] += heroImageWH[0];
|
|
33881
|
+
ltwh[1] += heroImageWH[1];
|
|
33882
|
+
isDraw3D = false;
|
|
33883
|
+
}
|
|
33884
|
+
const sX = volScale[0] * ltwh[4] + innerPad;
|
|
33885
|
+
const sY = volScale[1] * ltwh[4] + innerPad;
|
|
33886
|
+
const sZ = volScale[2] * ltwh[4] + innerPad;
|
|
33703
33887
|
const actualX = actualScale[0] * ltwh[4];
|
|
33704
33888
|
const actualY = actualScale[1] * ltwh[4];
|
|
33705
33889
|
const actualZ = actualScale[2] * ltwh[4];
|
|
@@ -33726,6 +33910,9 @@ var Niivue = class {
|
|
|
33726
33910
|
if (this.opts.multiplanarShowRender === 2 /* AUTO */) {
|
|
33727
33911
|
isDraw3D = true;
|
|
33728
33912
|
}
|
|
33913
|
+
if (isHeroImage) {
|
|
33914
|
+
isDraw3D = false;
|
|
33915
|
+
}
|
|
33729
33916
|
this.draw2D([ltwh[0], ltwh[1] + sZ + pad, sX, sY], 0, NaN, [actualX, actualY]);
|
|
33730
33917
|
this.draw2D([ltwh[0], ltwh[1], sX, sZ], 1, NaN, [actualX, actualZ]);
|
|
33731
33918
|
this.draw2D([ltwh[0] + sX + pad, ltwh[1], sY, sZ], 2, NaN, [actualY, actualZ]);
|