@niivue/niivue 0.59.0 → 0.60.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 +35 -4
- package/build/index.js +240 -19
- package/build/index.js.map +1 -1
- package/build/index.min.js +1 -1
- package/build/metafile-esm.json +1 -1
- package/dist/index.d.ts +35 -4
- package/dist/index.js +242 -18
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/niivue.umd.js +34 -34
- package/package.json +1 -1
- package/src/niivue/index.ts +234 -11
- package/src/nvdocument.ts +4 -2
- package/src/nvimage/ImageReaders/nii.ts +4 -1
- package/src/nvimage/ImageWriter.ts +46 -7
- package/src/nvimage/index.ts +1 -0
- package/src/nvimage/utils.ts +1 -1
package/build/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mat4, vec3, vec4, vec2 } from 'gl-matrix';
|
|
2
|
-
import { NIFTI1, NIFTI2 } from 'nifti-reader-js';
|
|
2
|
+
import { NIFTI1, NIFTI2, NIFTIEXTENSION } from 'nifti-reader-js';
|
|
3
3
|
|
|
4
4
|
type ColorMap = {
|
|
5
5
|
R: number[];
|
|
@@ -341,6 +341,7 @@ declare class NVImage {
|
|
|
341
341
|
extentsMaxOrtho?: number[];
|
|
342
342
|
mm2ortho?: mat4;
|
|
343
343
|
hdr: NIFTI1 | NIFTI2 | null;
|
|
344
|
+
extensions?: NIFTIEXTENSION[];
|
|
344
345
|
imageType?: ImageType;
|
|
345
346
|
img?: TypedVoxelArray;
|
|
346
347
|
imaginary?: Float32Array;
|
|
@@ -597,7 +598,8 @@ declare enum DRAG_MODE {
|
|
|
597
598
|
pan = 3,
|
|
598
599
|
slicer3D = 4,
|
|
599
600
|
callbackOnly = 5,
|
|
600
|
-
roiSelection = 6
|
|
601
|
+
roiSelection = 6,
|
|
602
|
+
angle = 7
|
|
601
603
|
}
|
|
602
604
|
declare enum DRAG_MODE_SECONDARY {
|
|
603
605
|
none = 0,
|
|
@@ -606,7 +608,8 @@ declare enum DRAG_MODE_SECONDARY {
|
|
|
606
608
|
pan = 3,
|
|
607
609
|
slicer3D = 4,
|
|
608
610
|
callbackOnly = 5,
|
|
609
|
-
roiSelection = 6
|
|
611
|
+
roiSelection = 6,
|
|
612
|
+
angle = 7
|
|
610
613
|
}
|
|
611
614
|
declare enum DRAG_MODE_PRIMARY {
|
|
612
615
|
crosshair = 0,
|
|
@@ -1568,6 +1571,8 @@ type UIData = {
|
|
|
1568
1571
|
max3D?: number;
|
|
1569
1572
|
windowX: number;
|
|
1570
1573
|
windowY: number;
|
|
1574
|
+
angleFirstLine: number[];
|
|
1575
|
+
angleState: 'none' | 'drawing_first_line' | 'drawing_second_line' | 'complete';
|
|
1571
1576
|
};
|
|
1572
1577
|
type SaveImageOptions = {
|
|
1573
1578
|
filename: string;
|
|
@@ -1720,6 +1725,7 @@ declare class Niivue {
|
|
|
1720
1725
|
dragModes: {
|
|
1721
1726
|
contrast: DRAG_MODE;
|
|
1722
1727
|
measurement: DRAG_MODE;
|
|
1728
|
+
angle: DRAG_MODE;
|
|
1723
1729
|
none: DRAG_MODE;
|
|
1724
1730
|
pan: DRAG_MODE;
|
|
1725
1731
|
slicer3D: DRAG_MODE;
|
|
@@ -3696,7 +3702,32 @@ declare class Niivue {
|
|
|
3696
3702
|
* Draw a measurement line with end caps and length text on a 2D tile.
|
|
3697
3703
|
* @internal
|
|
3698
3704
|
*/
|
|
3699
|
-
drawMeasurementTool(startXYendXY: number[]): void;
|
|
3705
|
+
drawMeasurementTool(startXYendXY: number[], isDrawText?: boolean): void;
|
|
3706
|
+
/**
|
|
3707
|
+
* Draw angle measurement tool with two lines and angle display.
|
|
3708
|
+
* @internal
|
|
3709
|
+
*/
|
|
3710
|
+
drawAngleMeasurementTool(): void;
|
|
3711
|
+
/**
|
|
3712
|
+
* Calculate and draw angle text at the intersection of two lines.
|
|
3713
|
+
* @internal
|
|
3714
|
+
*/
|
|
3715
|
+
drawAngleText(): void;
|
|
3716
|
+
/**
|
|
3717
|
+
* Calculate angle between two lines in degrees.
|
|
3718
|
+
* @internal
|
|
3719
|
+
*/
|
|
3720
|
+
calculateAngleBetweenLines(line1: number[], line2: number[]): number;
|
|
3721
|
+
/**
|
|
3722
|
+
* Reset the angle measurement state.
|
|
3723
|
+
* @internal
|
|
3724
|
+
*/
|
|
3725
|
+
resetAngleMeasurement(): void;
|
|
3726
|
+
/**
|
|
3727
|
+
* Set the drag mode for mouse interactions.
|
|
3728
|
+
* @param mode - The drag mode to set ('none', 'contrast', 'measurement', 'angle', 'pan', 'slicer3D', 'callbackOnly', 'roiSelection')
|
|
3729
|
+
*/
|
|
3730
|
+
setDragMode(mode: string | DRAG_MODE): void;
|
|
3700
3731
|
/**
|
|
3701
3732
|
* Draw a rectangle or outline at given position with specified color or default crosshair color.
|
|
3702
3733
|
* @internal
|
package/build/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import { mat4 as mat49, vec2, vec3 as vec314, vec4 as vec410 } from "gl-matrix";
|
|
|
19
19
|
// package.json
|
|
20
20
|
var package_default = {
|
|
21
21
|
name: "@niivue/niivue",
|
|
22
|
-
version: "0.
|
|
22
|
+
version: "0.60.0",
|
|
23
23
|
description: "minimal webgl2 nifti image viewer",
|
|
24
24
|
types: "./build/index.d.ts",
|
|
25
25
|
main: "./build/index.js",
|
|
@@ -16245,7 +16245,7 @@ function hdrToArrayBuffer(hdr, isDrawing8 = false, isInputEndian = false) {
|
|
|
16245
16245
|
view.setFloat32(112, 1, isLittleEndian);
|
|
16246
16246
|
view.setFloat32(116, 0, isLittleEndian);
|
|
16247
16247
|
} else {
|
|
16248
|
-
view.setFloat32(108,
|
|
16248
|
+
view.setFloat32(108, hdr.vox_offset, isLittleEndian);
|
|
16249
16249
|
view.setFloat32(112, hdr.scl_slope, isLittleEndian);
|
|
16250
16250
|
view.setFloat32(116, hdr.scl_inter, isLittleEndian);
|
|
16251
16251
|
}
|
|
@@ -16481,7 +16481,32 @@ function toUint8Array(nvImage, drawingBytes = null) {
|
|
|
16481
16481
|
}
|
|
16482
16482
|
const isDrawing = drawingBytes !== null;
|
|
16483
16483
|
const hdrCopy = JSON.parse(JSON.stringify(nvImage.hdr));
|
|
16484
|
-
|
|
16484
|
+
const hasExtensions = nvImage.extensions && nvImage.extensions.length > 0;
|
|
16485
|
+
const extFlag = new Uint8Array(4);
|
|
16486
|
+
extFlag[0] = hasExtensions ? 1 : 0;
|
|
16487
|
+
let extensionsData = new Uint8Array(0);
|
|
16488
|
+
if (hasExtensions) {
|
|
16489
|
+
const blocks = [];
|
|
16490
|
+
let totalSize = 0;
|
|
16491
|
+
for (const ext of nvImage.extensions) {
|
|
16492
|
+
const edataBytes = new Uint8Array(ext.edata);
|
|
16493
|
+
const block = new Uint8Array(8 + edataBytes.length);
|
|
16494
|
+
const dv = new DataView(block.buffer);
|
|
16495
|
+
dv.setInt32(0, ext.esize, true);
|
|
16496
|
+
dv.setInt32(4, ext.ecode, true);
|
|
16497
|
+
block.set(edataBytes, 8);
|
|
16498
|
+
blocks.push(block);
|
|
16499
|
+
totalSize += block.length;
|
|
16500
|
+
}
|
|
16501
|
+
extensionsData = new Uint8Array(totalSize);
|
|
16502
|
+
let offset2 = 0;
|
|
16503
|
+
for (const block of blocks) {
|
|
16504
|
+
extensionsData.set(block, offset2);
|
|
16505
|
+
offset2 += block.length;
|
|
16506
|
+
}
|
|
16507
|
+
}
|
|
16508
|
+
const headerSize = 348;
|
|
16509
|
+
hdrCopy.vox_offset = Math.max(352, headerSize + extFlag.length + extensionsData.length);
|
|
16485
16510
|
if (isDrawing) {
|
|
16486
16511
|
hdrCopy.datatypeCode = 2 /* DT_UINT8 */;
|
|
16487
16512
|
hdrCopy.numBitsPerVoxel = 8;
|
|
@@ -16545,13 +16570,20 @@ function toUint8Array(nvImage, drawingBytes = null) {
|
|
|
16545
16570
|
}
|
|
16546
16571
|
imageBytesToSave = new Uint8Array(nvImage.img.buffer, nvImage.img.byteOffset, nvImage.img.byteLength);
|
|
16547
16572
|
}
|
|
16548
|
-
const
|
|
16549
|
-
const paddingSize = Math.max(0, hdrCopy.vox_offset -
|
|
16573
|
+
const preImageBytesSize = hdrBytes.length + extFlag.length + extensionsData.length;
|
|
16574
|
+
const paddingSize = Math.max(0, hdrCopy.vox_offset - preImageBytesSize);
|
|
16550
16575
|
const padding = new Uint8Array(paddingSize);
|
|
16551
16576
|
const totalLength = hdrCopy.vox_offset + imageBytesToSave.length;
|
|
16552
16577
|
const outputData = new Uint8Array(totalLength);
|
|
16553
|
-
|
|
16554
|
-
outputData.set(
|
|
16578
|
+
let offset = 0;
|
|
16579
|
+
outputData.set(hdrBytes, offset);
|
|
16580
|
+
offset += hdrBytes.length;
|
|
16581
|
+
outputData.set(extFlag, offset);
|
|
16582
|
+
offset += extFlag.length;
|
|
16583
|
+
outputData.set(extensionsData, offset);
|
|
16584
|
+
offset += extensionsData.length;
|
|
16585
|
+
outputData.set(padding, offset);
|
|
16586
|
+
offset += padding.length;
|
|
16555
16587
|
outputData.set(imageBytesToSave, hdrCopy.vox_offset);
|
|
16556
16588
|
return outputData;
|
|
16557
16589
|
}
|
|
@@ -16994,7 +17026,7 @@ var nii_exports = {};
|
|
|
16994
17026
|
__export(nii_exports, {
|
|
16995
17027
|
readNifti: () => readNifti
|
|
16996
17028
|
});
|
|
16997
|
-
import { isCompressed, decompressAsync, readHeaderAsync, readImage } from "nifti-reader-js";
|
|
17029
|
+
import { isCompressed, decompressAsync, readHeaderAsync, readImage, hasExtension } from "nifti-reader-js";
|
|
16998
17030
|
async function readNifti(nvImage, buffer) {
|
|
16999
17031
|
let dataBuffer = buffer;
|
|
17000
17032
|
let imgRaw = null;
|
|
@@ -17008,6 +17040,9 @@ async function readNifti(nvImage, buffer) {
|
|
|
17008
17040
|
throw new Error("Buffer became invalid after decompression attempt.");
|
|
17009
17041
|
}
|
|
17010
17042
|
nvImage.hdr = await readHeaderAsync(dataBuffer);
|
|
17043
|
+
if (hasExtension(nvImage.hdr)) {
|
|
17044
|
+
nvImage.extensions = nvImage.hdr.extensions;
|
|
17045
|
+
}
|
|
17011
17046
|
if (nvImage.hdr === null) {
|
|
17012
17047
|
throw new Error(`Failed to read NIfTI header: ${nvImage.name}`);
|
|
17013
17048
|
}
|
|
@@ -17378,6 +17413,7 @@ var NVImage = class _NVImage {
|
|
|
17378
17413
|
__publicField(this, "extentsMaxOrtho");
|
|
17379
17414
|
__publicField(this, "mm2ortho");
|
|
17380
17415
|
__publicField(this, "hdr", null);
|
|
17416
|
+
__publicField(this, "extensions");
|
|
17381
17417
|
__publicField(this, "imageType");
|
|
17382
17418
|
__publicField(this, "img");
|
|
17383
17419
|
__publicField(this, "imaginary");
|
|
@@ -20785,6 +20821,7 @@ var DRAG_MODE = /* @__PURE__ */ ((DRAG_MODE2) => {
|
|
|
20785
20821
|
DRAG_MODE2[DRAG_MODE2["slicer3D"] = 4] = "slicer3D";
|
|
20786
20822
|
DRAG_MODE2[DRAG_MODE2["callbackOnly"] = 5] = "callbackOnly";
|
|
20787
20823
|
DRAG_MODE2[DRAG_MODE2["roiSelection"] = 6] = "roiSelection";
|
|
20824
|
+
DRAG_MODE2[DRAG_MODE2["angle"] = 7] = "angle";
|
|
20788
20825
|
return DRAG_MODE2;
|
|
20789
20826
|
})(DRAG_MODE || {});
|
|
20790
20827
|
var DRAG_MODE_SECONDARY = /* @__PURE__ */ ((DRAG_MODE_SECONDARY2) => {
|
|
@@ -20795,6 +20832,7 @@ var DRAG_MODE_SECONDARY = /* @__PURE__ */ ((DRAG_MODE_SECONDARY2) => {
|
|
|
20795
20832
|
DRAG_MODE_SECONDARY2[DRAG_MODE_SECONDARY2["slicer3D"] = 4] = "slicer3D";
|
|
20796
20833
|
DRAG_MODE_SECONDARY2[DRAG_MODE_SECONDARY2["callbackOnly"] = 5] = "callbackOnly";
|
|
20797
20834
|
DRAG_MODE_SECONDARY2[DRAG_MODE_SECONDARY2["roiSelection"] = 6] = "roiSelection";
|
|
20835
|
+
DRAG_MODE_SECONDARY2[DRAG_MODE_SECONDARY2["angle"] = 7] = "angle";
|
|
20798
20836
|
return DRAG_MODE_SECONDARY2;
|
|
20799
20837
|
})(DRAG_MODE_SECONDARY || {});
|
|
20800
20838
|
var DRAG_MODE_PRIMARY = /* @__PURE__ */ ((DRAG_MODE_PRIMARY2) => {
|
|
@@ -25800,7 +25838,9 @@ var Niivue = class {
|
|
|
25800
25838
|
lastTwoTouchDistance: 0,
|
|
25801
25839
|
multiTouchGesture: false,
|
|
25802
25840
|
windowX: 0,
|
|
25803
|
-
windowY: 0
|
|
25841
|
+
windowY: 0,
|
|
25842
|
+
angleFirstLine: [0, 0, 0, 0],
|
|
25843
|
+
angleState: "none"
|
|
25804
25844
|
});
|
|
25805
25845
|
__publicField(this, "back", null);
|
|
25806
25846
|
// base layer; defines image space to work in. Defined as this.volumes[0] in Niivue.loadVolumes
|
|
@@ -25900,6 +25940,7 @@ var Niivue = class {
|
|
|
25900
25940
|
__publicField(this, "dragModes", {
|
|
25901
25941
|
contrast: 1 /* contrast */,
|
|
25902
25942
|
measurement: 2 /* measurement */,
|
|
25943
|
+
angle: 7 /* angle */,
|
|
25903
25944
|
none: 0 /* none */,
|
|
25904
25945
|
pan: 3 /* pan */,
|
|
25905
25946
|
slicer3D: 4 /* slicer3D */,
|
|
@@ -26624,8 +26665,10 @@ var Niivue = class {
|
|
|
26624
26665
|
this.drawPenLocation = [NaN, NaN, NaN];
|
|
26625
26666
|
this.drawPenAxCorSag = -1;
|
|
26626
26667
|
this.uiData.mousedown = true;
|
|
26627
|
-
this.
|
|
26628
|
-
|
|
26668
|
+
if (!(this.opts.dragMode === 7 /* angle */ && this.uiData.angleState === "drawing_second_line")) {
|
|
26669
|
+
this.setDragStart(0, 0);
|
|
26670
|
+
this.setDragEnd(0, 0);
|
|
26671
|
+
}
|
|
26629
26672
|
log.debug("mouse down");
|
|
26630
26673
|
log.debug(e);
|
|
26631
26674
|
const pos = this.getNoPaddingNoBorderCanvasRelativeMousePosition(e, this.gl.canvas);
|
|
@@ -26717,6 +26760,18 @@ var Niivue = class {
|
|
|
26717
26760
|
if (this.opts.dragMode === 0 /* none */) {
|
|
26718
26761
|
return;
|
|
26719
26762
|
}
|
|
26763
|
+
if (this.opts.dragMode === 7 /* angle */) {
|
|
26764
|
+
if (this.uiData.angleState === "none") {
|
|
26765
|
+
this.uiData.angleState = "drawing_first_line";
|
|
26766
|
+
} else if (this.uiData.angleState === "drawing_second_line") {
|
|
26767
|
+
this.uiData.angleState = "complete";
|
|
26768
|
+
this.drawScene();
|
|
26769
|
+
return;
|
|
26770
|
+
} else if (this.uiData.angleState === "complete") {
|
|
26771
|
+
this.resetAngleMeasurement();
|
|
26772
|
+
this.uiData.angleState = "drawing_first_line";
|
|
26773
|
+
}
|
|
26774
|
+
}
|
|
26720
26775
|
this.setDragStart(pos.x, pos.y);
|
|
26721
26776
|
if (!this.uiData.isDragging) {
|
|
26722
26777
|
this.uiData.pan2DxyzmmAtMouseDown = vec410.clone(this.scene.pan2Dxyzmm);
|
|
@@ -26866,6 +26921,24 @@ var Niivue = class {
|
|
|
26866
26921
|
}
|
|
26867
26922
|
if (this.uiData.isDragging) {
|
|
26868
26923
|
this.uiData.isDragging = false;
|
|
26924
|
+
if (this.opts.dragMode === 7 /* angle */) {
|
|
26925
|
+
if (this.uiData.angleState === "drawing_first_line") {
|
|
26926
|
+
this.uiData.angleFirstLine = [
|
|
26927
|
+
this.uiData.dragStart[0],
|
|
26928
|
+
this.uiData.dragStart[1],
|
|
26929
|
+
this.uiData.dragEnd[0],
|
|
26930
|
+
this.uiData.dragEnd[1]
|
|
26931
|
+
];
|
|
26932
|
+
this.uiData.angleState = "drawing_second_line";
|
|
26933
|
+
this.uiData.isDragging = true;
|
|
26934
|
+
this.drawScene();
|
|
26935
|
+
return;
|
|
26936
|
+
} else if (this.uiData.angleState === "drawing_second_line") {
|
|
26937
|
+
this.uiData.angleState = "complete";
|
|
26938
|
+
this.drawScene();
|
|
26939
|
+
return;
|
|
26940
|
+
}
|
|
26941
|
+
}
|
|
26869
26942
|
if (this.opts.dragMode === 5 /* callbackOnly */) {
|
|
26870
26943
|
this.drawScene();
|
|
26871
26944
|
}
|
|
@@ -27062,6 +27135,13 @@ var Niivue = class {
|
|
|
27062
27135
|
this.drawScene();
|
|
27063
27136
|
this.uiData.prevX = this.uiData.currX;
|
|
27064
27137
|
this.uiData.prevY = this.uiData.currY;
|
|
27138
|
+
} else if (this.opts.dragMode === 7 /* angle */ && this.uiData.angleState === "drawing_second_line") {
|
|
27139
|
+
const pos = this.getNoPaddingNoBorderCanvasRelativeMousePosition(e, this.gl.canvas);
|
|
27140
|
+
if (!pos) {
|
|
27141
|
+
return;
|
|
27142
|
+
}
|
|
27143
|
+
this.setDragEnd(pos.x, pos.y);
|
|
27144
|
+
this.drawScene();
|
|
27065
27145
|
} else if (!this.uiData.mousedown && this.opts.clickToSegment) {
|
|
27066
27146
|
const pos = this.getNoPaddingNoBorderCanvasRelativeMousePosition(e, this.gl.canvas);
|
|
27067
27147
|
if (!pos) {
|
|
@@ -34072,7 +34152,7 @@ var Niivue = class {
|
|
|
34072
34152
|
* Draw a measurement line with end caps and length text on a 2D tile.
|
|
34073
34153
|
* @internal
|
|
34074
34154
|
*/
|
|
34075
|
-
drawMeasurementTool(startXYendXY) {
|
|
34155
|
+
drawMeasurementTool(startXYendXY, isDrawText = true) {
|
|
34076
34156
|
function extendTo(x0, y0, x1, y1, distance) {
|
|
34077
34157
|
const x = x0 - x1;
|
|
34078
34158
|
const y = y0 - y1;
|
|
@@ -34151,16 +34231,153 @@ var Niivue = class {
|
|
|
34151
34231
|
textCoords = startXYendXY;
|
|
34152
34232
|
break;
|
|
34153
34233
|
}
|
|
34154
|
-
|
|
34155
|
-
|
|
34156
|
-
|
|
34157
|
-
|
|
34158
|
-
|
|
34159
|
-
|
|
34160
|
-
|
|
34234
|
+
if (isDrawText) {
|
|
34235
|
+
this.drawTextBetween(
|
|
34236
|
+
textCoords,
|
|
34237
|
+
stringMM,
|
|
34238
|
+
this.opts.measureTextHeight / 0.06,
|
|
34239
|
+
// <- TODO measureFontPx
|
|
34240
|
+
this.opts.measureTextColor
|
|
34241
|
+
);
|
|
34242
|
+
}
|
|
34161
34243
|
}
|
|
34162
34244
|
gl.bindVertexArray(this.unusedVAO);
|
|
34163
34245
|
}
|
|
34246
|
+
/**
|
|
34247
|
+
* Draw angle measurement tool with two lines and angle display.
|
|
34248
|
+
* @internal
|
|
34249
|
+
*/
|
|
34250
|
+
drawAngleMeasurementTool() {
|
|
34251
|
+
if (this.uiData.angleState === "drawing_first_line") {
|
|
34252
|
+
this.drawMeasurementTool(
|
|
34253
|
+
[this.uiData.dragStart[0], this.uiData.dragStart[1], this.uiData.dragEnd[0], this.uiData.dragEnd[1]],
|
|
34254
|
+
false
|
|
34255
|
+
);
|
|
34256
|
+
} else if (this.uiData.angleState === "drawing_second_line") {
|
|
34257
|
+
this.drawMeasurementTool(this.uiData.angleFirstLine, false);
|
|
34258
|
+
this.drawMeasurementTool(
|
|
34259
|
+
[
|
|
34260
|
+
this.uiData.angleFirstLine[2],
|
|
34261
|
+
// start from end of first line
|
|
34262
|
+
this.uiData.angleFirstLine[3],
|
|
34263
|
+
this.uiData.dragEnd[0],
|
|
34264
|
+
// to current mouse position
|
|
34265
|
+
this.uiData.dragEnd[1]
|
|
34266
|
+
],
|
|
34267
|
+
false
|
|
34268
|
+
);
|
|
34269
|
+
this.drawAngleText();
|
|
34270
|
+
} else if (this.uiData.angleState === "complete") {
|
|
34271
|
+
this.drawMeasurementTool(this.uiData.angleFirstLine, false);
|
|
34272
|
+
const secondLine = [
|
|
34273
|
+
this.uiData.angleFirstLine[2],
|
|
34274
|
+
// start from end of first line
|
|
34275
|
+
this.uiData.angleFirstLine[3],
|
|
34276
|
+
this.uiData.dragEnd[0],
|
|
34277
|
+
// to final position
|
|
34278
|
+
this.uiData.dragEnd[1]
|
|
34279
|
+
];
|
|
34280
|
+
this.drawMeasurementTool(secondLine, false);
|
|
34281
|
+
this.drawAngleText();
|
|
34282
|
+
}
|
|
34283
|
+
}
|
|
34284
|
+
/**
|
|
34285
|
+
* Calculate and draw angle text at the intersection of two lines.
|
|
34286
|
+
* @internal
|
|
34287
|
+
*/
|
|
34288
|
+
drawAngleText() {
|
|
34289
|
+
const line1 = this.uiData.angleFirstLine;
|
|
34290
|
+
const line2 = [
|
|
34291
|
+
this.uiData.angleFirstLine[2],
|
|
34292
|
+
// start from end of first line
|
|
34293
|
+
this.uiData.angleFirstLine[3],
|
|
34294
|
+
this.uiData.dragEnd[0],
|
|
34295
|
+
// to current mouse position
|
|
34296
|
+
this.uiData.dragEnd[1]
|
|
34297
|
+
];
|
|
34298
|
+
const angle = this.calculateAngleBetweenLines(line1, line2);
|
|
34299
|
+
const intersectionX = this.uiData.angleFirstLine[2];
|
|
34300
|
+
const intersectionY = this.uiData.angleFirstLine[3];
|
|
34301
|
+
const angleText = `${angle.toFixed(1)}\xB0`;
|
|
34302
|
+
this.drawTextBetween(
|
|
34303
|
+
[intersectionX, intersectionY, intersectionX + 1, intersectionY + 1],
|
|
34304
|
+
angleText,
|
|
34305
|
+
this.opts.measureTextHeight / 0.06,
|
|
34306
|
+
this.opts.measureTextColor
|
|
34307
|
+
);
|
|
34308
|
+
}
|
|
34309
|
+
/**
|
|
34310
|
+
* Calculate angle between two lines in degrees.
|
|
34311
|
+
* @internal
|
|
34312
|
+
*/
|
|
34313
|
+
calculateAngleBetweenLines(line1, line2) {
|
|
34314
|
+
const intersectionX = line1[2];
|
|
34315
|
+
const intersectionY = line1[3];
|
|
34316
|
+
const v1x = line1[0] - intersectionX;
|
|
34317
|
+
const v1y = line1[1] - intersectionY;
|
|
34318
|
+
const v2x = line2[2] - intersectionX;
|
|
34319
|
+
const v2y = line2[3] - intersectionY;
|
|
34320
|
+
const dot = v1x * v2x + v1y * v2y;
|
|
34321
|
+
const mag1 = Math.sqrt(v1x * v1x + v1y * v1y);
|
|
34322
|
+
const mag2 = Math.sqrt(v2x * v2x + v2y * v2y);
|
|
34323
|
+
if (mag1 === 0 || mag2 === 0) {
|
|
34324
|
+
return 0;
|
|
34325
|
+
}
|
|
34326
|
+
const cosAngle = Math.max(-1, Math.min(1, dot / (mag1 * mag2)));
|
|
34327
|
+
const angleRad = Math.acos(cosAngle);
|
|
34328
|
+
const angleDeg = angleRad * (180 / Math.PI);
|
|
34329
|
+
return angleDeg;
|
|
34330
|
+
}
|
|
34331
|
+
/**
|
|
34332
|
+
* Reset the angle measurement state.
|
|
34333
|
+
* @internal
|
|
34334
|
+
*/
|
|
34335
|
+
resetAngleMeasurement() {
|
|
34336
|
+
this.uiData.angleState = "none";
|
|
34337
|
+
this.uiData.angleFirstLine = [0, 0, 0, 0];
|
|
34338
|
+
}
|
|
34339
|
+
/**
|
|
34340
|
+
* Set the drag mode for mouse interactions.
|
|
34341
|
+
* @param mode - The drag mode to set ('none', 'contrast', 'measurement', 'angle', 'pan', 'slicer3D', 'callbackOnly', 'roiSelection')
|
|
34342
|
+
*/
|
|
34343
|
+
setDragMode(mode) {
|
|
34344
|
+
if (typeof mode === "string") {
|
|
34345
|
+
switch (mode) {
|
|
34346
|
+
case "none":
|
|
34347
|
+
this.opts.dragMode = 0 /* none */;
|
|
34348
|
+
break;
|
|
34349
|
+
case "contrast":
|
|
34350
|
+
this.opts.dragMode = 1 /* contrast */;
|
|
34351
|
+
break;
|
|
34352
|
+
case "measurement":
|
|
34353
|
+
this.opts.dragMode = 2 /* measurement */;
|
|
34354
|
+
break;
|
|
34355
|
+
case "angle":
|
|
34356
|
+
this.opts.dragMode = 7 /* angle */;
|
|
34357
|
+
break;
|
|
34358
|
+
case "pan":
|
|
34359
|
+
this.opts.dragMode = 3 /* pan */;
|
|
34360
|
+
break;
|
|
34361
|
+
case "slicer3D":
|
|
34362
|
+
this.opts.dragMode = 4 /* slicer3D */;
|
|
34363
|
+
break;
|
|
34364
|
+
case "callbackOnly":
|
|
34365
|
+
this.opts.dragMode = 5 /* callbackOnly */;
|
|
34366
|
+
break;
|
|
34367
|
+
case "roiSelection":
|
|
34368
|
+
this.opts.dragMode = 6 /* roiSelection */;
|
|
34369
|
+
break;
|
|
34370
|
+
default:
|
|
34371
|
+
console.warn(`Unknown drag mode: ${mode}`);
|
|
34372
|
+
return;
|
|
34373
|
+
}
|
|
34374
|
+
} else {
|
|
34375
|
+
this.opts.dragMode = mode;
|
|
34376
|
+
}
|
|
34377
|
+
if (this.opts.dragMode !== 7 /* angle */) {
|
|
34378
|
+
this.resetAngleMeasurement();
|
|
34379
|
+
}
|
|
34380
|
+
}
|
|
34164
34381
|
/**
|
|
34165
34382
|
* Draw a rectangle or outline at given position with specified color or default crosshair color.
|
|
34166
34383
|
* @internal
|
|
@@ -37421,6 +37638,10 @@ var Niivue = class {
|
|
|
37421
37638
|
]);
|
|
37422
37639
|
return;
|
|
37423
37640
|
}
|
|
37641
|
+
if (this.opts.dragMode === 7 /* angle */) {
|
|
37642
|
+
this.drawAngleMeasurementTool();
|
|
37643
|
+
return;
|
|
37644
|
+
}
|
|
37424
37645
|
const width = Math.abs(this.uiData.dragStart[0] - this.uiData.dragEnd[0]);
|
|
37425
37646
|
const height = Math.abs(this.uiData.dragStart[1] - this.uiData.dragEnd[1]);
|
|
37426
37647
|
this.drawSelectionBox([
|