@niivue/niivue 0.44.1 → 0.44.2-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/build/index.d.ts +98 -17
- package/build/index.js +827 -169
- package/build/index.js.map +1 -1
- package/build/index.min.js +1 -1
- package/dist/index.d.ts +98 -17
- package/dist/index.js +26209 -30685
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/niivue.umd.js +110 -33
- package/package.json +18 -17
- package/src/index.html +46 -7
- package/src/index_segment.html +165 -0
- package/src/index_sync.html +289 -0
- package/src/niivue/index.ts +831 -136
- package/src/nvdocument.ts +36 -5
- package/src/nvimage/index.ts +4 -1
- package/src/nvimage/utils.ts +61 -42
- package/src/nvlabel.ts +34 -1
- package/src/nvmesh-loaders.ts +24 -1
- package/src/shader-srcs.ts +84 -3
- package/src/types.ts +13 -0
package/build/index.js
CHANGED
|
@@ -4,16 +4,13 @@ var __export = (target, all) => {
|
|
|
4
4
|
for (var name in all)
|
|
5
5
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
6
|
};
|
|
7
|
-
var __publicField = (obj, key, value) =>
|
|
8
|
-
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
9
|
-
return value;
|
|
10
|
-
};
|
|
7
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
11
8
|
|
|
12
9
|
// src/niivue/index.ts
|
|
13
10
|
import { mat4 as mat46, vec2, vec3 as vec311, vec4 as vec48 } from "gl-matrix";
|
|
14
11
|
|
|
15
12
|
// package.json
|
|
16
|
-
var version = "0.44.1";
|
|
13
|
+
var version = "0.44.2-dev.1";
|
|
17
14
|
|
|
18
15
|
// src/logger.ts
|
|
19
16
|
var _Log = class _Log {
|
|
@@ -809,6 +806,46 @@ out vec4 color;
|
|
|
809
806
|
void main() {
|
|
810
807
|
color = lineColor;
|
|
811
808
|
}`;
|
|
809
|
+
var fragRectOutlineShader = `#version 300 es
|
|
810
|
+
#line 723
|
|
811
|
+
precision highp int;
|
|
812
|
+
precision highp float;
|
|
813
|
+
|
|
814
|
+
uniform vec4 lineColor;
|
|
815
|
+
uniform vec4 leftTopWidthHeight;
|
|
816
|
+
uniform float thickness; // line thickness in pixels
|
|
817
|
+
uniform vec2 canvasWidthHeight;
|
|
818
|
+
|
|
819
|
+
out vec4 color;
|
|
820
|
+
|
|
821
|
+
void main() {
|
|
822
|
+
// fragment position in screen coordinates
|
|
823
|
+
vec2 fragCoord = gl_FragCoord.xy;
|
|
824
|
+
|
|
825
|
+
// canvas height
|
|
826
|
+
float canvasHeight = canvasWidthHeight.y;
|
|
827
|
+
|
|
828
|
+
// 'top' and 'bottom' to match gl_FragCoord.y coordinate system
|
|
829
|
+
float top = canvasHeight - leftTopWidthHeight.y;
|
|
830
|
+
float bottom = top - leftTopWidthHeight.w;
|
|
831
|
+
|
|
832
|
+
// left and right edges
|
|
833
|
+
float left = leftTopWidthHeight.x;
|
|
834
|
+
float right = left + leftTopWidthHeight.z;
|
|
835
|
+
|
|
836
|
+
bool withinLeft = fragCoord.x >= left && fragCoord.x <= left + thickness;
|
|
837
|
+
bool withinRight = fragCoord.x <= right && fragCoord.x >= right - thickness;
|
|
838
|
+
bool withinTop = fragCoord.y <= top && fragCoord.y >= top - thickness;
|
|
839
|
+
bool withinBottom = fragCoord.y >= bottom && fragCoord.y <= bottom + thickness;
|
|
840
|
+
|
|
841
|
+
bool isOutline = withinLeft || withinRight || withinTop || withinBottom;
|
|
842
|
+
|
|
843
|
+
if (isOutline) {
|
|
844
|
+
color = lineColor;
|
|
845
|
+
} else {
|
|
846
|
+
discard;
|
|
847
|
+
}
|
|
848
|
+
}`;
|
|
812
849
|
var vertColorbarShader = `#version 300 es
|
|
813
850
|
#line 490
|
|
814
851
|
layout(location=0) in vec3 pos;
|
|
@@ -1271,11 +1308,11 @@ uniform mat4 mvpMtx;
|
|
|
1271
1308
|
uniform mat4 normMtx;
|
|
1272
1309
|
out vec4 vClr;
|
|
1273
1310
|
out vec3 vN;
|
|
1274
|
-
out vec4
|
|
1311
|
+
out vec4 vPc;
|
|
1275
1312
|
void main(void) {
|
|
1276
1313
|
vec3 lightPosition = vec3(0.0, 0.0, -10.0);
|
|
1277
|
-
|
|
1278
|
-
gl_Position =
|
|
1314
|
+
vPc = mvpMtx * vec4(pos, 1.0);
|
|
1315
|
+
gl_Position = vPc;
|
|
1279
1316
|
vN = normalize((normMtx * vec4(norm.xyz,1.0)).xyz);
|
|
1280
1317
|
//vV = -vec3(modelMtx*vec4(pos,1.0));
|
|
1281
1318
|
vClr = clr;
|
|
@@ -1420,6 +1457,45 @@ void main() {
|
|
|
1420
1457
|
s *= specularWhite;
|
|
1421
1458
|
color = vec4(d3 + s3 + s, opacity);
|
|
1422
1459
|
}`;
|
|
1460
|
+
var fragMeshShaderCrevice = `#version 300 es
|
|
1461
|
+
precision highp int;
|
|
1462
|
+
precision highp float;
|
|
1463
|
+
uniform float opacity;
|
|
1464
|
+
in vec4 vClr;
|
|
1465
|
+
in vec3 vN;
|
|
1466
|
+
in vec4 vPc;
|
|
1467
|
+
out vec4 color;
|
|
1468
|
+
void main() {
|
|
1469
|
+
vec3 n = normalize(vN);
|
|
1470
|
+
// Compute curvature
|
|
1471
|
+
vec3 dx = dFdx(n);
|
|
1472
|
+
vec3 dy = dFdy(n);
|
|
1473
|
+
vec3 xneg = n - dx;
|
|
1474
|
+
vec3 xpos = n + dx;
|
|
1475
|
+
vec3 yneg = n - dy;
|
|
1476
|
+
vec3 ypos = n + dy;
|
|
1477
|
+
float depth = length(vPc.xyz);
|
|
1478
|
+
float curv = (cross(xneg, xpos).y - cross(yneg, ypos).x) / depth;
|
|
1479
|
+
//at this stage 0.5 for flat, with valleys dark and ridges bright
|
|
1480
|
+
curv = 1.0 - (curv + 0.5);
|
|
1481
|
+
//clamp
|
|
1482
|
+
curv = min(max(curv, 0.0), 1.0);
|
|
1483
|
+
// easing function
|
|
1484
|
+
curv = pow(curv, 0.5);
|
|
1485
|
+
//modulate ambient and diffuse with curvature
|
|
1486
|
+
vec3 r = vec3(0.0, 0.0, 1.0); //rayDir: for orthographic projections moving in Z direction (no need for normal matrix)
|
|
1487
|
+
float ambient = 0.6;
|
|
1488
|
+
float diffuse = 0.6;
|
|
1489
|
+
float specular = 0.2;
|
|
1490
|
+
float shininess = 10.0;
|
|
1491
|
+
vec3 lightPosition = vec3(0.0, 10.0, -2.0);
|
|
1492
|
+
vec3 l = normalize(lightPosition);
|
|
1493
|
+
float lightNormDot = dot(n, l);
|
|
1494
|
+
vec3 a = vClr.rgb * ambient * curv;
|
|
1495
|
+
vec3 d = max(lightNormDot, 0.0) * vClr.rgb * diffuse;
|
|
1496
|
+
float s = specular * pow(max(dot(reflect(l, n), r), 0.0), shininess);
|
|
1497
|
+
color = vec4(a + d + s, opacity);
|
|
1498
|
+
}`;
|
|
1423
1499
|
var fragMeshShader = `#version 300 es
|
|
1424
1500
|
precision highp int;
|
|
1425
1501
|
precision highp float;
|
|
@@ -12357,6 +12433,12 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
12357
12433
|
while (lines[pos].length < 1) {
|
|
12358
12434
|
pos++;
|
|
12359
12435
|
}
|
|
12436
|
+
if (lines[pos].startsWith("METADATA")) {
|
|
12437
|
+
while (lines[pos].length > 1) {
|
|
12438
|
+
pos++;
|
|
12439
|
+
}
|
|
12440
|
+
pos++;
|
|
12441
|
+
}
|
|
12360
12442
|
items = lines[pos].trim().split(/\s+/);
|
|
12361
12443
|
pos++;
|
|
12362
12444
|
if (items[0].includes("LINES")) {
|
|
@@ -12535,7 +12617,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
12535
12617
|
layer.values = obj;
|
|
12536
12618
|
}
|
|
12537
12619
|
}
|
|
12538
|
-
} else if (ext === "CRV" || ext === "CURV") {
|
|
12620
|
+
} else if (ext === "CRV" || ext === "CURV" || ext === "THICKNESS" || ext === "AREA") {
|
|
12539
12621
|
layer.values = _NVMeshLoaders.readCURV(buffer, n_vert);
|
|
12540
12622
|
layer.isTransparentBelowCalMin = false;
|
|
12541
12623
|
} else if (ext === "GII") {
|
|
@@ -12560,6 +12642,9 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
12560
12642
|
layer.values = _NVMeshLoaders.readSMP(buffer, n_vert);
|
|
12561
12643
|
} else if (ext === "STC") {
|
|
12562
12644
|
layer.values = _NVMeshLoaders.readSTC(buffer, n_vert);
|
|
12645
|
+
} else if (_NVMeshLoaders.isCurv(buffer)) {
|
|
12646
|
+
layer.values = _NVMeshLoaders.readCURV(buffer, n_vert);
|
|
12647
|
+
layer.isTransparentBelowCalMin = false;
|
|
12563
12648
|
} else {
|
|
12564
12649
|
log.warn("Unknown layer overlay format " + name);
|
|
12565
12650
|
return layer;
|
|
@@ -12733,6 +12818,17 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
12733
12818
|
return f32;
|
|
12734
12819
|
}
|
|
12735
12820
|
// readSTC()
|
|
12821
|
+
static isCurv(buffer) {
|
|
12822
|
+
const view = new DataView(buffer);
|
|
12823
|
+
const sig0 = view.getUint8(0);
|
|
12824
|
+
const sig1 = view.getUint8(1);
|
|
12825
|
+
const sig2 = view.getUint8(2);
|
|
12826
|
+
if (sig0 !== 255 || sig1 !== 255 || sig2 !== 255) {
|
|
12827
|
+
utiltiesLogger.debug("Unable to recognize file type: does not appear to be FreeSurfer format.");
|
|
12828
|
+
return false;
|
|
12829
|
+
}
|
|
12830
|
+
return true;
|
|
12831
|
+
}
|
|
12736
12832
|
// read freesurfer curv big-endian format
|
|
12737
12833
|
// https://github.com/bonilhamusclab/MRIcroS/blob/master/%2BfileUtils/%2Bpial/readPial.m
|
|
12738
12834
|
// http://www.grahamwideman.com/gw/brain/fs/surfacefileformats.htm
|
|
@@ -18369,76 +18465,90 @@ function getBestTransform(imageDirections, voxelDimensions, imagePosition) {
|
|
|
18369
18465
|
}
|
|
18370
18466
|
return m;
|
|
18371
18467
|
}
|
|
18372
|
-
function str2Buffer(str) {
|
|
18468
|
+
function str2Buffer(str, maxLen = 80) {
|
|
18469
|
+
str = str.replace(/[`$]/g, "");
|
|
18373
18470
|
const bytes = [];
|
|
18374
|
-
|
|
18471
|
+
const len = Math.min(maxLen, str.length);
|
|
18472
|
+
for (let i = 0; i < len; i++) {
|
|
18375
18473
|
const char = str.charCodeAt(i);
|
|
18376
18474
|
bytes.push(char & 255);
|
|
18377
18475
|
}
|
|
18378
18476
|
return bytes;
|
|
18379
18477
|
}
|
|
18380
|
-
function hdrToArrayBuffer(hdr, isDrawing8 = false) {
|
|
18478
|
+
function hdrToArrayBuffer(hdr, isDrawing8 = false, isInputEndian = false) {
|
|
18381
18479
|
const SHORT_SIZE = 2;
|
|
18382
18480
|
const FLOAT32_SIZE = 4;
|
|
18481
|
+
let isLittleEndian = true;
|
|
18482
|
+
if (isInputEndian) {
|
|
18483
|
+
isLittleEndian = hdr.littleEndian;
|
|
18484
|
+
}
|
|
18383
18485
|
const byteArray = new Uint8Array(348);
|
|
18384
18486
|
const view = new DataView(byteArray.buffer);
|
|
18385
|
-
view.setInt32(0, 348,
|
|
18487
|
+
view.setInt32(0, 348, isLittleEndian);
|
|
18488
|
+
view.setUint8(38, 114);
|
|
18386
18489
|
view.setUint8(39, hdr.dim_info);
|
|
18387
18490
|
for (let i = 0; i < 8; i++) {
|
|
18388
|
-
view.setUint16(40 + SHORT_SIZE * i, hdr.dims[i],
|
|
18491
|
+
view.setUint16(40 + SHORT_SIZE * i, hdr.dims[i], isLittleEndian);
|
|
18389
18492
|
}
|
|
18390
|
-
view.setFloat32(56, hdr.intent_p1,
|
|
18391
|
-
view.setFloat32(60, hdr.intent_p2,
|
|
18392
|
-
view.setFloat32(64, hdr.intent_p3,
|
|
18393
|
-
view.setInt16(68, hdr.intent_code,
|
|
18493
|
+
view.setFloat32(56, hdr.intent_p1, isLittleEndian);
|
|
18494
|
+
view.setFloat32(60, hdr.intent_p2, isLittleEndian);
|
|
18495
|
+
view.setFloat32(64, hdr.intent_p3, isLittleEndian);
|
|
18496
|
+
view.setInt16(68, hdr.intent_code, isLittleEndian);
|
|
18394
18497
|
if (isDrawing8) {
|
|
18395
|
-
view.setInt16(70, 2,
|
|
18396
|
-
view.setInt16(72, 8,
|
|
18498
|
+
view.setInt16(70, 2, isLittleEndian);
|
|
18499
|
+
view.setInt16(72, 8, isLittleEndian);
|
|
18397
18500
|
} else {
|
|
18398
|
-
view.setInt16(70, hdr.datatypeCode,
|
|
18399
|
-
view.setInt16(72, hdr.numBitsPerVoxel,
|
|
18501
|
+
view.setInt16(70, hdr.datatypeCode, isLittleEndian);
|
|
18502
|
+
view.setInt16(72, hdr.numBitsPerVoxel, isLittleEndian);
|
|
18400
18503
|
}
|
|
18401
|
-
view.setInt16(74, hdr.slice_start,
|
|
18504
|
+
view.setInt16(74, hdr.slice_start, isLittleEndian);
|
|
18402
18505
|
for (let i = 0; i < 8; i++) {
|
|
18403
|
-
view.setFloat32(76 + FLOAT32_SIZE * i, hdr.pixDims[i],
|
|
18506
|
+
view.setFloat32(76 + FLOAT32_SIZE * i, hdr.pixDims[i], isLittleEndian);
|
|
18404
18507
|
}
|
|
18405
18508
|
if (isDrawing8) {
|
|
18406
|
-
view.setFloat32(108, 352,
|
|
18407
|
-
view.setFloat32(112, 1,
|
|
18408
|
-
view.setFloat32(116, 0,
|
|
18509
|
+
view.setFloat32(108, 352, isLittleEndian);
|
|
18510
|
+
view.setFloat32(112, 1, isLittleEndian);
|
|
18511
|
+
view.setFloat32(116, 0, isLittleEndian);
|
|
18409
18512
|
} else {
|
|
18410
|
-
view.setFloat32(108,
|
|
18411
|
-
view.setFloat32(112, hdr.scl_slope,
|
|
18412
|
-
view.setFloat32(116, hdr.scl_inter,
|
|
18513
|
+
view.setFloat32(108, 352, isLittleEndian);
|
|
18514
|
+
view.setFloat32(112, hdr.scl_slope, isLittleEndian);
|
|
18515
|
+
view.setFloat32(116, hdr.scl_inter, isLittleEndian);
|
|
18413
18516
|
}
|
|
18414
|
-
view.setInt16(120, hdr.slice_end,
|
|
18517
|
+
view.setInt16(120, hdr.slice_end, isLittleEndian);
|
|
18415
18518
|
view.setUint8(122, hdr.slice_code);
|
|
18416
|
-
|
|
18519
|
+
if (hdr.xyzt_units === 0) {
|
|
18520
|
+
view.setUint8(123, 10);
|
|
18521
|
+
} else {
|
|
18522
|
+
view.setUint8(123, hdr.xyzt_units);
|
|
18523
|
+
}
|
|
18417
18524
|
if (isDrawing8) {
|
|
18418
|
-
view.setFloat32(124, 0,
|
|
18419
|
-
view.setFloat32(128, 0,
|
|
18525
|
+
view.setFloat32(124, 0, isLittleEndian);
|
|
18526
|
+
view.setFloat32(128, 0, isLittleEndian);
|
|
18420
18527
|
} else {
|
|
18421
|
-
view.setFloat32(124, hdr.cal_max,
|
|
18422
|
-
view.setFloat32(128, hdr.cal_min,
|
|
18528
|
+
view.setFloat32(124, hdr.cal_max, isLittleEndian);
|
|
18529
|
+
view.setFloat32(128, hdr.cal_min, isLittleEndian);
|
|
18423
18530
|
}
|
|
18424
|
-
view.setFloat32(132, hdr.slice_duration,
|
|
18425
|
-
view.setFloat32(136, hdr.toffset,
|
|
18531
|
+
view.setFloat32(132, hdr.slice_duration, isLittleEndian);
|
|
18532
|
+
view.setFloat32(136, hdr.toffset, isLittleEndian);
|
|
18426
18533
|
byteArray.set(str2Buffer(hdr.description), 148);
|
|
18427
18534
|
byteArray.set(str2Buffer(hdr.aux_file), 228);
|
|
18428
|
-
view.setInt16(252, hdr.qform_code,
|
|
18429
|
-
|
|
18430
|
-
|
|
18431
|
-
|
|
18432
|
-
|
|
18433
|
-
|
|
18434
|
-
view.setFloat32(
|
|
18435
|
-
view.setFloat32(
|
|
18535
|
+
view.setInt16(252, hdr.qform_code, isLittleEndian);
|
|
18536
|
+
if (hdr.sform_code < 1 || hdr.sform_code < 1) {
|
|
18537
|
+
view.setInt16(254, 1, isLittleEndian);
|
|
18538
|
+
} else {
|
|
18539
|
+
view.setInt16(254, hdr.sform_code, isLittleEndian);
|
|
18540
|
+
}
|
|
18541
|
+
view.setFloat32(256, hdr.quatern_b, isLittleEndian);
|
|
18542
|
+
view.setFloat32(260, hdr.quatern_c, isLittleEndian);
|
|
18543
|
+
view.setFloat32(264, hdr.quatern_d, isLittleEndian);
|
|
18544
|
+
view.setFloat32(268, hdr.qoffset_x, isLittleEndian);
|
|
18545
|
+
view.setFloat32(272, hdr.qoffset_y, isLittleEndian);
|
|
18546
|
+
view.setFloat32(276, hdr.qoffset_z, isLittleEndian);
|
|
18436
18547
|
const flattened = hdr.affine.flat();
|
|
18437
18548
|
for (let i = 0; i < 12; i++) {
|
|
18438
|
-
view.setFloat32(280 + FLOAT32_SIZE * i, flattened[i],
|
|
18549
|
+
view.setFloat32(280 + FLOAT32_SIZE * i, flattened[i], isLittleEndian);
|
|
18439
18550
|
}
|
|
18440
|
-
|
|
18441
|
-
byteArray.set(str2Buffer(hdr.magic), 344);
|
|
18551
|
+
view.setInt32(344, 3222382, true);
|
|
18442
18552
|
return byteArray;
|
|
18443
18553
|
}
|
|
18444
18554
|
function getExtents(positions, forceOriginInVolume = true) {
|
|
@@ -19982,7 +20092,7 @@ var NVImage = class _NVImage {
|
|
|
19982
20092
|
hdr.datatypeCode = 64 /* DT_FLOAT64 */;
|
|
19983
20093
|
break;
|
|
19984
20094
|
default:
|
|
19985
|
-
throw new Error("Unsupported
|
|
20095
|
+
throw new Error("Unsupported MHA data type: " + items[0]);
|
|
19986
20096
|
}
|
|
19987
20097
|
}
|
|
19988
20098
|
if (line.startsWith("ObjectType") && !items[0].includes("Image")) {
|
|
@@ -20004,6 +20114,9 @@ var NVImage = class _NVImage {
|
|
|
20004
20114
|
[mat33[2], mat33[5], mat33[8], offset[2]],
|
|
20005
20115
|
[0, 0, 0, 1]
|
|
20006
20116
|
];
|
|
20117
|
+
while (bytes[pos] === 10) {
|
|
20118
|
+
pos++;
|
|
20119
|
+
}
|
|
20007
20120
|
hdr.vox_offset = pos;
|
|
20008
20121
|
if (isDetached && pairedImgData) {
|
|
20009
20122
|
if (isGz) {
|
|
@@ -22177,6 +22290,7 @@ var DRAG_MODE = /* @__PURE__ */ ((DRAG_MODE2) => {
|
|
|
22177
22290
|
DRAG_MODE2[DRAG_MODE2["pan"] = 3] = "pan";
|
|
22178
22291
|
DRAG_MODE2[DRAG_MODE2["slicer3D"] = 4] = "slicer3D";
|
|
22179
22292
|
DRAG_MODE2[DRAG_MODE2["callbackOnly"] = 5] = "callbackOnly";
|
|
22293
|
+
DRAG_MODE2[DRAG_MODE2["roiSelection"] = 6] = "roiSelection";
|
|
22180
22294
|
return DRAG_MODE2;
|
|
22181
22295
|
})(DRAG_MODE || {});
|
|
22182
22296
|
var DEFAULT_OPTIONS = {
|
|
@@ -22211,6 +22325,7 @@ var DEFAULT_OPTIONS = {
|
|
|
22211
22325
|
multiplanarPadPixels: 0,
|
|
22212
22326
|
// @deprecated
|
|
22213
22327
|
multiplanarForceRender: false,
|
|
22328
|
+
multiplanarEqualSize: false,
|
|
22214
22329
|
multiplanarShowRender: 2 /* AUTO */,
|
|
22215
22330
|
// auto is the same behaviour as multiplanarForceRender: false
|
|
22216
22331
|
isRadiologicalConvention: false,
|
|
@@ -22245,12 +22360,30 @@ var DEFAULT_OPTIONS = {
|
|
|
22245
22360
|
renderOverlayBlend: 1,
|
|
22246
22361
|
sliceMosaicString: "",
|
|
22247
22362
|
centerMosaic: false,
|
|
22363
|
+
penSize: 1,
|
|
22364
|
+
// in voxels, since all drawing is done using bitmap indices
|
|
22248
22365
|
clickToSegment: false,
|
|
22249
|
-
clickToSegmentRadius:
|
|
22250
|
-
|
|
22251
|
-
clickToSegmentBright: true
|
|
22366
|
+
clickToSegmentRadius: 3,
|
|
22367
|
+
// in mm
|
|
22368
|
+
clickToSegmentBright: true,
|
|
22369
|
+
clickToSegmentAutoIntensity: false,
|
|
22370
|
+
// new option, but keep clickToSegmentBright for backwards compatibility
|
|
22371
|
+
clickToSegmentIntensityMax: NaN,
|
|
22372
|
+
// NaN will use auto threshold (default flood fill behavior from before)
|
|
22373
|
+
clickToSegmentIntensityMin: NaN,
|
|
22374
|
+
// NaN will use auto threshold (default flood fill behavior from before)
|
|
22375
|
+
// 0 will use auto threshold (default flood fill behavior from before)
|
|
22376
|
+
// Take the voxel intensity at the click point and use this percentage +/- to threshold the flood fill operation.
|
|
22377
|
+
// If greater than 0, clickedVoxelIntensity +/- clickedVoxelIntensity * clickToSegmentPercent will be used
|
|
22378
|
+
// for the clickToSegmentIntensityMin and clickToSegmentIntensityMax values.
|
|
22379
|
+
clickToSegmentPercent: 0,
|
|
22380
|
+
clickToSegmentMaxDistanceMM: Number.POSITIVE_INFINITY,
|
|
22381
|
+
// default value is infinity for backwards compatibility with flood fill routine.
|
|
22382
|
+
selectionBoxLineThickness: 4,
|
|
22383
|
+
selectionBoxIsOutline: false
|
|
22252
22384
|
};
|
|
22253
22385
|
var INITIAL_SCENE_DATA = {
|
|
22386
|
+
gamma: 1,
|
|
22254
22387
|
azimuth: 110,
|
|
22255
22388
|
elevation: 10,
|
|
22256
22389
|
crosshairPos: vec38.create(),
|
|
@@ -22489,6 +22622,9 @@ var NVDocument = class _NVDocument {
|
|
|
22489
22622
|
data.opts.meshThicknessOn2D = "infinity";
|
|
22490
22623
|
}
|
|
22491
22624
|
data.labels = [...this.data.labels];
|
|
22625
|
+
for (const label of data.labels) {
|
|
22626
|
+
delete label.onClick;
|
|
22627
|
+
}
|
|
22492
22628
|
data.customData = this.customData;
|
|
22493
22629
|
if (this.volumes.length) {
|
|
22494
22630
|
let imageOptions = this.imageOptionsArray[0];
|
|
@@ -22744,6 +22880,19 @@ var LabelLineTerminator = /* @__PURE__ */ ((LabelLineTerminator2) => {
|
|
|
22744
22880
|
LabelLineTerminator2["RING"] = "ring";
|
|
22745
22881
|
return LabelLineTerminator2;
|
|
22746
22882
|
})(LabelLineTerminator || {});
|
|
22883
|
+
var LabelAnchorPoint = /* @__PURE__ */ ((LabelAnchorPoint2) => {
|
|
22884
|
+
LabelAnchorPoint2[LabelAnchorPoint2["NONE"] = 0 /* NONE */] = "NONE";
|
|
22885
|
+
LabelAnchorPoint2[LabelAnchorPoint2["TOPLEFT"] = 9] = "TOPLEFT";
|
|
22886
|
+
LabelAnchorPoint2[LabelAnchorPoint2["TOPCENTER"] = 10] = "TOPCENTER";
|
|
22887
|
+
LabelAnchorPoint2[LabelAnchorPoint2["TOPRIGHT"] = 12] = "TOPRIGHT";
|
|
22888
|
+
LabelAnchorPoint2[LabelAnchorPoint2["MIDDLELEFT"] = 17] = "MIDDLELEFT";
|
|
22889
|
+
LabelAnchorPoint2[LabelAnchorPoint2["MIDDLECENTER"] = 18] = "MIDDLECENTER";
|
|
22890
|
+
LabelAnchorPoint2[LabelAnchorPoint2["MIDDLERIGHT"] = 20] = "MIDDLERIGHT";
|
|
22891
|
+
LabelAnchorPoint2[LabelAnchorPoint2["BOTTOMLEFT"] = 33] = "BOTTOMLEFT";
|
|
22892
|
+
LabelAnchorPoint2[LabelAnchorPoint2["BOTTOMCENTER"] = 34] = "BOTTOMCENTER";
|
|
22893
|
+
LabelAnchorPoint2[LabelAnchorPoint2["BOTTOMRIGHT"] = 36] = "BOTTOMRIGHT";
|
|
22894
|
+
return LabelAnchorPoint2;
|
|
22895
|
+
})(LabelAnchorPoint || {});
|
|
22747
22896
|
var NVLabel3DStyle = class {
|
|
22748
22897
|
/**
|
|
22749
22898
|
* @param textColor - Color of text
|
|
@@ -22781,13 +22930,17 @@ var NVLabel3D = class {
|
|
|
22781
22930
|
* @param style - The style of the label
|
|
22782
22931
|
* @param points - An array of points label for label lines
|
|
22783
22932
|
*/
|
|
22784
|
-
constructor(text, style, points) {
|
|
22933
|
+
constructor(text, style, points, anchor, onClick) {
|
|
22785
22934
|
__publicField(this, "text");
|
|
22786
22935
|
__publicField(this, "style");
|
|
22787
22936
|
__publicField(this, "points");
|
|
22937
|
+
__publicField(this, "anchor");
|
|
22938
|
+
__publicField(this, "onClick");
|
|
22788
22939
|
this.text = text;
|
|
22789
22940
|
this.style = style;
|
|
22790
22941
|
this.points = points;
|
|
22942
|
+
this.anchor = anchor || 0 /* NONE */;
|
|
22943
|
+
this.onClick = onClick;
|
|
22791
22944
|
}
|
|
22792
22945
|
};
|
|
22793
22946
|
|
|
@@ -23924,7 +24077,7 @@ var defaultSaveImageOptions = {
|
|
|
23924
24077
|
isSaveDrawing: false,
|
|
23925
24078
|
volumeByIndex: 0
|
|
23926
24079
|
};
|
|
23927
|
-
var Niivue = class
|
|
24080
|
+
var Niivue = class {
|
|
23928
24081
|
/**
|
|
23929
24082
|
* @param options - options object to set modifiable Niivue properties
|
|
23930
24083
|
*/
|
|
@@ -23971,6 +24124,7 @@ var Niivue = class _Niivue {
|
|
|
23971
24124
|
__publicField(this, "orientCubeShader");
|
|
23972
24125
|
__publicField(this, "orientCubeShaderVAO", null);
|
|
23973
24126
|
__publicField(this, "rectShader");
|
|
24127
|
+
__publicField(this, "rectOutlineShader");
|
|
23974
24128
|
__publicField(this, "renderShader");
|
|
23975
24129
|
__publicField(this, "lineShader");
|
|
23976
24130
|
__publicField(this, "line3DShader");
|
|
@@ -24023,7 +24177,20 @@ var Niivue = class _Niivue {
|
|
|
24023
24177
|
__publicField(this, "extentsMax");
|
|
24024
24178
|
// ResizeObserver
|
|
24025
24179
|
__publicField(this, "resizeObserver", null);
|
|
24026
|
-
|
|
24180
|
+
// syncOpts: Record<string, unknown> = {}
|
|
24181
|
+
__publicField(this, "syncOpts", {
|
|
24182
|
+
"3d": false,
|
|
24183
|
+
// legacy option
|
|
24184
|
+
"2d": false,
|
|
24185
|
+
// legacy option
|
|
24186
|
+
zoomPan: false,
|
|
24187
|
+
cal_min: false,
|
|
24188
|
+
cal_max: false,
|
|
24189
|
+
clipPlane: false,
|
|
24190
|
+
gamma: false,
|
|
24191
|
+
sliceType: false,
|
|
24192
|
+
crosshair: false
|
|
24193
|
+
});
|
|
24027
24194
|
__publicField(this, "readyForSync", false);
|
|
24028
24195
|
// UI Data
|
|
24029
24196
|
__publicField(this, "uiData", {
|
|
@@ -24101,6 +24268,10 @@ var Niivue = class _Niivue {
|
|
|
24101
24268
|
Name: "Hemispheric",
|
|
24102
24269
|
Frag: fragMeshHemiShader
|
|
24103
24270
|
},
|
|
24271
|
+
{
|
|
24272
|
+
Name: "Crevice",
|
|
24273
|
+
Frag: fragMeshShaderCrevice
|
|
24274
|
+
},
|
|
24104
24275
|
{
|
|
24105
24276
|
Name: "Edge",
|
|
24106
24277
|
Frag: fragMeshEdgeShader
|
|
@@ -24498,6 +24669,9 @@ var Niivue = class _Niivue {
|
|
|
24498
24669
|
* @see {@link https://niivue.github.io/niivue/features/sync.mesh.html | live demo usage}
|
|
24499
24670
|
*/
|
|
24500
24671
|
syncWith(otherNV, syncOpts = { "2d": true, "3d": true }) {
|
|
24672
|
+
if (!(otherNV instanceof Array)) {
|
|
24673
|
+
otherNV = [otherNV];
|
|
24674
|
+
}
|
|
24501
24675
|
this.otherNV = otherNV;
|
|
24502
24676
|
this.syncOpts = { ...syncOpts };
|
|
24503
24677
|
}
|
|
@@ -24513,9 +24687,55 @@ var Niivue = class _Niivue {
|
|
|
24513
24687
|
* @see {@link https://niivue.github.io/niivue/features/sync.mesh.html | live demo usage}
|
|
24514
24688
|
*/
|
|
24515
24689
|
broadcastTo(otherNV, syncOpts = { "2d": true, "3d": true }) {
|
|
24690
|
+
if (!(otherNV instanceof Array)) {
|
|
24691
|
+
otherNV = [otherNV];
|
|
24692
|
+
}
|
|
24516
24693
|
this.otherNV = otherNV;
|
|
24517
24694
|
this.syncOpts = syncOpts;
|
|
24518
24695
|
}
|
|
24696
|
+
doSync3d(otherNV) {
|
|
24697
|
+
otherNV.scene.renderAzimuth = this.scene.renderAzimuth;
|
|
24698
|
+
otherNV.scene.renderElevation = this.scene.renderElevation;
|
|
24699
|
+
otherNV.scene.volScaleMultiplier = this.scene.volScaleMultiplier;
|
|
24700
|
+
}
|
|
24701
|
+
// both crosshair and zoomPan
|
|
24702
|
+
doSync2d(otherNV) {
|
|
24703
|
+
const thisMM = this.frac2mm(this.scene.crosshairPos);
|
|
24704
|
+
otherNV.scene.crosshairPos = otherNV.mm2frac(thisMM);
|
|
24705
|
+
otherNV.scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24706
|
+
}
|
|
24707
|
+
doSyncGamma(otherNV) {
|
|
24708
|
+
const thisGamma = this.scene.gamma;
|
|
24709
|
+
const otherGamma = otherNV.scene.gamma;
|
|
24710
|
+
if (thisGamma !== otherGamma) {
|
|
24711
|
+
otherNV.setGamma(thisGamma);
|
|
24712
|
+
}
|
|
24713
|
+
}
|
|
24714
|
+
doSyncZoomPan(otherNV) {
|
|
24715
|
+
otherNV.scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24716
|
+
}
|
|
24717
|
+
doSyncCrosshair(otherNV) {
|
|
24718
|
+
const thisMM = this.frac2mm(this.scene.crosshairPos);
|
|
24719
|
+
otherNV.scene.crosshairPos = otherNV.mm2frac(thisMM);
|
|
24720
|
+
}
|
|
24721
|
+
doSyncCalMin(otherNV) {
|
|
24722
|
+
if (this.volumes[0].cal_min !== otherNV.volumes[0].cal_min) {
|
|
24723
|
+
otherNV.volumes[0].cal_min = this.volumes[0].cal_min;
|
|
24724
|
+
otherNV.updateGLVolume();
|
|
24725
|
+
}
|
|
24726
|
+
}
|
|
24727
|
+
doSyncCalMax(otherNV) {
|
|
24728
|
+
if (this.volumes[0].cal_max !== otherNV.volumes[0].cal_max) {
|
|
24729
|
+
otherNV.volumes[0].cal_max = this.volumes[0].cal_max;
|
|
24730
|
+
otherNV.updateGLVolume();
|
|
24731
|
+
}
|
|
24732
|
+
}
|
|
24733
|
+
doSyncSliceType(otherNV) {
|
|
24734
|
+
otherNV.setSliceType(this.opts.sliceType);
|
|
24735
|
+
}
|
|
24736
|
+
doSyncClipPlane(otherNV) {
|
|
24737
|
+
otherNV.setClipPlane(this.scene.clipPlaneDepthAziElev);
|
|
24738
|
+
}
|
|
24519
24739
|
/**
|
|
24520
24740
|
* Sync the scene controls (orientation, crosshair location, etc.) from one Niivue instance to another. useful for using one canvas to drive another.
|
|
24521
24741
|
* @internal
|
|
@@ -24532,36 +24752,39 @@ var Niivue = class _Niivue {
|
|
|
24532
24752
|
if (!this.gl.canvas.matches(":focus")) {
|
|
24533
24753
|
return;
|
|
24534
24754
|
}
|
|
24535
|
-
|
|
24536
|
-
|
|
24755
|
+
for (let i = 0; i < this.otherNV.length; i++) {
|
|
24756
|
+
if (this.otherNV[i] === this) {
|
|
24757
|
+
continue;
|
|
24758
|
+
}
|
|
24759
|
+
if (this.syncOpts.gamma) {
|
|
24760
|
+
this.doSyncGamma(this.otherNV[i]);
|
|
24761
|
+
}
|
|
24762
|
+
if (this.syncOpts.crosshair) {
|
|
24763
|
+
this.doSyncCrosshair(this.otherNV[i]);
|
|
24764
|
+
}
|
|
24765
|
+
if (this.syncOpts.zoomPan) {
|
|
24766
|
+
this.doSyncZoomPan(this.otherNV[i]);
|
|
24767
|
+
}
|
|
24768
|
+
if (this.syncOpts.sliceType) {
|
|
24769
|
+
this.doSyncSliceType(this.otherNV[i]);
|
|
24770
|
+
}
|
|
24771
|
+
if (this.syncOpts.cal_min) {
|
|
24772
|
+
this.doSyncCalMin(this.otherNV[i]);
|
|
24773
|
+
}
|
|
24774
|
+
if (this.syncOpts.cal_max) {
|
|
24775
|
+
this.doSyncCalMax(this.otherNV[i]);
|
|
24776
|
+
}
|
|
24777
|
+
if (this.syncOpts.clipPlane) {
|
|
24778
|
+
this.doSyncClipPlane(this.otherNV[i]);
|
|
24779
|
+
}
|
|
24537
24780
|
if (this.syncOpts["2d"]) {
|
|
24538
|
-
this.
|
|
24539
|
-
this.otherNV.scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24781
|
+
this.doSync2d(this.otherNV[i]);
|
|
24540
24782
|
}
|
|
24541
24783
|
if (this.syncOpts["3d"]) {
|
|
24542
|
-
this.
|
|
24543
|
-
this.otherNV.scene.renderElevation = this.scene.renderElevation;
|
|
24544
|
-
this.otherNV.scene.volScaleMultiplier = this.scene.volScaleMultiplier;
|
|
24545
|
-
}
|
|
24546
|
-
this.otherNV.drawScene();
|
|
24547
|
-
this.otherNV.createOnLocationChange();
|
|
24548
|
-
} else if (Array.isArray(this.otherNV)) {
|
|
24549
|
-
for (let i = 0; i < this.otherNV.length; i++) {
|
|
24550
|
-
if (this.otherNV[i] === this) {
|
|
24551
|
-
continue;
|
|
24552
|
-
}
|
|
24553
|
-
if (this.syncOpts["2d"]) {
|
|
24554
|
-
this.otherNV[i].scene.crosshairPos = this.otherNV[i].mm2frac(thisMM);
|
|
24555
|
-
this.otherNV[i].scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24556
|
-
}
|
|
24557
|
-
if (this.syncOpts["3d"]) {
|
|
24558
|
-
this.otherNV[i].scene.renderAzimuth = this.scene.renderAzimuth;
|
|
24559
|
-
this.otherNV[i].scene.renderElevation = this.scene.renderElevation;
|
|
24560
|
-
this.otherNV[i].scene.volScaleMultiplier = this.scene.volScaleMultiplier;
|
|
24561
|
-
}
|
|
24562
|
-
this.otherNV[i].drawScene();
|
|
24563
|
-
this.otherNV[i].createOnLocationChange();
|
|
24784
|
+
this.doSync3d(this.otherNV[i]);
|
|
24564
24785
|
}
|
|
24786
|
+
this.otherNV[i].drawScene();
|
|
24787
|
+
this.otherNV[i].createOnLocationChange();
|
|
24565
24788
|
}
|
|
24566
24789
|
}
|
|
24567
24790
|
/** Not documented publicly for now
|
|
@@ -24665,6 +24888,10 @@ var Niivue = class _Niivue {
|
|
|
24665
24888
|
const [x, y] = [pos.x * this.uiData.dpr, pos.y * this.uiData.dpr];
|
|
24666
24889
|
const label = this.getLabelAtPoint([x, y]);
|
|
24667
24890
|
if (label) {
|
|
24891
|
+
if (label.onClick) {
|
|
24892
|
+
label.onClick(label);
|
|
24893
|
+
return;
|
|
24894
|
+
}
|
|
24668
24895
|
for (const mesh of this.meshes) {
|
|
24669
24896
|
if (mesh.type !== "connectome" /* CONNECTOME */) {
|
|
24670
24897
|
continue;
|
|
@@ -24874,6 +25101,9 @@ var Niivue = class _Niivue {
|
|
|
24874
25101
|
const fracStart = this.canvasPos2frac([this.uiData.dragStart[0], this.uiData.dragStart[1]]);
|
|
24875
25102
|
const fracEnd = this.canvasPos2frac([this.uiData.dragEnd[0], this.uiData.dragEnd[1]]);
|
|
24876
25103
|
this.generateMouseUpCallback(fracStart, fracEnd);
|
|
25104
|
+
if (this.opts.dragMode === 6 /* roiSelection */) {
|
|
25105
|
+
return;
|
|
25106
|
+
}
|
|
24877
25107
|
if (this.opts.dragMode !== 1 /* contrast */) {
|
|
24878
25108
|
return;
|
|
24879
25109
|
}
|
|
@@ -24979,6 +25209,15 @@ var Niivue = class _Niivue {
|
|
|
24979
25209
|
this.drawScene();
|
|
24980
25210
|
this.uiData.prevX = this.uiData.currX;
|
|
24981
25211
|
this.uiData.prevY = this.uiData.currY;
|
|
25212
|
+
} else if (!this.uiData.mousedown && this.opts.clickToSegment) {
|
|
25213
|
+
const pos = this.getNoPaddingNoBorderCanvasRelativeMousePosition(e, this.gl.canvas);
|
|
25214
|
+
if (!pos) {
|
|
25215
|
+
return;
|
|
25216
|
+
}
|
|
25217
|
+
const x = pos.x * this.uiData.dpr;
|
|
25218
|
+
const y = pos.y * this.uiData.dpr;
|
|
25219
|
+
this.mousePos = [x, y];
|
|
25220
|
+
this.drawScene();
|
|
24982
25221
|
}
|
|
24983
25222
|
}
|
|
24984
25223
|
// not included in public docs
|
|
@@ -25503,7 +25742,6 @@ var Niivue = class _Niivue {
|
|
|
25503
25742
|
this.loadDrawing(volume);
|
|
25504
25743
|
} else {
|
|
25505
25744
|
this.addVolume(volume);
|
|
25506
|
-
this.setDrawingEnabled(true);
|
|
25507
25745
|
}
|
|
25508
25746
|
}).catch((e2) => {
|
|
25509
25747
|
throw e2;
|
|
@@ -27437,6 +27675,23 @@ var Niivue = class _Niivue {
|
|
|
27437
27675
|
y = Math.min(Math.max(y, 0), dy - 1);
|
|
27438
27676
|
z = Math.min(Math.max(z, 0), dz - 1);
|
|
27439
27677
|
this.drawBitmap[x + y * dx + z * dx * dy] = penValue;
|
|
27678
|
+
const isAx = this.drawPenAxCorSag === 0;
|
|
27679
|
+
const isCor = this.drawPenAxCorSag === 1;
|
|
27680
|
+
const isSag = this.drawPenAxCorSag === 2;
|
|
27681
|
+
if (this.opts.penSize > 1) {
|
|
27682
|
+
const halfPenSize = Math.floor(this.opts.penSize / 2);
|
|
27683
|
+
for (let i = -halfPenSize; i <= halfPenSize; i++) {
|
|
27684
|
+
for (let j = -halfPenSize; j <= halfPenSize; j++) {
|
|
27685
|
+
if (isAx) {
|
|
27686
|
+
this.drawBitmap[x + i + (y + j) * dx + z * dx * dy] = penValue;
|
|
27687
|
+
} else if (isCor) {
|
|
27688
|
+
this.drawBitmap[x + i + y * dx + (z + j) * dx * dy] = penValue;
|
|
27689
|
+
} else if (isSag) {
|
|
27690
|
+
this.drawBitmap[x + (y + j) * dx + (z + i) * dx * dy] = penValue;
|
|
27691
|
+
}
|
|
27692
|
+
}
|
|
27693
|
+
}
|
|
27694
|
+
}
|
|
27440
27695
|
}
|
|
27441
27696
|
// not included in public docs
|
|
27442
27697
|
// create line between to voxels in drawing
|
|
@@ -27518,6 +27773,98 @@ var Niivue = class _Niivue {
|
|
|
27518
27773
|
}
|
|
27519
27774
|
}
|
|
27520
27775
|
}
|
|
27776
|
+
/**
|
|
27777
|
+
* Performs a 1-voxel binary dilation on a connected cluster within the drawing mask using the drawFloodFillCore function.
|
|
27778
|
+
*
|
|
27779
|
+
* @param seedXYZ - voxel index of the seed voxel in the mask array.
|
|
27780
|
+
* @param neighbors - Number of neighbors to consider for connectivity and dilation (6, 18, or 26).
|
|
27781
|
+
*/
|
|
27782
|
+
drawingBinaryDilationWithSeed(seedXYZ, neighbors = 6) {
|
|
27783
|
+
try {
|
|
27784
|
+
let xyz2vx = function(pt) {
|
|
27785
|
+
return pt[0] + pt[1] * nx + pt[2] * nxy;
|
|
27786
|
+
};
|
|
27787
|
+
const mask = this.drawBitmap;
|
|
27788
|
+
const xDim = this.back.dims[1];
|
|
27789
|
+
const yDim = this.back.dims[2];
|
|
27790
|
+
const zDim = this.back.dims[3];
|
|
27791
|
+
const nx = xDim;
|
|
27792
|
+
const nxy = xDim * yDim;
|
|
27793
|
+
const totalVoxels = nxy * zDim;
|
|
27794
|
+
const seedIndex = xyz2vx(seedXYZ);
|
|
27795
|
+
if (seedIndex < 0 || seedIndex >= totalVoxels) {
|
|
27796
|
+
throw new Error("Seed index is out of bounds.");
|
|
27797
|
+
}
|
|
27798
|
+
const seedValue = mask[seedIndex];
|
|
27799
|
+
if (seedValue === 0) {
|
|
27800
|
+
throw new Error("Seed voxel is not part of a filled cluster.");
|
|
27801
|
+
}
|
|
27802
|
+
const img = mask.slice();
|
|
27803
|
+
for (let i = 0; i < totalVoxels; i++) {
|
|
27804
|
+
img[i] = img[i] === seedValue ? 1 : 0;
|
|
27805
|
+
}
|
|
27806
|
+
this.drawFloodFillCore(img, seedIndex, neighbors);
|
|
27807
|
+
const outputMask = mask.slice();
|
|
27808
|
+
const neighborOffsets = [];
|
|
27809
|
+
const offsets6 = [-nxy, nxy, -xDim, xDim, -1, 1];
|
|
27810
|
+
neighborOffsets.push(...offsets6);
|
|
27811
|
+
if (neighbors > 6) {
|
|
27812
|
+
neighborOffsets.push(
|
|
27813
|
+
-xDim - 1,
|
|
27814
|
+
-xDim + 1,
|
|
27815
|
+
xDim - 1,
|
|
27816
|
+
xDim + 1,
|
|
27817
|
+
-nxy - xDim,
|
|
27818
|
+
-nxy + xDim,
|
|
27819
|
+
-nxy - 1,
|
|
27820
|
+
-nxy + 1,
|
|
27821
|
+
nxy - xDim,
|
|
27822
|
+
nxy + xDim,
|
|
27823
|
+
nxy - 1,
|
|
27824
|
+
nxy + 1
|
|
27825
|
+
);
|
|
27826
|
+
}
|
|
27827
|
+
if (neighbors > 18) {
|
|
27828
|
+
neighborOffsets.push(
|
|
27829
|
+
-nxy - xDim - 1,
|
|
27830
|
+
-nxy - xDim + 1,
|
|
27831
|
+
-nxy + xDim - 1,
|
|
27832
|
+
-nxy + xDim + 1,
|
|
27833
|
+
nxy - xDim - 1,
|
|
27834
|
+
nxy - xDim + 1,
|
|
27835
|
+
nxy + xDim - 1,
|
|
27836
|
+
nxy + xDim + 1
|
|
27837
|
+
);
|
|
27838
|
+
}
|
|
27839
|
+
for (let idx = 0; idx < totalVoxels; idx++) {
|
|
27840
|
+
if (img[idx] === 2) {
|
|
27841
|
+
const x = idx % xDim;
|
|
27842
|
+
const y = Math.floor(idx % nxy / xDim);
|
|
27843
|
+
const z = Math.floor(idx / nxy);
|
|
27844
|
+
for (const offset of neighborOffsets) {
|
|
27845
|
+
const neighborIdx = idx + offset;
|
|
27846
|
+
if (neighborIdx < 0 || neighborIdx >= totalVoxels) {
|
|
27847
|
+
continue;
|
|
27848
|
+
}
|
|
27849
|
+
const nx2 = neighborIdx % xDim;
|
|
27850
|
+
const ny = Math.floor(neighborIdx % nxy / xDim);
|
|
27851
|
+
const nz = Math.floor(neighborIdx / nxy);
|
|
27852
|
+
if (Math.abs(nx2 - x) > 1 || Math.abs(ny - y) > 1 || Math.abs(nz - z) > 1) {
|
|
27853
|
+
continue;
|
|
27854
|
+
}
|
|
27855
|
+
if (mask[neighborIdx] === 0) {
|
|
27856
|
+
outputMask[neighborIdx] = seedValue;
|
|
27857
|
+
}
|
|
27858
|
+
}
|
|
27859
|
+
}
|
|
27860
|
+
}
|
|
27861
|
+
this.drawBitmap = outputMask;
|
|
27862
|
+
this.drawAddUndoBitmap();
|
|
27863
|
+
this.refreshDrawing(true);
|
|
27864
|
+
} catch (error) {
|
|
27865
|
+
log.error("Error in drawingBinaryDilationWithSeed:", error);
|
|
27866
|
+
}
|
|
27867
|
+
}
|
|
27521
27868
|
// a voxel can be defined as having 6, 18 or 26 neighbors:
|
|
27522
27869
|
// 6: neighbors share faces (distance=1)
|
|
27523
27870
|
// 18: neighbors share faces (distance=1) or edges (1.4)
|
|
@@ -27599,7 +27946,7 @@ var Niivue = class _Niivue {
|
|
|
27599
27946
|
}
|
|
27600
27947
|
// not included in public docs
|
|
27601
27948
|
// set all connected voxels in drawing to new color
|
|
27602
|
-
drawFloodFill(seedXYZ, newColor = 0, growSelectedCluster = 0, forceMin = NaN, forceMax = NaN, neighbors = 6) {
|
|
27949
|
+
drawFloodFill(seedXYZ, newColor = 0, growSelectedCluster = 0, forceMin = NaN, forceMax = NaN, neighbors = 6, maxDistanceMM = Number.POSITIVE_INFINITY) {
|
|
27603
27950
|
if (!this.drawBitmap) {
|
|
27604
27951
|
throw new Error("drawBitmap undefined");
|
|
27605
27952
|
}
|
|
@@ -27621,9 +27968,25 @@ var Niivue = class _Niivue {
|
|
|
27621
27968
|
if (img.length !== nxy * dims[2]) {
|
|
27622
27969
|
return;
|
|
27623
27970
|
}
|
|
27971
|
+
function vx2xyz(vx) {
|
|
27972
|
+
const Z = Math.floor(vx / nxy);
|
|
27973
|
+
const Y = Math.floor((vx - Z * nxy) / nx);
|
|
27974
|
+
const X = Math.floor(vx % nx);
|
|
27975
|
+
return [X, Y, Z];
|
|
27976
|
+
}
|
|
27624
27977
|
function xyz2vx(pt) {
|
|
27625
27978
|
return pt[0] + pt[1] * nx + pt[2] * nxy;
|
|
27626
27979
|
}
|
|
27980
|
+
const vx2mm = (xyz) => {
|
|
27981
|
+
return this.vox2mm(xyz, this.back.matRAS);
|
|
27982
|
+
};
|
|
27983
|
+
const seedMM = vx2mm(seedXYZ);
|
|
27984
|
+
const maxDistanceMM2 = maxDistanceMM ** 2;
|
|
27985
|
+
function isWithinDistance(vx) {
|
|
27986
|
+
const xyzMM = vx2mm(vx2xyz(vx));
|
|
27987
|
+
const dist2 = (xyzMM[0] - seedMM[0]) ** 2 + (xyzMM[1] - seedMM[1]) ** 2 + (xyzMM[2] - seedMM[2]) ** 2;
|
|
27988
|
+
return dist2 <= maxDistanceMM2;
|
|
27989
|
+
}
|
|
27627
27990
|
const seedVx = xyz2vx(seedXYZ);
|
|
27628
27991
|
const seedColor = img[seedVx];
|
|
27629
27992
|
if (seedColor === newColor) {
|
|
@@ -27637,6 +28000,9 @@ var Niivue = class _Niivue {
|
|
|
27637
28000
|
for (let i = 1; i < nxyz; i++) {
|
|
27638
28001
|
img[i] = 0;
|
|
27639
28002
|
if (this.drawBitmap[i] === seedColor) {
|
|
28003
|
+
if (!isWithinDistance(i)) {
|
|
28004
|
+
continue;
|
|
28005
|
+
}
|
|
27640
28006
|
img[i] = 1;
|
|
27641
28007
|
}
|
|
27642
28008
|
}
|
|
@@ -27666,6 +28032,9 @@ var Niivue = class _Niivue {
|
|
|
27666
28032
|
for (let i = 1; i < nxyz; i++) {
|
|
27667
28033
|
img[i] = 0;
|
|
27668
28034
|
if (backImg[i] >= mn && backImg[i] <= mx) {
|
|
28035
|
+
if (!isWithinDistance(i)) {
|
|
28036
|
+
continue;
|
|
28037
|
+
}
|
|
27669
28038
|
img[i] = 1;
|
|
27670
28039
|
}
|
|
27671
28040
|
}
|
|
@@ -28287,6 +28656,8 @@ var Niivue = class _Niivue {
|
|
|
28287
28656
|
gl.bindVertexArray(this.unusedVAO);
|
|
28288
28657
|
this.rectShader = new Shader(gl, vertRectShader, fragRectShader);
|
|
28289
28658
|
this.rectShader.use(gl);
|
|
28659
|
+
this.rectOutlineShader = new Shader(gl, vertRectShader, fragRectOutlineShader);
|
|
28660
|
+
this.rectOutlineShader.use(gl);
|
|
28290
28661
|
this.lineShader = new Shader(gl, vertLineShader, fragRectShader);
|
|
28291
28662
|
this.lineShader.use(gl);
|
|
28292
28663
|
this.line3DShader = new Shader(gl, vertLine3DShader, fragRectShader);
|
|
@@ -28439,14 +28810,37 @@ var Niivue = class _Niivue {
|
|
|
28439
28810
|
}
|
|
28440
28811
|
/**
|
|
28441
28812
|
* basic statistics for selected voxel-based image
|
|
28442
|
-
* @param
|
|
28443
|
-
*
|
|
28444
|
-
*
|
|
28445
|
-
*
|
|
28813
|
+
* @param options - an object containing the following properties:
|
|
28814
|
+
* - layer: selects image to describe
|
|
28815
|
+
* - masks: optional binary images to filter voxels
|
|
28816
|
+
* - drawingIsMask: a boolean indicating if the drawing is used as a mask
|
|
28817
|
+
* - roiIsMask: a boolean indicating if the ROI is used as a mask
|
|
28818
|
+
* - startVox: the starting voxel coordinates
|
|
28819
|
+
* - endVox: the ending voxel coordinates
|
|
28820
|
+
* @returns numeric values to describe image or regions of images
|
|
28821
|
+
* @example
|
|
28822
|
+
* niivue.getDescriptives({
|
|
28823
|
+
* layer: 0,
|
|
28824
|
+
* masks: [],
|
|
28825
|
+
* drawingIsMask: true, // drawingIsMask and roiIsMask are mutually exclusive
|
|
28826
|
+
* roiIsMask: false,
|
|
28827
|
+
* startVox: [10, 20, 30], // ignored if roiIsMask is false
|
|
28828
|
+
* endVox: [40, 50, 60] // ignored if roiIsMask is false
|
|
28829
|
+
* });
|
|
28446
28830
|
* @see {@link https://niivue.github.io/niivue/features/draw2.html | live demo usage}
|
|
28447
28831
|
*/
|
|
28448
|
-
getDescriptives(
|
|
28832
|
+
getDescriptives(options) {
|
|
28833
|
+
const {
|
|
28834
|
+
layer = 0,
|
|
28835
|
+
masks = [],
|
|
28836
|
+
drawingIsMask = false,
|
|
28837
|
+
roiIsMask = false,
|
|
28838
|
+
startVox = [0, 0, 0],
|
|
28839
|
+
endVox = [0, 0, 0]
|
|
28840
|
+
} = options;
|
|
28841
|
+
let area = null;
|
|
28449
28842
|
const hdr = this.volumes[layer].hdr;
|
|
28843
|
+
const pixDimsRAS = this.volumes[layer].pixDimsRAS;
|
|
28450
28844
|
let slope = hdr.scl_slope;
|
|
28451
28845
|
if (isNaN(slope)) {
|
|
28452
28846
|
slope = 1;
|
|
@@ -28484,6 +28878,37 @@ var Niivue = class _Niivue {
|
|
|
28484
28878
|
mask[i] = 0;
|
|
28485
28879
|
}
|
|
28486
28880
|
}
|
|
28881
|
+
} else if (masks.length < 1 && roiIsMask) {
|
|
28882
|
+
mask.fill(0);
|
|
28883
|
+
console.log("startVox", startVox);
|
|
28884
|
+
console.log("endVox", endVox);
|
|
28885
|
+
const xrange = this.calculateMinMaxVoxIdx([startVox[0], endVox[0]]);
|
|
28886
|
+
const yrange = this.calculateMinMaxVoxIdx([startVox[1], endVox[1]]);
|
|
28887
|
+
const zrange = this.calculateMinMaxVoxIdx([startVox[2], endVox[2]]);
|
|
28888
|
+
if (startVox[0] - endVox[0] === 0) {
|
|
28889
|
+
xrange[1] = startVox[0] + 1;
|
|
28890
|
+
} else if (startVox[1] - endVox[1] === 0) {
|
|
28891
|
+
yrange[1] = startVox[1] + 1;
|
|
28892
|
+
} else if (startVox[2] - endVox[2] === 0) {
|
|
28893
|
+
zrange[1] = startVox[2] + 1;
|
|
28894
|
+
}
|
|
28895
|
+
const xdim = hdr.dims[1];
|
|
28896
|
+
const ydim = hdr.dims[2];
|
|
28897
|
+
for (let z = zrange[0]; z < zrange[1]; z++) {
|
|
28898
|
+
const zi = z * xdim * ydim;
|
|
28899
|
+
for (let y = yrange[0]; y < yrange[1]; y++) {
|
|
28900
|
+
const yi = y * xdim;
|
|
28901
|
+
for (let x = xrange[0]; x < xrange[1]; x++) {
|
|
28902
|
+
const index = zi + yi + x;
|
|
28903
|
+
mask[index] = 1;
|
|
28904
|
+
}
|
|
28905
|
+
}
|
|
28906
|
+
}
|
|
28907
|
+
const xmm = Math.abs(pixDimsRAS[1] * (endVox[0] - startVox[0]));
|
|
28908
|
+
const ymm = Math.abs(pixDimsRAS[2] * (endVox[1] - startVox[1]));
|
|
28909
|
+
const zmm = Math.abs(pixDimsRAS[3] * (endVox[2] - startVox[2]));
|
|
28910
|
+
const mms = [xmm, ymm, zmm];
|
|
28911
|
+
area = mms.filter((m) => m !== 0).reduce((a, b) => a * b, 1);
|
|
28487
28912
|
}
|
|
28488
28913
|
let k = 0;
|
|
28489
28914
|
let M = 0;
|
|
@@ -28537,7 +28962,8 @@ var Niivue = class _Niivue {
|
|
|
28537
28962
|
cal_min: this.volumes[layer].cal_min,
|
|
28538
28963
|
cal_max: this.volumes[layer].cal_max,
|
|
28539
28964
|
robust_min: this.volumes[layer].robust_min,
|
|
28540
|
-
robust_max: this.volumes[layer].robust_max
|
|
28965
|
+
robust_max: this.volumes[layer].robust_max,
|
|
28966
|
+
area
|
|
28541
28967
|
};
|
|
28542
28968
|
}
|
|
28543
28969
|
// not included in public docs
|
|
@@ -29728,6 +30154,7 @@ var Niivue = class _Niivue {
|
|
|
29728
30154
|
* @see {@link https://niivue.github.io/niivue/features/colormaps.html | live demo usage}
|
|
29729
30155
|
*/
|
|
29730
30156
|
setGamma(gamma = 1) {
|
|
30157
|
+
this.scene.gamma = gamma;
|
|
29731
30158
|
cmapper.gamma = gamma;
|
|
29732
30159
|
this.updateGLVolume();
|
|
29733
30160
|
}
|
|
@@ -30092,27 +30519,50 @@ var Niivue = class _Niivue {
|
|
|
30092
30519
|
if (this.opts.drawingEnabled) {
|
|
30093
30520
|
const pt = this.frac2vox(this.scene.crosshairPos);
|
|
30094
30521
|
if (this.opts.clickToSegment) {
|
|
30095
|
-
const
|
|
30096
|
-
|
|
30522
|
+
const voxelIntensity = this.back.getValue(pt[0], pt[1], pt[2]);
|
|
30523
|
+
if (this.opts.clickToSegmentPercent > 0 && this.opts.clickToSegmentPercent <= 1) {
|
|
30524
|
+
this.opts.clickToSegmentIntensityMax = voxelIntensity * (1 + this.opts.clickToSegmentPercent);
|
|
30525
|
+
this.opts.clickToSegmentIntensityMin = voxelIntensity * (1 - this.opts.clickToSegmentPercent);
|
|
30526
|
+
}
|
|
30527
|
+
if (this.opts.clickToSegmentAutoIntensity) {
|
|
30528
|
+
if (voxelIntensity > (this.back.cal_min + this.back.cal_max) * 0.5) {
|
|
30529
|
+
this.opts.clickToSegmentBright = true;
|
|
30530
|
+
} else {
|
|
30531
|
+
this.opts.clickToSegmentBright = false;
|
|
30532
|
+
}
|
|
30533
|
+
}
|
|
30097
30534
|
const brightOrDark = this.opts.clickToSegmentBright ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
|
|
30098
|
-
this.drawPenFillPts = [];
|
|
30099
30535
|
this.drawPenAxCorSag = axCorSag;
|
|
30100
|
-
|
|
30101
|
-
const
|
|
30102
|
-
|
|
30103
|
-
|
|
30104
|
-
|
|
30105
|
-
|
|
30106
|
-
|
|
30107
|
-
this.
|
|
30108
|
-
|
|
30109
|
-
|
|
30110
|
-
|
|
30111
|
-
|
|
30536
|
+
if (this.drawPenAxCorSag === 0 /* AXIAL */) {
|
|
30537
|
+
const pixDims = [this.back.pixDimsRAS[0], this.back.pixDimsRAS[1]];
|
|
30538
|
+
const minPixDim = Math.min(...pixDims);
|
|
30539
|
+
this.opts.penSize = Math.ceil(this.opts.clickToSegmentRadius / minPixDim);
|
|
30540
|
+
} else if (this.drawPenAxCorSag === 1 /* CORONAL */) {
|
|
30541
|
+
const pixDims = [this.back.pixDimsRAS[0], this.back.pixDimsRAS[2]];
|
|
30542
|
+
const minPixDim = Math.min(...pixDims);
|
|
30543
|
+
this.opts.penSize = Math.ceil(this.opts.clickToSegmentRadius / minPixDim);
|
|
30544
|
+
} else if (this.drawPenAxCorSag === 2 /* SAGITTAL */) {
|
|
30545
|
+
const pixDims = [this.back.pixDimsRAS[1], this.back.pixDimsRAS[2]];
|
|
30546
|
+
const minPixDim = Math.min(...pixDims);
|
|
30547
|
+
this.opts.penSize = Math.ceil(this.opts.clickToSegmentRadius / minPixDim);
|
|
30112
30548
|
}
|
|
30549
|
+
this.drawPt(pt[0], pt[1], pt[2], this.opts.penValue);
|
|
30550
|
+
this.drawFloodFill(
|
|
30551
|
+
[pt[0], pt[1], pt[2]],
|
|
30552
|
+
0,
|
|
30553
|
+
brightOrDark,
|
|
30554
|
+
this.opts.clickToSegmentIntensityMin,
|
|
30555
|
+
this.opts.clickToSegmentIntensityMax,
|
|
30556
|
+
this.opts.floodFillNeighbors,
|
|
30557
|
+
this.opts.clickToSegmentMaxDistanceMM
|
|
30558
|
+
);
|
|
30113
30559
|
this.drawScene();
|
|
30114
30560
|
this.createOnLocationChange(axCorSag);
|
|
30115
|
-
const info = this.getDescriptives(
|
|
30561
|
+
const info = this.getDescriptives({
|
|
30562
|
+
layer: 0,
|
|
30563
|
+
masks: [],
|
|
30564
|
+
drawingIsMask: true
|
|
30565
|
+
});
|
|
30116
30566
|
this.onClickToSegment({ mL: info.volumeML, mm3: info.volumeMM3 });
|
|
30117
30567
|
return;
|
|
30118
30568
|
}
|
|
@@ -30330,20 +30780,41 @@ var Niivue = class _Niivue {
|
|
|
30330
30780
|
if (!this.rectShader) {
|
|
30331
30781
|
throw new Error("rectShader undefined");
|
|
30332
30782
|
}
|
|
30333
|
-
this.
|
|
30334
|
-
|
|
30335
|
-
|
|
30336
|
-
|
|
30337
|
-
|
|
30338
|
-
this.
|
|
30339
|
-
|
|
30340
|
-
|
|
30341
|
-
|
|
30342
|
-
|
|
30343
|
-
|
|
30344
|
-
|
|
30345
|
-
|
|
30346
|
-
|
|
30783
|
+
if (!this.opts.selectionBoxIsOutline) {
|
|
30784
|
+
this.rectShader.use(this.gl);
|
|
30785
|
+
this.gl.enable(this.gl.BLEND);
|
|
30786
|
+
this.gl.uniform4fv(this.rectShader.uniforms.lineColor, lineColor);
|
|
30787
|
+
this.gl.uniform2fv(this.rectShader.uniforms.canvasWidthHeight, [this.gl.canvas.width, this.gl.canvas.height]);
|
|
30788
|
+
this.gl.uniform4f(
|
|
30789
|
+
this.rectShader.uniforms.leftTopWidthHeight,
|
|
30790
|
+
leftTopWidthHeight[0],
|
|
30791
|
+
leftTopWidthHeight[1],
|
|
30792
|
+
leftTopWidthHeight[2],
|
|
30793
|
+
leftTopWidthHeight[3]
|
|
30794
|
+
);
|
|
30795
|
+
this.gl.bindVertexArray(this.genericVAO);
|
|
30796
|
+
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
30797
|
+
this.gl.bindVertexArray(this.unusedVAO);
|
|
30798
|
+
} else {
|
|
30799
|
+
this.rectOutlineShader.use(this.gl);
|
|
30800
|
+
this.gl.enable(this.gl.BLEND);
|
|
30801
|
+
this.gl.uniform1f(this.rectOutlineShader.uniforms.thickness, this.opts.selectionBoxLineThickness);
|
|
30802
|
+
this.gl.uniform4fv(this.rectOutlineShader.uniforms.lineColor, lineColor);
|
|
30803
|
+
this.gl.uniform2fv(this.rectOutlineShader.uniforms.canvasWidthHeight, [
|
|
30804
|
+
this.gl.canvas.width,
|
|
30805
|
+
this.gl.canvas.height
|
|
30806
|
+
]);
|
|
30807
|
+
this.gl.uniform4f(
|
|
30808
|
+
this.rectOutlineShader.uniforms.leftTopWidthHeight,
|
|
30809
|
+
leftTopWidthHeight[0],
|
|
30810
|
+
leftTopWidthHeight[1],
|
|
30811
|
+
leftTopWidthHeight[2],
|
|
30812
|
+
leftTopWidthHeight[3]
|
|
30813
|
+
);
|
|
30814
|
+
this.gl.bindVertexArray(this.genericVAO);
|
|
30815
|
+
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
30816
|
+
this.gl.bindVertexArray(this.unusedVAO);
|
|
30817
|
+
}
|
|
30347
30818
|
}
|
|
30348
30819
|
drawCircle(leftTopWidthHeight, circleColor = this.opts.fontColor, fillPercent = 1) {
|
|
30349
30820
|
if (!this.circleShader) {
|
|
@@ -30387,9 +30858,21 @@ var Niivue = class _Niivue {
|
|
|
30387
30858
|
const labels = [...this.document.labels, ...definedMeshLabels];
|
|
30388
30859
|
return labels;
|
|
30389
30860
|
}
|
|
30861
|
+
getConnectomeLabels() {
|
|
30862
|
+
const connectomes = this.meshes.filter((m) => m.type === "connectome" /* CONNECTOME */);
|
|
30863
|
+
const meshNodes = connectomes.flatMap((m) => m.nodes);
|
|
30864
|
+
const meshLabels = meshNodes.map((n) => n.label);
|
|
30865
|
+
const definedMeshLabels = meshLabels.filter((l) => l !== void 0);
|
|
30866
|
+
const nonAnchoredLabels = this.document.labels.filter((l) => l.anchor == null || l.anchor === 0 /* NONE */);
|
|
30867
|
+
const nonAnchoredLabelSet = new Set(definedMeshLabels);
|
|
30868
|
+
for (const label of nonAnchoredLabels) {
|
|
30869
|
+
nonAnchoredLabelSet.add(label);
|
|
30870
|
+
}
|
|
30871
|
+
return Array.from(nonAnchoredLabelSet);
|
|
30872
|
+
}
|
|
30390
30873
|
getBulletMarginWidth() {
|
|
30391
30874
|
let bulletMargin = 0;
|
|
30392
|
-
const labels = this.
|
|
30875
|
+
const labels = this.getConnectomeLabels();
|
|
30393
30876
|
if (labels.length === 0) {
|
|
30394
30877
|
return 0;
|
|
30395
30878
|
}
|
|
@@ -30406,7 +30889,7 @@ var Niivue = class _Niivue {
|
|
|
30406
30889
|
return bulletMargin;
|
|
30407
30890
|
}
|
|
30408
30891
|
getLegendPanelWidth() {
|
|
30409
|
-
const labels = this.
|
|
30892
|
+
const labels = this.getConnectomeLabels();
|
|
30410
30893
|
if (!this.opts.showLegend || labels.length === 0) {
|
|
30411
30894
|
return 0;
|
|
30412
30895
|
}
|
|
@@ -30432,7 +30915,7 @@ var Niivue = class _Niivue {
|
|
|
30432
30915
|
return width;
|
|
30433
30916
|
}
|
|
30434
30917
|
getLegendPanelHeight() {
|
|
30435
|
-
const labels = this.
|
|
30918
|
+
const labels = this.getConnectomeLabels();
|
|
30436
30919
|
let height = 0;
|
|
30437
30920
|
const scale = 1;
|
|
30438
30921
|
const verticalMargin = this.opts.textHeight * this.gl.canvas.height * scale;
|
|
@@ -30874,7 +31357,7 @@ var Niivue = class _Niivue {
|
|
|
30874
31357
|
}
|
|
30875
31358
|
// not included in public docs
|
|
30876
31359
|
// show text labels for L/R, A/P, I/S dimensions
|
|
30877
|
-
drawSliceOrientationText(leftTopWidthHeight, axCorSag) {
|
|
31360
|
+
drawSliceOrientationText(leftTopWidthHeight, axCorSag, padLeftTop = [NaN, NaN]) {
|
|
30878
31361
|
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
|
|
30879
31362
|
let topText = "S";
|
|
30880
31363
|
if (axCorSag === 0 /* AXIAL */) {
|
|
@@ -30888,8 +31371,32 @@ var Niivue = class _Niivue {
|
|
|
30888
31371
|
this.drawTextRightBelow([leftTopWidthHeight[0], leftTopWidthHeight[1]], leftText + topText);
|
|
30889
31372
|
return;
|
|
30890
31373
|
}
|
|
30891
|
-
|
|
30892
|
-
|
|
31374
|
+
let drawBelow = true;
|
|
31375
|
+
let drawRight = true;
|
|
31376
|
+
if (!isNaN(padLeftTop[0])) {
|
|
31377
|
+
const ht = this.opts.textHeight * this.gl.canvas.height + 2;
|
|
31378
|
+
if (padLeftTop[1] > ht) {
|
|
31379
|
+
this.drawTextBelow(
|
|
31380
|
+
[leftTopWidthHeight[0] + leftTopWidthHeight[2] * 0.5, leftTopWidthHeight[1] + padLeftTop[1] - ht],
|
|
31381
|
+
topText
|
|
31382
|
+
);
|
|
31383
|
+
drawBelow = false;
|
|
31384
|
+
}
|
|
31385
|
+
const wid = this.textWidth(ht, leftText) + 2;
|
|
31386
|
+
if (padLeftTop[0] > wid) {
|
|
31387
|
+
this.drawTextRight(
|
|
31388
|
+
[leftTopWidthHeight[0] + padLeftTop[0] - wid, leftTopWidthHeight[1] + leftTopWidthHeight[3] * 0.5],
|
|
31389
|
+
leftText
|
|
31390
|
+
);
|
|
31391
|
+
drawRight = false;
|
|
31392
|
+
}
|
|
31393
|
+
}
|
|
31394
|
+
if (drawBelow) {
|
|
31395
|
+
this.drawTextBelow([leftTopWidthHeight[0] + leftTopWidthHeight[2] * 0.5, leftTopWidthHeight[1]], topText);
|
|
31396
|
+
}
|
|
31397
|
+
if (drawRight) {
|
|
31398
|
+
this.drawTextRight([leftTopWidthHeight[0], leftTopWidthHeight[1] + leftTopWidthHeight[3] * 0.5], leftText);
|
|
31399
|
+
}
|
|
30893
31400
|
}
|
|
30894
31401
|
// not included in public docs
|
|
30895
31402
|
xyMM2xyzMM(axCorSag, sliceFrac) {
|
|
@@ -30927,7 +31434,7 @@ var Niivue = class _Niivue {
|
|
|
30927
31434
|
}
|
|
30928
31435
|
// not included in public docs
|
|
30929
31436
|
// draw 2D tile
|
|
30930
|
-
|
|
31437
|
+
draw2DMain(leftTopWidthHeight, axCorSag, customMM = NaN) {
|
|
30931
31438
|
let frac2mmTexture = this.volumes[0].frac2mm.slice();
|
|
30932
31439
|
let screen = this.screenFieldOfViewExtendedMM(axCorSag);
|
|
30933
31440
|
let mesh2ortho = mat46.create();
|
|
@@ -30956,7 +31463,9 @@ var Niivue = class _Niivue {
|
|
|
30956
31463
|
elevation = isRadiolgical ? -90 : 90;
|
|
30957
31464
|
}
|
|
30958
31465
|
const gl = this.gl;
|
|
31466
|
+
let isStretchToScreen = false;
|
|
30959
31467
|
if (leftTopWidthHeight[2] === 0 || leftTopWidthHeight[3] === 0) {
|
|
31468
|
+
isStretchToScreen = true;
|
|
30960
31469
|
const pixPerMMw = gl.canvas.width / screen.fovMM[0];
|
|
30961
31470
|
const pixPerMMh = gl.canvas.height / screen.fovMM[1];
|
|
30962
31471
|
const pixPerMMmin = Math.min(pixPerMMw, pixPerMMh);
|
|
@@ -31103,9 +31612,32 @@ var Niivue = class _Niivue {
|
|
|
31103
31612
|
if (isNaN(customMM)) {
|
|
31104
31613
|
this.drawCrosshairs3D(false, 0.15, obj.modelViewProjectionMatrix, true, this.opts.isSliceMM);
|
|
31105
31614
|
}
|
|
31106
|
-
|
|
31615
|
+
if (isStretchToScreen) {
|
|
31616
|
+
this.drawSliceOrientationText(leftTopWidthHeight, axCorSag);
|
|
31617
|
+
}
|
|
31107
31618
|
this.readyForSync = true;
|
|
31108
31619
|
}
|
|
31620
|
+
draw2D(leftTopWidthHeight, axCorSag, customMM = NaN, imageWidthHeight = [NaN, NaN]) {
|
|
31621
|
+
const padLeftTop = [NaN, NaN];
|
|
31622
|
+
if (isNaN(imageWidthHeight[0])) {
|
|
31623
|
+
this.draw2DMain(leftTopWidthHeight, axCorSag, customMM);
|
|
31624
|
+
} else {
|
|
31625
|
+
const ltwh = leftTopWidthHeight.slice();
|
|
31626
|
+
padLeftTop[0] = Math.floor(0.5 * (ltwh[2] - imageWidthHeight[0]));
|
|
31627
|
+
padLeftTop[1] = Math.floor(0.5 * (ltwh[3] - imageWidthHeight[1]));
|
|
31628
|
+
ltwh[0] += padLeftTop[0];
|
|
31629
|
+
ltwh[1] += padLeftTop[1];
|
|
31630
|
+
ltwh[2] = imageWidthHeight[0];
|
|
31631
|
+
ltwh[3] = imageWidthHeight[1];
|
|
31632
|
+
this.draw2DMain(ltwh, axCorSag, customMM);
|
|
31633
|
+
}
|
|
31634
|
+
if (customMM === Infinity || customMM === -Infinity || axCorSag === 4 /* RENDER */) {
|
|
31635
|
+
return;
|
|
31636
|
+
}
|
|
31637
|
+
if (leftTopWidthHeight[2] !== 0 && leftTopWidthHeight[3] !== 0) {
|
|
31638
|
+
this.drawSliceOrientationText(leftTopWidthHeight, axCorSag, padLeftTop);
|
|
31639
|
+
}
|
|
31640
|
+
}
|
|
31109
31641
|
// not included in public docs
|
|
31110
31642
|
// determine 3D model view projection matrix
|
|
31111
31643
|
calculateMvpMatrix(_unused, leftTopWidthHeight = [0, 0, 0, 0], azimuth, elevation) {
|
|
@@ -31716,7 +32248,7 @@ var Niivue = class _Niivue {
|
|
|
31716
32248
|
* @param style - label style
|
|
31717
32249
|
* @param point - 3D point on the model
|
|
31718
32250
|
*/
|
|
31719
|
-
addLabel(text, style, points) {
|
|
32251
|
+
addLabel(text, style, points, anchor, onClick) {
|
|
31720
32252
|
const defaultStyle = {
|
|
31721
32253
|
textColor: this.opts.legendTextColor,
|
|
31722
32254
|
textScale: 1,
|
|
@@ -31728,7 +32260,7 @@ var Niivue = class _Niivue {
|
|
|
31728
32260
|
bulletColor: this.opts.legendTextColor
|
|
31729
32261
|
};
|
|
31730
32262
|
const labelStyle = style ? { ...defaultStyle, ...style } : { ...defaultStyle };
|
|
31731
|
-
const label = new NVLabel3D(text, labelStyle, points);
|
|
32263
|
+
const label = new NVLabel3D(text, { ...labelStyle }, points, anchor, onClick);
|
|
31732
32264
|
this.document.labels.push(label);
|
|
31733
32265
|
return label;
|
|
31734
32266
|
}
|
|
@@ -31746,6 +32278,60 @@ var Niivue = class _Niivue {
|
|
|
31746
32278
|
return screenPoint;
|
|
31747
32279
|
}
|
|
31748
32280
|
getLabelAtPoint(screenPoint) {
|
|
32281
|
+
const scale = 1;
|
|
32282
|
+
const size = this.opts.textHeight * Math.min(this.gl.canvas.height, this.gl.canvas.width) * scale;
|
|
32283
|
+
const verticalMargin = this.opts.textHeight * this.gl.canvas.height * scale;
|
|
32284
|
+
for (const label of this.document.labels) {
|
|
32285
|
+
if (label.anchor == null || label.anchor === 0 /* NONE */) {
|
|
32286
|
+
continue;
|
|
32287
|
+
}
|
|
32288
|
+
const labelSize = this.opts.textHeight * this.gl.canvas.height * label.style.textScale;
|
|
32289
|
+
const textHeight = this.textHeight(labelSize, label.text);
|
|
32290
|
+
const textWidth = this.textWidth(labelSize, label.text);
|
|
32291
|
+
if (label.anchor & 1 /* LEFT */) {
|
|
32292
|
+
if (screenPoint[0] > textWidth) {
|
|
32293
|
+
continue;
|
|
32294
|
+
}
|
|
32295
|
+
}
|
|
32296
|
+
if (label.anchor & 2 /* CENTER */) {
|
|
32297
|
+
if (screenPoint[0] < (this.gl.canvas.width - textWidth) / 2) {
|
|
32298
|
+
continue;
|
|
32299
|
+
}
|
|
32300
|
+
if (screenPoint[0] > (this.gl.canvas.width + textWidth) / 2) {
|
|
32301
|
+
continue;
|
|
32302
|
+
}
|
|
32303
|
+
}
|
|
32304
|
+
if (label.anchor & 4 /* RIGHT */) {
|
|
32305
|
+
if (screenPoint[0] < this.gl.canvas.width - textWidth) {
|
|
32306
|
+
continue;
|
|
32307
|
+
}
|
|
32308
|
+
}
|
|
32309
|
+
if (label.anchor & 8 /* TOP */) {
|
|
32310
|
+
if (screenPoint[1] < verticalMargin / 2) {
|
|
32311
|
+
continue;
|
|
32312
|
+
}
|
|
32313
|
+
if (screenPoint[1] > textHeight + verticalMargin / 2) {
|
|
32314
|
+
continue;
|
|
32315
|
+
}
|
|
32316
|
+
}
|
|
32317
|
+
if (label.anchor & 16 /* MIDDLE */) {
|
|
32318
|
+
if (screenPoint[1] < (this.gl.canvas.height - textHeight - verticalMargin) / 2) {
|
|
32319
|
+
continue;
|
|
32320
|
+
}
|
|
32321
|
+
if (screenPoint[1] > (this.gl.canvas.height + textHeight - verticalMargin / 2) / 2) {
|
|
32322
|
+
continue;
|
|
32323
|
+
}
|
|
32324
|
+
}
|
|
32325
|
+
if (label.anchor & 32 /* BOTTOM */) {
|
|
32326
|
+
if (screenPoint[1] < this.gl.canvas.height - textHeight - verticalMargin) {
|
|
32327
|
+
continue;
|
|
32328
|
+
}
|
|
32329
|
+
if (screenPoint[1] > this.gl.canvas.height - verticalMargin / 2) {
|
|
32330
|
+
continue;
|
|
32331
|
+
}
|
|
32332
|
+
}
|
|
32333
|
+
return label;
|
|
32334
|
+
}
|
|
31749
32335
|
log.debug("screenPoint", screenPoint);
|
|
31750
32336
|
const panelHeight = this.getLegendPanelHeight();
|
|
31751
32337
|
const panelWidth = this.getLegendPanelWidth();
|
|
@@ -31755,9 +32341,7 @@ var Niivue = class _Niivue {
|
|
|
31755
32341
|
if (screenPoint[0] < left || screenPoint[1] < top || screenPoint[0] > left + panelWidth || screenPoint[1] > top + panelHeight) {
|
|
31756
32342
|
return null;
|
|
31757
32343
|
}
|
|
31758
|
-
const
|
|
31759
|
-
const size = this.opts.textHeight * Math.min(this.gl.canvas.height, this.gl.canvas.width) * scale;
|
|
31760
|
-
const labels = this.getAllLabels();
|
|
32344
|
+
const labels = this.getConnectomeLabels();
|
|
31761
32345
|
for (const label of labels) {
|
|
31762
32346
|
const labelSize = this.opts.textHeight * this.gl.canvas.height * label.style.textScale;
|
|
31763
32347
|
const textHeight = this.textHeight(labelSize, label.text);
|
|
@@ -31786,7 +32370,7 @@ var Niivue = class _Niivue {
|
|
|
31786
32370
|
}
|
|
31787
32371
|
}
|
|
31788
32372
|
// not included in public docs
|
|
31789
|
-
draw3DLabel(label, pos, mvpMatrix, leftTopWidthHeight, bulletMargin
|
|
32373
|
+
draw3DLabel(label, pos, mvpMatrix, leftTopWidthHeight, bulletMargin, legendWidth, secondPass) {
|
|
31790
32374
|
const text = label.text;
|
|
31791
32375
|
const left = pos[0];
|
|
31792
32376
|
const top = pos[1];
|
|
@@ -31803,22 +32387,24 @@ var Niivue = class _Niivue {
|
|
|
31803
32387
|
this.drawCircle([rectLeft, rectTop, bulletSize, bulletSize], label.style.bulletColor);
|
|
31804
32388
|
}
|
|
31805
32389
|
let textLeft = left;
|
|
31806
|
-
if (
|
|
31807
|
-
|
|
31808
|
-
|
|
31809
|
-
|
|
32390
|
+
if (legendWidth) {
|
|
32391
|
+
if (label.style.textAlignment !== "left" /* LEFT */) {
|
|
32392
|
+
const textWidth = this.textWidth(label.style.textScale, label.text) * size;
|
|
32393
|
+
if (label.style.textAlignment === "right" /* RIGHT */) {
|
|
32394
|
+
textLeft = left + legendWidth - size * 1.5 - textWidth;
|
|
32395
|
+
} else {
|
|
32396
|
+
const remaining = legendWidth - (bulletMargin || size);
|
|
32397
|
+
textLeft += (remaining - textWidth) / 2;
|
|
32398
|
+
}
|
|
31810
32399
|
} else {
|
|
31811
|
-
|
|
31812
|
-
textLeft += (remaining - textWidth) / 2;
|
|
32400
|
+
textLeft += bulletMargin;
|
|
31813
32401
|
}
|
|
31814
|
-
} else {
|
|
31815
|
-
textLeft += bulletMargin;
|
|
31816
32402
|
}
|
|
31817
32403
|
this.drawText([textLeft, top], text, label.style.textScale, label.style.textColor);
|
|
31818
32404
|
}
|
|
31819
32405
|
// not included in public docs
|
|
31820
32406
|
draw3DLabels(mvpMatrix, leftTopWidthHeight, secondPass = false) {
|
|
31821
|
-
const labels = this.
|
|
32407
|
+
const labels = this.getConnectomeLabels();
|
|
31822
32408
|
if (!this.opts.showLegend || labels.length === 0) {
|
|
31823
32409
|
return;
|
|
31824
32410
|
}
|
|
@@ -31856,6 +32442,51 @@ var Niivue = class _Niivue {
|
|
|
31856
32442
|
}
|
|
31857
32443
|
}
|
|
31858
32444
|
}
|
|
32445
|
+
drawAnchoredLabels() {
|
|
32446
|
+
const size = this.opts.textHeight * Math.min(this.gl.canvas.height, this.gl.canvas.width) * 1;
|
|
32447
|
+
const anchoredLabels = this.document.labels.filter((l) => l.anchor != null && l.anchor !== 0 /* NONE */);
|
|
32448
|
+
for (const label of anchoredLabels) {
|
|
32449
|
+
const text = label.text;
|
|
32450
|
+
const textHeight = this.textHeight(label.style.textScale, text) * size;
|
|
32451
|
+
const textWidth = this.textWidth(label.style.textScale, text) * size;
|
|
32452
|
+
let left;
|
|
32453
|
+
let top;
|
|
32454
|
+
const scale = 1;
|
|
32455
|
+
const verticalMargin = this.opts.textHeight * this.gl.canvas.height * scale;
|
|
32456
|
+
const rectHeightDiff = verticalMargin;
|
|
32457
|
+
let rectWidthDiff = verticalMargin / 4;
|
|
32458
|
+
let rectHorizontalOffset = 0;
|
|
32459
|
+
let rectVerticalOffset = 0;
|
|
32460
|
+
if (label.anchor & 1 /* LEFT */) {
|
|
32461
|
+
left = 0;
|
|
32462
|
+
}
|
|
32463
|
+
if (label.anchor & 4 /* RIGHT */) {
|
|
32464
|
+
left = this.canvas.width - textWidth;
|
|
32465
|
+
rectHorizontalOffset -= verticalMargin / 4;
|
|
32466
|
+
}
|
|
32467
|
+
if (label.anchor & 2 /* CENTER */) {
|
|
32468
|
+
left = (this.canvas.width - textWidth) / 2;
|
|
32469
|
+
rectHorizontalOffset -= verticalMargin / 4;
|
|
32470
|
+
rectWidthDiff += verticalMargin / 4;
|
|
32471
|
+
}
|
|
32472
|
+
if (label.anchor & 8 /* TOP */) {
|
|
32473
|
+
top = 0;
|
|
32474
|
+
}
|
|
32475
|
+
if (label.anchor & 16 /* MIDDLE */) {
|
|
32476
|
+
top = (this.canvas.height - textHeight - verticalMargin) / 2;
|
|
32477
|
+
rectVerticalOffset -= verticalMargin / 4;
|
|
32478
|
+
}
|
|
32479
|
+
if (label.anchor & 32 /* BOTTOM */) {
|
|
32480
|
+
top = this.canvas.height - textHeight - verticalMargin;
|
|
32481
|
+
rectVerticalOffset -= verticalMargin / 4;
|
|
32482
|
+
}
|
|
32483
|
+
this.drawRect(
|
|
32484
|
+
[left + rectHorizontalOffset, top + rectVerticalOffset, textWidth + rectWidthDiff, textHeight + rectHeightDiff],
|
|
32485
|
+
label.style.backgroundColor
|
|
32486
|
+
);
|
|
32487
|
+
this.draw3DLabel(label, [left, top]);
|
|
32488
|
+
}
|
|
32489
|
+
}
|
|
31859
32490
|
// not included in public docs
|
|
31860
32491
|
draw3D(leftTopWidthHeight = [0, 0, 0, 0], mvpMatrix = null, modelMatrix = null, normalMatrix = null, azimuth = null, elevation = 0) {
|
|
31861
32492
|
const isMosaic = azimuth !== null;
|
|
@@ -32731,6 +33362,12 @@ var Niivue = class _Niivue {
|
|
|
32731
33362
|
}
|
|
32732
33363
|
const isDrawPenDown = isFinite(this.drawPenLocation[0]) && this.opts.drawingEnabled;
|
|
32733
33364
|
const { volScale } = this.sliceScale();
|
|
33365
|
+
const actualScale = volScale.slice();
|
|
33366
|
+
if (this.opts.multiplanarEqualSize) {
|
|
33367
|
+
volScale[0] = 1;
|
|
33368
|
+
volScale[1] = 1;
|
|
33369
|
+
volScale[2] = 1;
|
|
33370
|
+
}
|
|
32734
33371
|
if (typeof this.opts.multiplanarPadPixels !== "number") {
|
|
32735
33372
|
log.debug("multiplanarPadPixels must be numeric");
|
|
32736
33373
|
}
|
|
@@ -32768,38 +33405,43 @@ var Niivue = class _Niivue {
|
|
|
32768
33405
|
isDrawGrid = true;
|
|
32769
33406
|
}
|
|
32770
33407
|
}
|
|
33408
|
+
let ltwh = ltwh2x2;
|
|
32771
33409
|
if (isDrawColumn) {
|
|
32772
|
-
|
|
33410
|
+
ltwh = ltwh1x3;
|
|
32773
33411
|
if (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh1x4[4] >= ltwh1x3[4]) {
|
|
32774
33412
|
ltwh = ltwh1x4;
|
|
32775
33413
|
} else {
|
|
32776
33414
|
isDraw3D = false;
|
|
32777
33415
|
}
|
|
32778
|
-
const sX = volScale[0] * ltwh[4];
|
|
32779
|
-
const sY = volScale[1] * ltwh[4];
|
|
32780
|
-
const sZ = volScale[2] * ltwh[4];
|
|
32781
|
-
const sMx = mx * ltwh[4];
|
|
32782
|
-
this.draw2D([ltwh[0], ltwh[1], sX, sY], 0);
|
|
32783
|
-
this.draw2D([ltwh[0], ltwh[1] + sY + pad, sX, sZ], 1);
|
|
32784
|
-
this.draw2D([ltwh[0], ltwh[1] + sY + pad + sZ + pad, sY, sZ], 2);
|
|
32785
|
-
if (isDraw3D) {
|
|
32786
|
-
this.draw3D([ltwh[0], ltwh[1] + sY + sZ + sZ + pad * 3, sMx, sMx]);
|
|
32787
|
-
}
|
|
32788
33416
|
} else if (isDrawRow) {
|
|
32789
|
-
|
|
33417
|
+
ltwh = ltwh3x1;
|
|
32790
33418
|
if (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh4x1[4] >= ltwh3x1[4]) {
|
|
32791
33419
|
ltwh = ltwh4x1;
|
|
32792
33420
|
} else {
|
|
32793
33421
|
isDraw3D = false;
|
|
32794
33422
|
}
|
|
32795
|
-
|
|
32796
|
-
|
|
32797
|
-
|
|
32798
|
-
|
|
32799
|
-
|
|
32800
|
-
|
|
33423
|
+
}
|
|
33424
|
+
const sX = volScale[0] * ltwh[4];
|
|
33425
|
+
const sY = volScale[1] * ltwh[4];
|
|
33426
|
+
const sZ = volScale[2] * ltwh[4];
|
|
33427
|
+
const actualX = actualScale[0] * ltwh[4];
|
|
33428
|
+
const actualY = actualScale[1] * ltwh[4];
|
|
33429
|
+
const actualZ = actualScale[2] * ltwh[4];
|
|
33430
|
+
if (isDrawColumn) {
|
|
33431
|
+
this.draw2D([ltwh[0], ltwh[1], sX, sY], 0, NaN, [actualX, actualY]);
|
|
33432
|
+
this.draw2D([ltwh[0], ltwh[1] + sY + pad, sX, sZ], 1, NaN, [actualX, actualZ]);
|
|
33433
|
+
this.draw2D([ltwh[0], ltwh[1] + sY + pad + sZ + pad, sY, sZ], 2, NaN, [actualY, actualZ]);
|
|
32801
33434
|
if (isDraw3D) {
|
|
32802
|
-
|
|
33435
|
+
const sMx = mx * ltwh[4];
|
|
33436
|
+
this.draw3D([ltwh[0], ltwh[1] + sY + sZ + sZ + pad * 3, sMx, sMx]);
|
|
33437
|
+
}
|
|
33438
|
+
} else if (isDrawRow) {
|
|
33439
|
+
this.draw2D([ltwh[0], ltwh[1], sX, sY], 0, NaN, [actualX, actualY]);
|
|
33440
|
+
this.draw2D([ltwh[0] + sX + pad, ltwh[1], sX, sZ], 1, NaN, [actualX, actualZ]);
|
|
33441
|
+
this.draw2D([ltwh[0] + sX + sX + pad * 2, ltwh[1], sY, sZ], 2, NaN, [actualY, actualZ]);
|
|
33442
|
+
if (isDraw3D) {
|
|
33443
|
+
const sMx = mx * ltwh[4];
|
|
33444
|
+
this.draw3D([ltwh[0] + sX + sX + sY + pad * 3, ltwh[1], sMx, sMx]);
|
|
32803
33445
|
}
|
|
32804
33446
|
} else if (isDrawGrid) {
|
|
32805
33447
|
if (!isShowRender) {
|
|
@@ -32808,13 +33450,9 @@ var Niivue = class _Niivue {
|
|
|
32808
33450
|
if (this.opts.multiplanarShowRender === 2 /* AUTO */) {
|
|
32809
33451
|
isDraw3D = true;
|
|
32810
33452
|
}
|
|
32811
|
-
|
|
32812
|
-
|
|
32813
|
-
|
|
32814
|
-
const sZ = volScale[2] * ltwh[4];
|
|
32815
|
-
this.draw2D([ltwh[0], ltwh[1] + sZ + pad, sX, sY], 0);
|
|
32816
|
-
this.draw2D([ltwh[0], ltwh[1], sX, sZ], 1);
|
|
32817
|
-
this.draw2D([ltwh[0] + sX + pad, ltwh[1], sY, sZ], 2);
|
|
33453
|
+
this.draw2D([ltwh[0], ltwh[1] + sZ + pad, sX, sY], 0, NaN, [actualX, actualY]);
|
|
33454
|
+
this.draw2D([ltwh[0], ltwh[1], sX, sZ], 1, NaN, [actualX, actualZ]);
|
|
33455
|
+
this.draw2D([ltwh[0] + sX + pad, ltwh[1], sY, sZ], 2, NaN, [actualY, actualZ]);
|
|
32818
33456
|
if (isDraw3D) {
|
|
32819
33457
|
this.draw3D([ltwh[0] + sX + pad, ltwh[1] + sZ + pad, sY, sY]);
|
|
32820
33458
|
}
|
|
@@ -32878,11 +33516,30 @@ var Niivue = class _Niivue {
|
|
|
32878
33516
|
width,
|
|
32879
33517
|
height
|
|
32880
33518
|
]);
|
|
33519
|
+
return;
|
|
33520
|
+
}
|
|
33521
|
+
if (this.opts.clickToSegment) {
|
|
33522
|
+
const x = this.mousePos[0];
|
|
33523
|
+
const y = this.mousePos[1];
|
|
33524
|
+
if (this.inRenderTile(x, y) >= 0) {
|
|
33525
|
+
return;
|
|
33526
|
+
}
|
|
33527
|
+
const tileIdx = this.tileIndex(x, y);
|
|
33528
|
+
if (tileIdx > -1) {
|
|
33529
|
+
const fovMM = this.screenSlices[tileIdx].fovMM;
|
|
33530
|
+
const ltwh = this.screenSlices[tileIdx].leftTopWidthHeight;
|
|
33531
|
+
const pixPerMM = ltwh[2] / fovMM[0];
|
|
33532
|
+
const color = this.opts.crosshairColor;
|
|
33533
|
+
const segmentCursorColor = [color[0], color[1], color[2], 0.4];
|
|
33534
|
+
const radius = this.opts.clickToSegmentRadius * pixPerMM;
|
|
33535
|
+
this.drawCircle([x - radius, y - radius, radius * 2, radius * 2], segmentCursorColor, 1);
|
|
33536
|
+
}
|
|
32881
33537
|
}
|
|
32882
33538
|
const pos = this.frac2mm([this.scene.crosshairPos[0], this.scene.crosshairPos[1], this.scene.crosshairPos[2]]);
|
|
32883
33539
|
posString = pos[0].toFixed(2) + "\xD7" + pos[1].toFixed(2) + "\xD7" + pos[2].toFixed(2);
|
|
32884
33540
|
this.readyForSync = true;
|
|
32885
33541
|
this.sync();
|
|
33542
|
+
this.drawAnchoredLabels();
|
|
32886
33543
|
return posString;
|
|
32887
33544
|
}
|
|
32888
33545
|
// not included in public docs
|
|
@@ -32917,6 +33574,7 @@ export {
|
|
|
32917
33574
|
DEFAULT_OPTIONS,
|
|
32918
33575
|
DRAG_MODE,
|
|
32919
33576
|
INITIAL_SCENE_DATA,
|
|
33577
|
+
LabelAnchorPoint,
|
|
32920
33578
|
LabelLineTerminator,
|
|
32921
33579
|
LabelTextAlignment,
|
|
32922
33580
|
MULTIPLANAR_TYPE,
|