@niivue/niivue 0.44.0 → 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 +132 -18
- package/build/index.js +1088 -178
- package/build/index.js.map +1 -1
- package/build/index.min.js +1 -1
- package/dist/index.d.ts +132 -18
- package/dist/index.js +26585 -30810
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/niivue.umd.js +111 -32
- 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 +857 -137
- package/src/nvdocument.ts +36 -5
- package/src/nvimage/index.ts +29 -3
- package/src/nvimage/utils.ts +61 -42
- package/src/nvlabel.ts +34 -1
- package/src/nvmesh-loaders.ts +50 -6
- package/src/nvmesh-utilities.ts +139 -0
- package/src/nvmesh.ts +96 -0
- 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.
|
|
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;
|
|
@@ -11254,6 +11330,7 @@ var cmapper = new ColorTables();
|
|
|
11254
11330
|
|
|
11255
11331
|
// src/nvmesh-utilities.ts
|
|
11256
11332
|
import { vec3 as vec32 } from "gl-matrix";
|
|
11333
|
+
import { gzipSync } from "fflate/browser";
|
|
11257
11334
|
var NVMeshUtilities = class {
|
|
11258
11335
|
static getClusterBoundaryU8(u8, faces) {
|
|
11259
11336
|
const border = new Array(u8.length).fill(false);
|
|
@@ -11279,6 +11356,112 @@ var NVMeshUtilities = class {
|
|
|
11279
11356
|
}
|
|
11280
11357
|
return border;
|
|
11281
11358
|
}
|
|
11359
|
+
static createMZ3(vertices, indices, compress2 = false) {
|
|
11360
|
+
const magic = 23117;
|
|
11361
|
+
const attr = 3;
|
|
11362
|
+
const nface = indices.length / 3;
|
|
11363
|
+
const nvert = vertices.length / 3;
|
|
11364
|
+
const nskip = 0;
|
|
11365
|
+
const headerSize = 16;
|
|
11366
|
+
const indexSize = nface * 3 * 4;
|
|
11367
|
+
const vertexSize = nvert * 3 * 4;
|
|
11368
|
+
const totalSize = headerSize + indexSize + vertexSize;
|
|
11369
|
+
const buffer = new ArrayBuffer(totalSize);
|
|
11370
|
+
const writer = new DataView(buffer);
|
|
11371
|
+
writer.setUint16(0, magic, true);
|
|
11372
|
+
writer.setUint16(2, attr, true);
|
|
11373
|
+
writer.setUint32(4, nface, true);
|
|
11374
|
+
writer.setUint32(8, nvert, true);
|
|
11375
|
+
writer.setUint32(12, nskip, true);
|
|
11376
|
+
let offset = headerSize;
|
|
11377
|
+
new Uint32Array(buffer, offset, indices.length).set(indices);
|
|
11378
|
+
offset += indexSize;
|
|
11379
|
+
new Float32Array(buffer, offset, vertices.length).set(vertices);
|
|
11380
|
+
if (compress2) {
|
|
11381
|
+
return gzipSync(new Uint8Array(buffer));
|
|
11382
|
+
}
|
|
11383
|
+
return buffer;
|
|
11384
|
+
}
|
|
11385
|
+
static createOBJ(vertices, indices) {
|
|
11386
|
+
let objContent = "";
|
|
11387
|
+
for (let i = 0; i < vertices.length; i += 3) {
|
|
11388
|
+
objContent += `v ${vertices[i]} ${vertices[i + 1]} ${vertices[i + 2]}
|
|
11389
|
+
`;
|
|
11390
|
+
}
|
|
11391
|
+
for (let i = 0; i < indices.length; i += 3) {
|
|
11392
|
+
objContent += `f ${indices[i] + 1} ${indices[i + 1] + 1} ${indices[i + 2] + 1}
|
|
11393
|
+
`;
|
|
11394
|
+
}
|
|
11395
|
+
const encoder = new TextEncoder();
|
|
11396
|
+
const arrayBuffer = encoder.encode(objContent).buffer;
|
|
11397
|
+
return arrayBuffer;
|
|
11398
|
+
}
|
|
11399
|
+
static createSTL(vertices, indices) {
|
|
11400
|
+
const numTriangles = indices.length / 3;
|
|
11401
|
+
const bufferLength = 84 + numTriangles * 50;
|
|
11402
|
+
const arrayBuffer = new ArrayBuffer(bufferLength);
|
|
11403
|
+
const dataView = new DataView(arrayBuffer);
|
|
11404
|
+
for (let i = 0; i < 80; i++) {
|
|
11405
|
+
dataView.setUint8(i, 0);
|
|
11406
|
+
}
|
|
11407
|
+
dataView.setUint32(80, numTriangles, true);
|
|
11408
|
+
let offset = 84;
|
|
11409
|
+
for (let i = 0; i < indices.length; i += 3) {
|
|
11410
|
+
const i0 = indices[i] * 3;
|
|
11411
|
+
const i1 = indices[i + 1] * 3;
|
|
11412
|
+
const i2 = indices[i + 2] * 3;
|
|
11413
|
+
dataView.setFloat32(offset, 0, true);
|
|
11414
|
+
dataView.setFloat32(offset + 4, 0, true);
|
|
11415
|
+
dataView.setFloat32(offset + 8, 0, true);
|
|
11416
|
+
offset += 12;
|
|
11417
|
+
dataView.setFloat32(offset, vertices[i0], true);
|
|
11418
|
+
dataView.setFloat32(offset + 4, vertices[i0 + 1], true);
|
|
11419
|
+
dataView.setFloat32(offset + 8, vertices[i0 + 2], true);
|
|
11420
|
+
offset += 12;
|
|
11421
|
+
dataView.setFloat32(offset, vertices[i1], true);
|
|
11422
|
+
dataView.setFloat32(offset + 4, vertices[i1 + 1], true);
|
|
11423
|
+
dataView.setFloat32(offset + 8, vertices[i1 + 2], true);
|
|
11424
|
+
offset += 12;
|
|
11425
|
+
dataView.setFloat32(offset, vertices[i2], true);
|
|
11426
|
+
dataView.setFloat32(offset + 4, vertices[i2 + 1], true);
|
|
11427
|
+
dataView.setFloat32(offset + 8, vertices[i2 + 2], true);
|
|
11428
|
+
offset += 12;
|
|
11429
|
+
dataView.setUint16(offset, 0, true);
|
|
11430
|
+
offset += 2;
|
|
11431
|
+
}
|
|
11432
|
+
return arrayBuffer;
|
|
11433
|
+
}
|
|
11434
|
+
static downloadArrayBuffer(buffer, filename) {
|
|
11435
|
+
const blob = new Blob([buffer], { type: "application/octet-stream" });
|
|
11436
|
+
const url = URL.createObjectURL(blob);
|
|
11437
|
+
const a = document.createElement("a");
|
|
11438
|
+
a.href = url;
|
|
11439
|
+
a.download = filename;
|
|
11440
|
+
document.body.appendChild(a);
|
|
11441
|
+
a.style.display = "none";
|
|
11442
|
+
a.click();
|
|
11443
|
+
setTimeout(() => {
|
|
11444
|
+
document.body.removeChild(a);
|
|
11445
|
+
URL.revokeObjectURL(url);
|
|
11446
|
+
}, 0);
|
|
11447
|
+
}
|
|
11448
|
+
static saveMesh(vertices, indices, filename = ".mz3", compress2 = false) {
|
|
11449
|
+
let buff = new ArrayBuffer(0);
|
|
11450
|
+
if (/\.obj$/i.test(filename)) {
|
|
11451
|
+
buff = this.createOBJ(vertices, indices);
|
|
11452
|
+
} else if (/\.stl$/i.test(filename)) {
|
|
11453
|
+
buff = this.createSTL(vertices, indices);
|
|
11454
|
+
} else {
|
|
11455
|
+
if (!/\.mz3$/i.test(filename)) {
|
|
11456
|
+
filename += ".mz3";
|
|
11457
|
+
}
|
|
11458
|
+
buff = this.createMZ3(vertices, indices, compress2);
|
|
11459
|
+
}
|
|
11460
|
+
if (filename.length > 4) {
|
|
11461
|
+
this.downloadArrayBuffer(buff, filename);
|
|
11462
|
+
}
|
|
11463
|
+
return buff;
|
|
11464
|
+
}
|
|
11282
11465
|
static getClusterBoundary(rgba8, faces) {
|
|
11283
11466
|
const rgba32 = new Uint32Array(rgba8.buffer);
|
|
11284
11467
|
const border = new Array(rgba32.length).fill(false);
|
|
@@ -12250,6 +12433,12 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
12250
12433
|
while (lines[pos].length < 1) {
|
|
12251
12434
|
pos++;
|
|
12252
12435
|
}
|
|
12436
|
+
if (lines[pos].startsWith("METADATA")) {
|
|
12437
|
+
while (lines[pos].length > 1) {
|
|
12438
|
+
pos++;
|
|
12439
|
+
}
|
|
12440
|
+
pos++;
|
|
12441
|
+
}
|
|
12253
12442
|
items = lines[pos].trim().split(/\s+/);
|
|
12254
12443
|
pos++;
|
|
12255
12444
|
if (items[0].includes("LINES")) {
|
|
@@ -12428,7 +12617,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
12428
12617
|
layer.values = obj;
|
|
12429
12618
|
}
|
|
12430
12619
|
}
|
|
12431
|
-
} else if (ext === "CRV" || ext === "CURV") {
|
|
12620
|
+
} else if (ext === "CRV" || ext === "CURV" || ext === "THICKNESS" || ext === "AREA") {
|
|
12432
12621
|
layer.values = _NVMeshLoaders.readCURV(buffer, n_vert);
|
|
12433
12622
|
layer.isTransparentBelowCalMin = false;
|
|
12434
12623
|
} else if (ext === "GII") {
|
|
@@ -12453,6 +12642,9 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
12453
12642
|
layer.values = _NVMeshLoaders.readSMP(buffer, n_vert);
|
|
12454
12643
|
} else if (ext === "STC") {
|
|
12455
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;
|
|
12456
12648
|
} else {
|
|
12457
12649
|
log.warn("Unknown layer overlay format " + name);
|
|
12458
12650
|
return layer;
|
|
@@ -12626,6 +12818,17 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
12626
12818
|
return f32;
|
|
12627
12819
|
}
|
|
12628
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
|
+
}
|
|
12629
12832
|
// read freesurfer curv big-endian format
|
|
12630
12833
|
// https://github.com/bonilhamusclab/MRIcroS/blob/master/%2BfileUtils/%2Bpial/readPial.m
|
|
12631
12834
|
// http://www.grahamwideman.com/gw/brain/fs/surfacefileformats.htm
|
|
@@ -12669,7 +12872,8 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
12669
12872
|
static readANNOT(buffer, n_vert, isReadColortables = false) {
|
|
12670
12873
|
const view = new DataView(buffer);
|
|
12671
12874
|
const n_vertex = view.getUint32(0, false);
|
|
12672
|
-
|
|
12875
|
+
const n_vertexDecimated = this.decimateLayerVertices(n_vertex, n_vert);
|
|
12876
|
+
if (n_vert !== n_vertexDecimated) {
|
|
12673
12877
|
throw new Error("ANNOT file has different number of vertices than mesh");
|
|
12674
12878
|
}
|
|
12675
12879
|
if (buffer.byteLength < 4 + 8 * n_vertex) {
|
|
@@ -13917,6 +14121,20 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
13917
14121
|
};
|
|
13918
14122
|
}
|
|
13919
14123
|
// readSTL()
|
|
14124
|
+
static decimateLayerVertices(nVertLayer, nVertMesh) {
|
|
14125
|
+
if (nVertLayer % nVertMesh === 0) {
|
|
14126
|
+
return nVertLayer;
|
|
14127
|
+
}
|
|
14128
|
+
const V0 = 12;
|
|
14129
|
+
const orderLayer = Math.round(Math.log((nVertLayer - 2) / (V0 - 2)) / Math.log(4));
|
|
14130
|
+
const orderMesh = Math.round(Math.log((nVertMesh - 2) / (V0 - 2)) / Math.log(4));
|
|
14131
|
+
const nVLayer = Math.pow(4, orderLayer) * (V0 - 2) + 2;
|
|
14132
|
+
const nVMesh = Math.pow(4, orderMesh) * (V0 - 2) + 2;
|
|
14133
|
+
if (nVLayer !== nVertLayer || nVMesh !== nVertMesh) {
|
|
14134
|
+
return nVertLayer;
|
|
14135
|
+
}
|
|
14136
|
+
return nVertMesh;
|
|
14137
|
+
}
|
|
13920
14138
|
// read NIfTI2 format with embedded CIfTI
|
|
13921
14139
|
// this variation very specific to connectome workbench
|
|
13922
14140
|
// https://brainder.org/2015/04/03/the-nifti-2-file-format/
|
|
@@ -14077,8 +14295,9 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
14077
14295
|
);
|
|
14078
14296
|
return scalars2;
|
|
14079
14297
|
}
|
|
14298
|
+
nvert = this.decimateLayerVertices(nvert, n_vert);
|
|
14080
14299
|
if (nvert % n_vert !== 0) {
|
|
14081
|
-
throw new Error("Vertices in
|
|
14300
|
+
throw new Error("Vertices in layer (" + nvert + ") is not a multiple of number of vertices (" + n_vert + ")");
|
|
14082
14301
|
}
|
|
14083
14302
|
if (isLittleEndian) {
|
|
14084
14303
|
if (datatype === 16) {
|
|
@@ -14157,8 +14376,9 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
14157
14376
|
const dim = reader.getUint16(40 + i * 2, isLittleEndian);
|
|
14158
14377
|
nvert *= Math.max(dim, 1);
|
|
14159
14378
|
}
|
|
14379
|
+
nvert = this.decimateLayerVertices(nvert, n_vert);
|
|
14160
14380
|
if (nvert % n_vert !== 0) {
|
|
14161
|
-
throw new Error("Vertices in
|
|
14381
|
+
throw new Error("Vertices in layer (" + nvert + ") is not a multiple of number of vertices (" + n_vert + ")");
|
|
14162
14382
|
}
|
|
14163
14383
|
if (isLittleEndian) {
|
|
14164
14384
|
if (datatype === 16) {
|
|
@@ -14214,10 +14434,11 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
14214
14434
|
if (version2 !== 1 || mtype < 0 || mtype > 4) {
|
|
14215
14435
|
log.warn("Not a valid MGH file");
|
|
14216
14436
|
}
|
|
14217
|
-
|
|
14437
|
+
let nvert = width * height * depth * nframes;
|
|
14218
14438
|
let scalars = [];
|
|
14439
|
+
nvert = this.decimateLayerVertices(nvert, n_vert);
|
|
14219
14440
|
if (nvert % n_vert !== 0) {
|
|
14220
|
-
log.warn("Vertices in
|
|
14441
|
+
log.warn("Vertices in layer (" + nvert + ") is not a multiple of number of vertices (" + n_vert + ")");
|
|
14221
14442
|
return scalars;
|
|
14222
14443
|
}
|
|
14223
14444
|
if (mtype === 3) {
|
|
@@ -15961,6 +16182,91 @@ var NVMesh2 = class _NVMesh {
|
|
|
15961
16182
|
}
|
|
15962
16183
|
this.updateMesh(gl);
|
|
15963
16184
|
}
|
|
16185
|
+
hierarchicalOrder() {
|
|
16186
|
+
const V0 = 12;
|
|
16187
|
+
const F0 = 20;
|
|
16188
|
+
const nF = this.tris.length / 3;
|
|
16189
|
+
const order = Math.log(nF / F0) / Math.log(4);
|
|
16190
|
+
if (nF !== Math.pow(4, order) * F0) {
|
|
16191
|
+
return NaN;
|
|
16192
|
+
}
|
|
16193
|
+
const nV = this.pts.length / 3;
|
|
16194
|
+
if (nV !== Math.pow(4, order) * (V0 - 2) + 2) {
|
|
16195
|
+
return NaN;
|
|
16196
|
+
}
|
|
16197
|
+
for (let i = 0; i < 15; i += 3) {
|
|
16198
|
+
if (this.tris[i] !== 0) {
|
|
16199
|
+
return NaN;
|
|
16200
|
+
}
|
|
16201
|
+
}
|
|
16202
|
+
for (let i = 15; i < 24; i += 3) {
|
|
16203
|
+
if (this.tris[i] !== 3) {
|
|
16204
|
+
return NaN;
|
|
16205
|
+
}
|
|
16206
|
+
}
|
|
16207
|
+
for (let i = 24; i < 30; i += 3) {
|
|
16208
|
+
if (this.tris[i] !== 4) {
|
|
16209
|
+
return NaN;
|
|
16210
|
+
}
|
|
16211
|
+
}
|
|
16212
|
+
return order;
|
|
16213
|
+
}
|
|
16214
|
+
decimateFaces(n, ntarget) {
|
|
16215
|
+
let fac = this.tris;
|
|
16216
|
+
const V0 = 12;
|
|
16217
|
+
const F0 = 20;
|
|
16218
|
+
for (let j = n - 1; j >= ntarget; j--) {
|
|
16219
|
+
const nVjprev = Math.pow(4, j + 1) * (V0 - 2) + 2;
|
|
16220
|
+
const nVj = Math.pow(4, j) * (V0 - 2) + 2;
|
|
16221
|
+
const nFjprev = fac.length / 3;
|
|
16222
|
+
const nFj = Math.pow(4, j) * F0;
|
|
16223
|
+
console.log(`order ${j + 1} -> ${j} vertices ${nVjprev} -> ${nVj} faces ${nFjprev} -> ${nFj}`);
|
|
16224
|
+
const remap = Array.from({ length: nVjprev }, (_, i) => i + 1);
|
|
16225
|
+
for (let i = 0; i < nFjprev; i++) {
|
|
16226
|
+
const v1 = fac[3 * i];
|
|
16227
|
+
const v2 = fac[3 * i + 1];
|
|
16228
|
+
const v3 = fac[3 * i + 2];
|
|
16229
|
+
remap[v1 - 1] = Math.min(remap[v1 - 1], v2, v3);
|
|
16230
|
+
}
|
|
16231
|
+
const facJ = new Uint32Array(nFj * 3);
|
|
16232
|
+
for (let i = 0; i < nFj; i++) {
|
|
16233
|
+
facJ[3 * i] = remap[fac[3 * i] - 1];
|
|
16234
|
+
facJ[3 * i + 1] = remap[fac[3 * i + 1] - 1];
|
|
16235
|
+
facJ[3 * i + 2] = remap[fac[3 * i + 2] - 1];
|
|
16236
|
+
}
|
|
16237
|
+
fac = facJ;
|
|
16238
|
+
}
|
|
16239
|
+
this.tris = new Uint32Array(fac);
|
|
16240
|
+
}
|
|
16241
|
+
// internal function simplifies FreeSurfer triangulated mesh and overlays
|
|
16242
|
+
decimateHierarchicalMesh(gl, order = 4) {
|
|
16243
|
+
const inputOrder = this.hierarchicalOrder();
|
|
16244
|
+
if (isNaN(inputOrder)) {
|
|
16245
|
+
log.warn("Unable to decimate mesh: it does not have a hierarchical structure");
|
|
16246
|
+
return false;
|
|
16247
|
+
}
|
|
16248
|
+
if (order >= inputOrder) {
|
|
16249
|
+
log.warn(`Unable to decimate mesh: input order (${inputOrder}) must be larger than downsampled order (${order})`);
|
|
16250
|
+
return false;
|
|
16251
|
+
}
|
|
16252
|
+
const inputVLength = this.pts.length / 3;
|
|
16253
|
+
const V0 = 12;
|
|
16254
|
+
const nV = Math.pow(4, order) * (V0 - 2) + 2;
|
|
16255
|
+
this.pts = new Float32Array(this.pts.slice(0, nV * 3));
|
|
16256
|
+
this.decimateFaces(inputOrder, order);
|
|
16257
|
+
if (this.layers && this.layers.length > 0) {
|
|
16258
|
+
for (let i = 0; i < this.layers.length; i++) {
|
|
16259
|
+
const layer = this.layers[i];
|
|
16260
|
+
if (layer.values instanceof Float32Array || layer.values.length !== inputVLength) {
|
|
16261
|
+
layer.values = new Float32Array(layer.values.slice(0, nV));
|
|
16262
|
+
} else {
|
|
16263
|
+
log.warn(`decimation logic needs to be updated`);
|
|
16264
|
+
}
|
|
16265
|
+
}
|
|
16266
|
+
}
|
|
16267
|
+
this.updateMesh(gl);
|
|
16268
|
+
return true;
|
|
16269
|
+
}
|
|
15964
16270
|
// adjust attributes of a mesh layer. invoked by niivue.setMeshLayerProperty()
|
|
15965
16271
|
// TODO this method is a bit too generic
|
|
15966
16272
|
setLayerProperty(id, key, val, gl) {
|
|
@@ -18016,7 +18322,7 @@ import { vec3 as vec38, vec4 as vec46 } from "gl-matrix";
|
|
|
18016
18322
|
import * as nifti from "nifti-reader-js";
|
|
18017
18323
|
import daikon from "daikon";
|
|
18018
18324
|
import { mat3, mat4 as mat44, vec3 as vec37, vec4 as vec45 } from "gl-matrix";
|
|
18019
|
-
import { Decompress, decompressSync as decompressSync3, gzipSync } from "fflate/browser";
|
|
18325
|
+
import { Decompress, decompressSync as decompressSync3, gzipSync as gzipSync2 } from "fflate/browser";
|
|
18020
18326
|
import { v4 as uuidv42 } from "@lukeed/uuid";
|
|
18021
18327
|
|
|
18022
18328
|
// src/nvimage/utils.ts
|
|
@@ -18159,76 +18465,90 @@ function getBestTransform(imageDirections, voxelDimensions, imagePosition) {
|
|
|
18159
18465
|
}
|
|
18160
18466
|
return m;
|
|
18161
18467
|
}
|
|
18162
|
-
function str2Buffer(str) {
|
|
18468
|
+
function str2Buffer(str, maxLen = 80) {
|
|
18469
|
+
str = str.replace(/[`$]/g, "");
|
|
18163
18470
|
const bytes = [];
|
|
18164
|
-
|
|
18471
|
+
const len = Math.min(maxLen, str.length);
|
|
18472
|
+
for (let i = 0; i < len; i++) {
|
|
18165
18473
|
const char = str.charCodeAt(i);
|
|
18166
18474
|
bytes.push(char & 255);
|
|
18167
18475
|
}
|
|
18168
18476
|
return bytes;
|
|
18169
18477
|
}
|
|
18170
|
-
function hdrToArrayBuffer(hdr, isDrawing8 = false) {
|
|
18478
|
+
function hdrToArrayBuffer(hdr, isDrawing8 = false, isInputEndian = false) {
|
|
18171
18479
|
const SHORT_SIZE = 2;
|
|
18172
18480
|
const FLOAT32_SIZE = 4;
|
|
18481
|
+
let isLittleEndian = true;
|
|
18482
|
+
if (isInputEndian) {
|
|
18483
|
+
isLittleEndian = hdr.littleEndian;
|
|
18484
|
+
}
|
|
18173
18485
|
const byteArray = new Uint8Array(348);
|
|
18174
18486
|
const view = new DataView(byteArray.buffer);
|
|
18175
|
-
view.setInt32(0, 348,
|
|
18487
|
+
view.setInt32(0, 348, isLittleEndian);
|
|
18488
|
+
view.setUint8(38, 114);
|
|
18176
18489
|
view.setUint8(39, hdr.dim_info);
|
|
18177
18490
|
for (let i = 0; i < 8; i++) {
|
|
18178
|
-
view.setUint16(40 + SHORT_SIZE * i, hdr.dims[i],
|
|
18491
|
+
view.setUint16(40 + SHORT_SIZE * i, hdr.dims[i], isLittleEndian);
|
|
18179
18492
|
}
|
|
18180
|
-
view.setFloat32(56, hdr.intent_p1,
|
|
18181
|
-
view.setFloat32(60, hdr.intent_p2,
|
|
18182
|
-
view.setFloat32(64, hdr.intent_p3,
|
|
18183
|
-
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);
|
|
18184
18497
|
if (isDrawing8) {
|
|
18185
|
-
view.setInt16(70, 2,
|
|
18186
|
-
view.setInt16(72, 8,
|
|
18498
|
+
view.setInt16(70, 2, isLittleEndian);
|
|
18499
|
+
view.setInt16(72, 8, isLittleEndian);
|
|
18187
18500
|
} else {
|
|
18188
|
-
view.setInt16(70, hdr.datatypeCode,
|
|
18189
|
-
view.setInt16(72, hdr.numBitsPerVoxel,
|
|
18501
|
+
view.setInt16(70, hdr.datatypeCode, isLittleEndian);
|
|
18502
|
+
view.setInt16(72, hdr.numBitsPerVoxel, isLittleEndian);
|
|
18190
18503
|
}
|
|
18191
|
-
view.setInt16(74, hdr.slice_start,
|
|
18504
|
+
view.setInt16(74, hdr.slice_start, isLittleEndian);
|
|
18192
18505
|
for (let i = 0; i < 8; i++) {
|
|
18193
|
-
view.setFloat32(76 + FLOAT32_SIZE * i, hdr.pixDims[i],
|
|
18506
|
+
view.setFloat32(76 + FLOAT32_SIZE * i, hdr.pixDims[i], isLittleEndian);
|
|
18194
18507
|
}
|
|
18195
18508
|
if (isDrawing8) {
|
|
18196
|
-
view.setFloat32(108, 352,
|
|
18197
|
-
view.setFloat32(112, 1,
|
|
18198
|
-
view.setFloat32(116, 0,
|
|
18509
|
+
view.setFloat32(108, 352, isLittleEndian);
|
|
18510
|
+
view.setFloat32(112, 1, isLittleEndian);
|
|
18511
|
+
view.setFloat32(116, 0, isLittleEndian);
|
|
18199
18512
|
} else {
|
|
18200
|
-
view.setFloat32(108,
|
|
18201
|
-
view.setFloat32(112, hdr.scl_slope,
|
|
18202
|
-
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);
|
|
18203
18516
|
}
|
|
18204
|
-
view.setInt16(120, hdr.slice_end,
|
|
18517
|
+
view.setInt16(120, hdr.slice_end, isLittleEndian);
|
|
18205
18518
|
view.setUint8(122, hdr.slice_code);
|
|
18206
|
-
|
|
18519
|
+
if (hdr.xyzt_units === 0) {
|
|
18520
|
+
view.setUint8(123, 10);
|
|
18521
|
+
} else {
|
|
18522
|
+
view.setUint8(123, hdr.xyzt_units);
|
|
18523
|
+
}
|
|
18207
18524
|
if (isDrawing8) {
|
|
18208
|
-
view.setFloat32(124, 0,
|
|
18209
|
-
view.setFloat32(128, 0,
|
|
18525
|
+
view.setFloat32(124, 0, isLittleEndian);
|
|
18526
|
+
view.setFloat32(128, 0, isLittleEndian);
|
|
18210
18527
|
} else {
|
|
18211
|
-
view.setFloat32(124, hdr.cal_max,
|
|
18212
|
-
view.setFloat32(128, hdr.cal_min,
|
|
18528
|
+
view.setFloat32(124, hdr.cal_max, isLittleEndian);
|
|
18529
|
+
view.setFloat32(128, hdr.cal_min, isLittleEndian);
|
|
18213
18530
|
}
|
|
18214
|
-
view.setFloat32(132, hdr.slice_duration,
|
|
18215
|
-
view.setFloat32(136, hdr.toffset,
|
|
18531
|
+
view.setFloat32(132, hdr.slice_duration, isLittleEndian);
|
|
18532
|
+
view.setFloat32(136, hdr.toffset, isLittleEndian);
|
|
18216
18533
|
byteArray.set(str2Buffer(hdr.description), 148);
|
|
18217
18534
|
byteArray.set(str2Buffer(hdr.aux_file), 228);
|
|
18218
|
-
view.setInt16(252, hdr.qform_code,
|
|
18219
|
-
|
|
18220
|
-
|
|
18221
|
-
|
|
18222
|
-
|
|
18223
|
-
|
|
18224
|
-
view.setFloat32(
|
|
18225
|
-
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);
|
|
18226
18547
|
const flattened = hdr.affine.flat();
|
|
18227
18548
|
for (let i = 0; i < 12; i++) {
|
|
18228
|
-
view.setFloat32(280 + FLOAT32_SIZE * i, flattened[i],
|
|
18549
|
+
view.setFloat32(280 + FLOAT32_SIZE * i, flattened[i], isLittleEndian);
|
|
18229
18550
|
}
|
|
18230
|
-
|
|
18231
|
-
byteArray.set(str2Buffer(hdr.magic), 344);
|
|
18551
|
+
view.setInt32(344, 3222382, true);
|
|
18232
18552
|
return byteArray;
|
|
18233
18553
|
}
|
|
18234
18554
|
function getExtents(positions, forceOriginInVolume = true) {
|
|
@@ -19772,7 +20092,7 @@ var NVImage = class _NVImage {
|
|
|
19772
20092
|
hdr.datatypeCode = 64 /* DT_FLOAT64 */;
|
|
19773
20093
|
break;
|
|
19774
20094
|
default:
|
|
19775
|
-
throw new Error("Unsupported
|
|
20095
|
+
throw new Error("Unsupported MHA data type: " + items[0]);
|
|
19776
20096
|
}
|
|
19777
20097
|
}
|
|
19778
20098
|
if (line.startsWith("ObjectType") && !items[0].includes("Image")) {
|
|
@@ -19794,6 +20114,9 @@ var NVImage = class _NVImage {
|
|
|
19794
20114
|
[mat33[2], mat33[5], mat33[8], offset[2]],
|
|
19795
20115
|
[0, 0, 0, 1]
|
|
19796
20116
|
];
|
|
20117
|
+
while (bytes[pos] === 10) {
|
|
20118
|
+
pos++;
|
|
20119
|
+
}
|
|
19797
20120
|
hdr.vox_offset = pos;
|
|
19798
20121
|
if (isDetached && pairedImgData) {
|
|
19799
20122
|
if (isGz) {
|
|
@@ -20875,7 +21198,7 @@ var NVImage = class _NVImage {
|
|
|
20875
21198
|
let saveData = null;
|
|
20876
21199
|
const compress2 = fnm.endsWith(".gz");
|
|
20877
21200
|
if (compress2) {
|
|
20878
|
-
saveData =
|
|
21201
|
+
saveData = gzipSync2(odata, {
|
|
20879
21202
|
// GZIP-specific: the filename to use when decompressed
|
|
20880
21203
|
filename: fnm,
|
|
20881
21204
|
// GZIP-specific: the modification time. Can be a Date, date string,
|
|
@@ -21338,7 +21661,7 @@ var NVImage = class _NVImage {
|
|
|
21338
21661
|
* read a 3D slab of voxels from a volume
|
|
21339
21662
|
* @param voxStart - first row, column and slice (RAS order) for selection
|
|
21340
21663
|
* @param voxEnd - final row, column and slice (RAS order) for selection
|
|
21341
|
-
* @param dataType - array data type. Options: 'same' (default), 'uint8', 'float32'
|
|
21664
|
+
* @param dataType - array data type. Options: 'same' (default), 'uint8', 'float32', 'scaled', 'normalized', 'windowed'
|
|
21342
21665
|
* @returns the an array where ret[0] is the voxel values and ret[1] is dimension of selection
|
|
21343
21666
|
* @see {@link https://niivue.github.io/niivue/features/slab_selection.html | live demo usage}
|
|
21344
21667
|
*/
|
|
@@ -21362,7 +21685,7 @@ var NVImage = class _NVImage {
|
|
|
21362
21685
|
let dt = this.hdr.datatypeCode;
|
|
21363
21686
|
if (dataType === "uint8") {
|
|
21364
21687
|
dt = 2 /* DT_UINT8 */;
|
|
21365
|
-
} else if (dataType === "float32") {
|
|
21688
|
+
} else if (dataType === "float32" || dataType === "scaled" || dataType === "normalized" || dataType === "windowed") {
|
|
21366
21689
|
dt = 16 /* DT_FLOAT32 */;
|
|
21367
21690
|
}
|
|
21368
21691
|
if (dt === 2 /* DT_UINT8 */) {
|
|
@@ -21392,6 +21715,24 @@ var NVImage = class _NVImage {
|
|
|
21392
21715
|
}
|
|
21393
21716
|
}
|
|
21394
21717
|
}
|
|
21718
|
+
if (dataType === "scaled" || dataType === "normalized" || dataType === "windowed") {
|
|
21719
|
+
for (let i2 = 0; i2 < img.length; i2++) {
|
|
21720
|
+
img[i2] = img[i2] * this.hdr.scl_slope + this.hdr.scl_inter;
|
|
21721
|
+
}
|
|
21722
|
+
}
|
|
21723
|
+
if (dataType === "normalized" || dataType === "windowed") {
|
|
21724
|
+
let mn = this.cal_min;
|
|
21725
|
+
let mx = this.cal_max;
|
|
21726
|
+
if (dataType === "normalized") {
|
|
21727
|
+
mn = this.global_min;
|
|
21728
|
+
mx = this.global_max;
|
|
21729
|
+
}
|
|
21730
|
+
const scale = 1 / (mx - mn);
|
|
21731
|
+
for (let i2 = 0; i2 < img.length; i2++) {
|
|
21732
|
+
img[i2] = (img[i2] - mn) * scale;
|
|
21733
|
+
img[i2] = Math.max(Math.min(img[i2], 1), 0);
|
|
21734
|
+
}
|
|
21735
|
+
}
|
|
21395
21736
|
return [img, slabDims];
|
|
21396
21737
|
}
|
|
21397
21738
|
// getVolumeData()
|
|
@@ -21949,6 +22290,7 @@ var DRAG_MODE = /* @__PURE__ */ ((DRAG_MODE2) => {
|
|
|
21949
22290
|
DRAG_MODE2[DRAG_MODE2["pan"] = 3] = "pan";
|
|
21950
22291
|
DRAG_MODE2[DRAG_MODE2["slicer3D"] = 4] = "slicer3D";
|
|
21951
22292
|
DRAG_MODE2[DRAG_MODE2["callbackOnly"] = 5] = "callbackOnly";
|
|
22293
|
+
DRAG_MODE2[DRAG_MODE2["roiSelection"] = 6] = "roiSelection";
|
|
21952
22294
|
return DRAG_MODE2;
|
|
21953
22295
|
})(DRAG_MODE || {});
|
|
21954
22296
|
var DEFAULT_OPTIONS = {
|
|
@@ -21983,6 +22325,7 @@ var DEFAULT_OPTIONS = {
|
|
|
21983
22325
|
multiplanarPadPixels: 0,
|
|
21984
22326
|
// @deprecated
|
|
21985
22327
|
multiplanarForceRender: false,
|
|
22328
|
+
multiplanarEqualSize: false,
|
|
21986
22329
|
multiplanarShowRender: 2 /* AUTO */,
|
|
21987
22330
|
// auto is the same behaviour as multiplanarForceRender: false
|
|
21988
22331
|
isRadiologicalConvention: false,
|
|
@@ -22017,12 +22360,30 @@ var DEFAULT_OPTIONS = {
|
|
|
22017
22360
|
renderOverlayBlend: 1,
|
|
22018
22361
|
sliceMosaicString: "",
|
|
22019
22362
|
centerMosaic: false,
|
|
22363
|
+
penSize: 1,
|
|
22364
|
+
// in voxels, since all drawing is done using bitmap indices
|
|
22020
22365
|
clickToSegment: false,
|
|
22021
|
-
clickToSegmentRadius:
|
|
22022
|
-
|
|
22023
|
-
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
|
|
22024
22384
|
};
|
|
22025
22385
|
var INITIAL_SCENE_DATA = {
|
|
22386
|
+
gamma: 1,
|
|
22026
22387
|
azimuth: 110,
|
|
22027
22388
|
elevation: 10,
|
|
22028
22389
|
crosshairPos: vec38.create(),
|
|
@@ -22261,6 +22622,9 @@ var NVDocument = class _NVDocument {
|
|
|
22261
22622
|
data.opts.meshThicknessOn2D = "infinity";
|
|
22262
22623
|
}
|
|
22263
22624
|
data.labels = [...this.data.labels];
|
|
22625
|
+
for (const label of data.labels) {
|
|
22626
|
+
delete label.onClick;
|
|
22627
|
+
}
|
|
22264
22628
|
data.customData = this.customData;
|
|
22265
22629
|
if (this.volumes.length) {
|
|
22266
22630
|
let imageOptions = this.imageOptionsArray[0];
|
|
@@ -22516,6 +22880,19 @@ var LabelLineTerminator = /* @__PURE__ */ ((LabelLineTerminator2) => {
|
|
|
22516
22880
|
LabelLineTerminator2["RING"] = "ring";
|
|
22517
22881
|
return LabelLineTerminator2;
|
|
22518
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 || {});
|
|
22519
22896
|
var NVLabel3DStyle = class {
|
|
22520
22897
|
/**
|
|
22521
22898
|
* @param textColor - Color of text
|
|
@@ -22553,13 +22930,17 @@ var NVLabel3D = class {
|
|
|
22553
22930
|
* @param style - The style of the label
|
|
22554
22931
|
* @param points - An array of points label for label lines
|
|
22555
22932
|
*/
|
|
22556
|
-
constructor(text, style, points) {
|
|
22933
|
+
constructor(text, style, points, anchor, onClick) {
|
|
22557
22934
|
__publicField(this, "text");
|
|
22558
22935
|
__publicField(this, "style");
|
|
22559
22936
|
__publicField(this, "points");
|
|
22937
|
+
__publicField(this, "anchor");
|
|
22938
|
+
__publicField(this, "onClick");
|
|
22560
22939
|
this.text = text;
|
|
22561
22940
|
this.style = style;
|
|
22562
22941
|
this.points = points;
|
|
22942
|
+
this.anchor = anchor || 0 /* NONE */;
|
|
22943
|
+
this.onClick = onClick;
|
|
22563
22944
|
}
|
|
22564
22945
|
};
|
|
22565
22946
|
|
|
@@ -23696,7 +24077,7 @@ var defaultSaveImageOptions = {
|
|
|
23696
24077
|
isSaveDrawing: false,
|
|
23697
24078
|
volumeByIndex: 0
|
|
23698
24079
|
};
|
|
23699
|
-
var Niivue = class
|
|
24080
|
+
var Niivue = class {
|
|
23700
24081
|
/**
|
|
23701
24082
|
* @param options - options object to set modifiable Niivue properties
|
|
23702
24083
|
*/
|
|
@@ -23743,6 +24124,7 @@ var Niivue = class _Niivue {
|
|
|
23743
24124
|
__publicField(this, "orientCubeShader");
|
|
23744
24125
|
__publicField(this, "orientCubeShaderVAO", null);
|
|
23745
24126
|
__publicField(this, "rectShader");
|
|
24127
|
+
__publicField(this, "rectOutlineShader");
|
|
23746
24128
|
__publicField(this, "renderShader");
|
|
23747
24129
|
__publicField(this, "lineShader");
|
|
23748
24130
|
__publicField(this, "line3DShader");
|
|
@@ -23795,7 +24177,20 @@ var Niivue = class _Niivue {
|
|
|
23795
24177
|
__publicField(this, "extentsMax");
|
|
23796
24178
|
// ResizeObserver
|
|
23797
24179
|
__publicField(this, "resizeObserver", null);
|
|
23798
|
-
|
|
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
|
+
});
|
|
23799
24194
|
__publicField(this, "readyForSync", false);
|
|
23800
24195
|
// UI Data
|
|
23801
24196
|
__publicField(this, "uiData", {
|
|
@@ -23873,6 +24268,10 @@ var Niivue = class _Niivue {
|
|
|
23873
24268
|
Name: "Hemispheric",
|
|
23874
24269
|
Frag: fragMeshHemiShader
|
|
23875
24270
|
},
|
|
24271
|
+
{
|
|
24272
|
+
Name: "Crevice",
|
|
24273
|
+
Frag: fragMeshShaderCrevice
|
|
24274
|
+
},
|
|
23876
24275
|
{
|
|
23877
24276
|
Name: "Edge",
|
|
23878
24277
|
Frag: fragMeshEdgeShader
|
|
@@ -24270,6 +24669,9 @@ var Niivue = class _Niivue {
|
|
|
24270
24669
|
* @see {@link https://niivue.github.io/niivue/features/sync.mesh.html | live demo usage}
|
|
24271
24670
|
*/
|
|
24272
24671
|
syncWith(otherNV, syncOpts = { "2d": true, "3d": true }) {
|
|
24672
|
+
if (!(otherNV instanceof Array)) {
|
|
24673
|
+
otherNV = [otherNV];
|
|
24674
|
+
}
|
|
24273
24675
|
this.otherNV = otherNV;
|
|
24274
24676
|
this.syncOpts = { ...syncOpts };
|
|
24275
24677
|
}
|
|
@@ -24285,9 +24687,55 @@ var Niivue = class _Niivue {
|
|
|
24285
24687
|
* @see {@link https://niivue.github.io/niivue/features/sync.mesh.html | live demo usage}
|
|
24286
24688
|
*/
|
|
24287
24689
|
broadcastTo(otherNV, syncOpts = { "2d": true, "3d": true }) {
|
|
24690
|
+
if (!(otherNV instanceof Array)) {
|
|
24691
|
+
otherNV = [otherNV];
|
|
24692
|
+
}
|
|
24288
24693
|
this.otherNV = otherNV;
|
|
24289
24694
|
this.syncOpts = syncOpts;
|
|
24290
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
|
+
}
|
|
24291
24739
|
/**
|
|
24292
24740
|
* Sync the scene controls (orientation, crosshair location, etc.) from one Niivue instance to another. useful for using one canvas to drive another.
|
|
24293
24741
|
* @internal
|
|
@@ -24304,36 +24752,39 @@ var Niivue = class _Niivue {
|
|
|
24304
24752
|
if (!this.gl.canvas.matches(":focus")) {
|
|
24305
24753
|
return;
|
|
24306
24754
|
}
|
|
24307
|
-
|
|
24308
|
-
|
|
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
|
+
}
|
|
24309
24780
|
if (this.syncOpts["2d"]) {
|
|
24310
|
-
this.
|
|
24311
|
-
this.otherNV.scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24781
|
+
this.doSync2d(this.otherNV[i]);
|
|
24312
24782
|
}
|
|
24313
24783
|
if (this.syncOpts["3d"]) {
|
|
24314
|
-
this.
|
|
24315
|
-
this.otherNV.scene.renderElevation = this.scene.renderElevation;
|
|
24316
|
-
this.otherNV.scene.volScaleMultiplier = this.scene.volScaleMultiplier;
|
|
24317
|
-
}
|
|
24318
|
-
this.otherNV.drawScene();
|
|
24319
|
-
this.otherNV.createOnLocationChange();
|
|
24320
|
-
} else if (Array.isArray(this.otherNV)) {
|
|
24321
|
-
for (let i = 0; i < this.otherNV.length; i++) {
|
|
24322
|
-
if (this.otherNV[i] === this) {
|
|
24323
|
-
continue;
|
|
24324
|
-
}
|
|
24325
|
-
if (this.syncOpts["2d"]) {
|
|
24326
|
-
this.otherNV[i].scene.crosshairPos = this.otherNV[i].mm2frac(thisMM);
|
|
24327
|
-
this.otherNV[i].scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24328
|
-
}
|
|
24329
|
-
if (this.syncOpts["3d"]) {
|
|
24330
|
-
this.otherNV[i].scene.renderAzimuth = this.scene.renderAzimuth;
|
|
24331
|
-
this.otherNV[i].scene.renderElevation = this.scene.renderElevation;
|
|
24332
|
-
this.otherNV[i].scene.volScaleMultiplier = this.scene.volScaleMultiplier;
|
|
24333
|
-
}
|
|
24334
|
-
this.otherNV[i].drawScene();
|
|
24335
|
-
this.otherNV[i].createOnLocationChange();
|
|
24784
|
+
this.doSync3d(this.otherNV[i]);
|
|
24336
24785
|
}
|
|
24786
|
+
this.otherNV[i].drawScene();
|
|
24787
|
+
this.otherNV[i].createOnLocationChange();
|
|
24337
24788
|
}
|
|
24338
24789
|
}
|
|
24339
24790
|
/** Not documented publicly for now
|
|
@@ -24437,6 +24888,10 @@ var Niivue = class _Niivue {
|
|
|
24437
24888
|
const [x, y] = [pos.x * this.uiData.dpr, pos.y * this.uiData.dpr];
|
|
24438
24889
|
const label = this.getLabelAtPoint([x, y]);
|
|
24439
24890
|
if (label) {
|
|
24891
|
+
if (label.onClick) {
|
|
24892
|
+
label.onClick(label);
|
|
24893
|
+
return;
|
|
24894
|
+
}
|
|
24440
24895
|
for (const mesh of this.meshes) {
|
|
24441
24896
|
if (mesh.type !== "connectome" /* CONNECTOME */) {
|
|
24442
24897
|
continue;
|
|
@@ -24646,6 +25101,9 @@ var Niivue = class _Niivue {
|
|
|
24646
25101
|
const fracStart = this.canvasPos2frac([this.uiData.dragStart[0], this.uiData.dragStart[1]]);
|
|
24647
25102
|
const fracEnd = this.canvasPos2frac([this.uiData.dragEnd[0], this.uiData.dragEnd[1]]);
|
|
24648
25103
|
this.generateMouseUpCallback(fracStart, fracEnd);
|
|
25104
|
+
if (this.opts.dragMode === 6 /* roiSelection */) {
|
|
25105
|
+
return;
|
|
25106
|
+
}
|
|
24649
25107
|
if (this.opts.dragMode !== 1 /* contrast */) {
|
|
24650
25108
|
return;
|
|
24651
25109
|
}
|
|
@@ -24751,6 +25209,15 @@ var Niivue = class _Niivue {
|
|
|
24751
25209
|
this.drawScene();
|
|
24752
25210
|
this.uiData.prevX = this.uiData.currX;
|
|
24753
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();
|
|
24754
25221
|
}
|
|
24755
25222
|
}
|
|
24756
25223
|
// not included in public docs
|
|
@@ -25275,7 +25742,6 @@ var Niivue = class _Niivue {
|
|
|
25275
25742
|
this.loadDrawing(volume);
|
|
25276
25743
|
} else {
|
|
25277
25744
|
this.addVolume(volume);
|
|
25278
|
-
this.setDrawingEnabled(true);
|
|
25279
25745
|
}
|
|
25280
25746
|
}).catch((e2) => {
|
|
25281
25747
|
throw e2;
|
|
@@ -25966,6 +26432,24 @@ var Niivue = class _Niivue {
|
|
|
25966
26432
|
}
|
|
25967
26433
|
return this.meshes[idx].indexNearestXYZmm(Xmm, Ymm, Zmm);
|
|
25968
26434
|
}
|
|
26435
|
+
/**
|
|
26436
|
+
* reduce complexity of FreeSurfer mesh
|
|
26437
|
+
* @param mesh - identity of mesh to change
|
|
26438
|
+
* @param order - decimation order 0..6
|
|
26439
|
+
* @example niivue.decimateHierarchicalMesh(niivue.meshes[0].id, 4)
|
|
26440
|
+
* @returns boolean false if mesh is not hierarchical or of lower order
|
|
26441
|
+
* @see {@link https://niivue.github.io/niivue/features/meshes.html | live demo usage}
|
|
26442
|
+
*/
|
|
26443
|
+
decimateHierarchicalMesh(mesh, order = 3) {
|
|
26444
|
+
const idx = this.getMeshIndexByID(mesh);
|
|
26445
|
+
if (idx < 0) {
|
|
26446
|
+
log.warn("reverseFaces() id not loaded", mesh);
|
|
26447
|
+
return;
|
|
26448
|
+
}
|
|
26449
|
+
const ret = this.meshes[idx].decimateHierarchicalMesh(this.gl, order);
|
|
26450
|
+
this.updateGLVolume();
|
|
26451
|
+
return ret;
|
|
26452
|
+
}
|
|
25969
26453
|
/**
|
|
25970
26454
|
* reverse triangle winding of mesh (swap front and back faces)
|
|
25971
26455
|
* @param id - identity of mesh to change
|
|
@@ -26464,6 +26948,9 @@ var Niivue = class _Niivue {
|
|
|
26464
26948
|
this.renderShader.use(this.gl);
|
|
26465
26949
|
this.gl.uniform3fv(this.renderShader.uniforms.clipLo, this.opts.clipVolumeLow);
|
|
26466
26950
|
this.gl.uniform3fv(this.renderShader.uniforms.clipHi, this.opts.clipVolumeHigh);
|
|
26951
|
+
this.pickingImageShader.use(this.gl);
|
|
26952
|
+
this.gl.uniform3fv(this.pickingImageShader.uniforms.clipLo, this.opts.clipVolumeLow);
|
|
26953
|
+
this.gl.uniform3fv(this.pickingImageShader.uniforms.clipHi, this.opts.clipVolumeHigh);
|
|
26467
26954
|
this.drawScene();
|
|
26468
26955
|
}
|
|
26469
26956
|
/**
|
|
@@ -27188,6 +27675,23 @@ var Niivue = class _Niivue {
|
|
|
27188
27675
|
y = Math.min(Math.max(y, 0), dy - 1);
|
|
27189
27676
|
z = Math.min(Math.max(z, 0), dz - 1);
|
|
27190
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
|
+
}
|
|
27191
27695
|
}
|
|
27192
27696
|
// not included in public docs
|
|
27193
27697
|
// create line between to voxels in drawing
|
|
@@ -27269,6 +27773,98 @@ var Niivue = class _Niivue {
|
|
|
27269
27773
|
}
|
|
27270
27774
|
}
|
|
27271
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
|
+
}
|
|
27272
27868
|
// a voxel can be defined as having 6, 18 or 26 neighbors:
|
|
27273
27869
|
// 6: neighbors share faces (distance=1)
|
|
27274
27870
|
// 18: neighbors share faces (distance=1) or edges (1.4)
|
|
@@ -27350,7 +27946,7 @@ var Niivue = class _Niivue {
|
|
|
27350
27946
|
}
|
|
27351
27947
|
// not included in public docs
|
|
27352
27948
|
// set all connected voxels in drawing to new color
|
|
27353
|
-
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) {
|
|
27354
27950
|
if (!this.drawBitmap) {
|
|
27355
27951
|
throw new Error("drawBitmap undefined");
|
|
27356
27952
|
}
|
|
@@ -27372,9 +27968,25 @@ var Niivue = class _Niivue {
|
|
|
27372
27968
|
if (img.length !== nxy * dims[2]) {
|
|
27373
27969
|
return;
|
|
27374
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
|
+
}
|
|
27375
27977
|
function xyz2vx(pt) {
|
|
27376
27978
|
return pt[0] + pt[1] * nx + pt[2] * nxy;
|
|
27377
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
|
+
}
|
|
27378
27990
|
const seedVx = xyz2vx(seedXYZ);
|
|
27379
27991
|
const seedColor = img[seedVx];
|
|
27380
27992
|
if (seedColor === newColor) {
|
|
@@ -27388,6 +28000,9 @@ var Niivue = class _Niivue {
|
|
|
27388
28000
|
for (let i = 1; i < nxyz; i++) {
|
|
27389
28001
|
img[i] = 0;
|
|
27390
28002
|
if (this.drawBitmap[i] === seedColor) {
|
|
28003
|
+
if (!isWithinDistance(i)) {
|
|
28004
|
+
continue;
|
|
28005
|
+
}
|
|
27391
28006
|
img[i] = 1;
|
|
27392
28007
|
}
|
|
27393
28008
|
}
|
|
@@ -27417,6 +28032,9 @@ var Niivue = class _Niivue {
|
|
|
27417
28032
|
for (let i = 1; i < nxyz; i++) {
|
|
27418
28033
|
img[i] = 0;
|
|
27419
28034
|
if (backImg[i] >= mn && backImg[i] <= mx) {
|
|
28035
|
+
if (!isWithinDistance(i)) {
|
|
28036
|
+
continue;
|
|
28037
|
+
}
|
|
27420
28038
|
img[i] = 1;
|
|
27421
28039
|
}
|
|
27422
28040
|
}
|
|
@@ -28038,6 +28656,8 @@ var Niivue = class _Niivue {
|
|
|
28038
28656
|
gl.bindVertexArray(this.unusedVAO);
|
|
28039
28657
|
this.rectShader = new Shader(gl, vertRectShader, fragRectShader);
|
|
28040
28658
|
this.rectShader.use(gl);
|
|
28659
|
+
this.rectOutlineShader = new Shader(gl, vertRectShader, fragRectOutlineShader);
|
|
28660
|
+
this.rectOutlineShader.use(gl);
|
|
28041
28661
|
this.lineShader = new Shader(gl, vertLineShader, fragRectShader);
|
|
28042
28662
|
this.lineShader.use(gl);
|
|
28043
28663
|
this.line3DShader = new Shader(gl, vertLine3DShader, fragRectShader);
|
|
@@ -28190,14 +28810,37 @@ var Niivue = class _Niivue {
|
|
|
28190
28810
|
}
|
|
28191
28811
|
/**
|
|
28192
28812
|
* basic statistics for selected voxel-based image
|
|
28193
|
-
* @param
|
|
28194
|
-
*
|
|
28195
|
-
*
|
|
28196
|
-
*
|
|
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
|
+
* });
|
|
28197
28830
|
* @see {@link https://niivue.github.io/niivue/features/draw2.html | live demo usage}
|
|
28198
28831
|
*/
|
|
28199
|
-
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;
|
|
28200
28842
|
const hdr = this.volumes[layer].hdr;
|
|
28843
|
+
const pixDimsRAS = this.volumes[layer].pixDimsRAS;
|
|
28201
28844
|
let slope = hdr.scl_slope;
|
|
28202
28845
|
if (isNaN(slope)) {
|
|
28203
28846
|
slope = 1;
|
|
@@ -28235,6 +28878,37 @@ var Niivue = class _Niivue {
|
|
|
28235
28878
|
mask[i] = 0;
|
|
28236
28879
|
}
|
|
28237
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);
|
|
28238
28912
|
}
|
|
28239
28913
|
let k = 0;
|
|
28240
28914
|
let M = 0;
|
|
@@ -28288,7 +28962,8 @@ var Niivue = class _Niivue {
|
|
|
28288
28962
|
cal_min: this.volumes[layer].cal_min,
|
|
28289
28963
|
cal_max: this.volumes[layer].cal_max,
|
|
28290
28964
|
robust_min: this.volumes[layer].robust_min,
|
|
28291
|
-
robust_max: this.volumes[layer].robust_max
|
|
28965
|
+
robust_max: this.volumes[layer].robust_max,
|
|
28966
|
+
area
|
|
28292
28967
|
};
|
|
28293
28968
|
}
|
|
28294
28969
|
// not included in public docs
|
|
@@ -28765,6 +29440,8 @@ var Niivue = class _Niivue {
|
|
|
28765
29440
|
this.pickingImageShader.use(this.gl);
|
|
28766
29441
|
this.gl.uniform1f(this.pickingImageShader.uniforms.overlays, this.overlays.length);
|
|
28767
29442
|
this.gl.uniform3fv(this.pickingImageShader.uniforms.texVox, vox);
|
|
29443
|
+
this.gl.uniform3fv(this.pickingImageShader.uniforms.clipLo, this.opts.clipVolumeLow);
|
|
29444
|
+
this.gl.uniform3fv(this.pickingImageShader.uniforms.clipHi, this.opts.clipVolumeHigh);
|
|
28768
29445
|
let shader = this.sliceMMShader;
|
|
28769
29446
|
if (this.opts.isV1SliceShader) {
|
|
28770
29447
|
shader = this.sliceV1Shader;
|
|
@@ -29477,6 +30154,7 @@ var Niivue = class _Niivue {
|
|
|
29477
30154
|
* @see {@link https://niivue.github.io/niivue/features/colormaps.html | live demo usage}
|
|
29478
30155
|
*/
|
|
29479
30156
|
setGamma(gamma = 1) {
|
|
30157
|
+
this.scene.gamma = gamma;
|
|
29480
30158
|
cmapper.gamma = gamma;
|
|
29481
30159
|
this.updateGLVolume();
|
|
29482
30160
|
}
|
|
@@ -29841,27 +30519,50 @@ var Niivue = class _Niivue {
|
|
|
29841
30519
|
if (this.opts.drawingEnabled) {
|
|
29842
30520
|
const pt = this.frac2vox(this.scene.crosshairPos);
|
|
29843
30521
|
if (this.opts.clickToSegment) {
|
|
29844
|
-
const
|
|
29845
|
-
|
|
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
|
+
}
|
|
29846
30534
|
const brightOrDark = this.opts.clickToSegmentBright ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
|
|
29847
|
-
this.drawPenFillPts = [];
|
|
29848
30535
|
this.drawPenAxCorSag = axCorSag;
|
|
29849
|
-
|
|
29850
|
-
const
|
|
29851
|
-
|
|
29852
|
-
|
|
29853
|
-
|
|
29854
|
-
|
|
29855
|
-
|
|
29856
|
-
this.
|
|
29857
|
-
|
|
29858
|
-
|
|
29859
|
-
|
|
29860
|
-
|
|
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);
|
|
29861
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
|
+
);
|
|
29862
30559
|
this.drawScene();
|
|
29863
30560
|
this.createOnLocationChange(axCorSag);
|
|
29864
|
-
const info = this.getDescriptives(
|
|
30561
|
+
const info = this.getDescriptives({
|
|
30562
|
+
layer: 0,
|
|
30563
|
+
masks: [],
|
|
30564
|
+
drawingIsMask: true
|
|
30565
|
+
});
|
|
29865
30566
|
this.onClickToSegment({ mL: info.volumeML, mm3: info.volumeMM3 });
|
|
29866
30567
|
return;
|
|
29867
30568
|
}
|
|
@@ -30079,20 +30780,41 @@ var Niivue = class _Niivue {
|
|
|
30079
30780
|
if (!this.rectShader) {
|
|
30080
30781
|
throw new Error("rectShader undefined");
|
|
30081
30782
|
}
|
|
30082
|
-
this.
|
|
30083
|
-
|
|
30084
|
-
|
|
30085
|
-
|
|
30086
|
-
|
|
30087
|
-
this.
|
|
30088
|
-
|
|
30089
|
-
|
|
30090
|
-
|
|
30091
|
-
|
|
30092
|
-
|
|
30093
|
-
|
|
30094
|
-
|
|
30095
|
-
|
|
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
|
+
}
|
|
30096
30818
|
}
|
|
30097
30819
|
drawCircle(leftTopWidthHeight, circleColor = this.opts.fontColor, fillPercent = 1) {
|
|
30098
30820
|
if (!this.circleShader) {
|
|
@@ -30136,9 +30858,21 @@ var Niivue = class _Niivue {
|
|
|
30136
30858
|
const labels = [...this.document.labels, ...definedMeshLabels];
|
|
30137
30859
|
return labels;
|
|
30138
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
|
+
}
|
|
30139
30873
|
getBulletMarginWidth() {
|
|
30140
30874
|
let bulletMargin = 0;
|
|
30141
|
-
const labels = this.
|
|
30875
|
+
const labels = this.getConnectomeLabels();
|
|
30142
30876
|
if (labels.length === 0) {
|
|
30143
30877
|
return 0;
|
|
30144
30878
|
}
|
|
@@ -30155,7 +30889,7 @@ var Niivue = class _Niivue {
|
|
|
30155
30889
|
return bulletMargin;
|
|
30156
30890
|
}
|
|
30157
30891
|
getLegendPanelWidth() {
|
|
30158
|
-
const labels = this.
|
|
30892
|
+
const labels = this.getConnectomeLabels();
|
|
30159
30893
|
if (!this.opts.showLegend || labels.length === 0) {
|
|
30160
30894
|
return 0;
|
|
30161
30895
|
}
|
|
@@ -30181,7 +30915,7 @@ var Niivue = class _Niivue {
|
|
|
30181
30915
|
return width;
|
|
30182
30916
|
}
|
|
30183
30917
|
getLegendPanelHeight() {
|
|
30184
|
-
const labels = this.
|
|
30918
|
+
const labels = this.getConnectomeLabels();
|
|
30185
30919
|
let height = 0;
|
|
30186
30920
|
const scale = 1;
|
|
30187
30921
|
const verticalMargin = this.opts.textHeight * this.gl.canvas.height * scale;
|
|
@@ -30623,7 +31357,7 @@ var Niivue = class _Niivue {
|
|
|
30623
31357
|
}
|
|
30624
31358
|
// not included in public docs
|
|
30625
31359
|
// show text labels for L/R, A/P, I/S dimensions
|
|
30626
|
-
drawSliceOrientationText(leftTopWidthHeight, axCorSag) {
|
|
31360
|
+
drawSliceOrientationText(leftTopWidthHeight, axCorSag, padLeftTop = [NaN, NaN]) {
|
|
30627
31361
|
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
|
|
30628
31362
|
let topText = "S";
|
|
30629
31363
|
if (axCorSag === 0 /* AXIAL */) {
|
|
@@ -30637,8 +31371,32 @@ var Niivue = class _Niivue {
|
|
|
30637
31371
|
this.drawTextRightBelow([leftTopWidthHeight[0], leftTopWidthHeight[1]], leftText + topText);
|
|
30638
31372
|
return;
|
|
30639
31373
|
}
|
|
30640
|
-
|
|
30641
|
-
|
|
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
|
+
}
|
|
30642
31400
|
}
|
|
30643
31401
|
// not included in public docs
|
|
30644
31402
|
xyMM2xyzMM(axCorSag, sliceFrac) {
|
|
@@ -30676,7 +31434,7 @@ var Niivue = class _Niivue {
|
|
|
30676
31434
|
}
|
|
30677
31435
|
// not included in public docs
|
|
30678
31436
|
// draw 2D tile
|
|
30679
|
-
|
|
31437
|
+
draw2DMain(leftTopWidthHeight, axCorSag, customMM = NaN) {
|
|
30680
31438
|
let frac2mmTexture = this.volumes[0].frac2mm.slice();
|
|
30681
31439
|
let screen = this.screenFieldOfViewExtendedMM(axCorSag);
|
|
30682
31440
|
let mesh2ortho = mat46.create();
|
|
@@ -30705,7 +31463,9 @@ var Niivue = class _Niivue {
|
|
|
30705
31463
|
elevation = isRadiolgical ? -90 : 90;
|
|
30706
31464
|
}
|
|
30707
31465
|
const gl = this.gl;
|
|
31466
|
+
let isStretchToScreen = false;
|
|
30708
31467
|
if (leftTopWidthHeight[2] === 0 || leftTopWidthHeight[3] === 0) {
|
|
31468
|
+
isStretchToScreen = true;
|
|
30709
31469
|
const pixPerMMw = gl.canvas.width / screen.fovMM[0];
|
|
30710
31470
|
const pixPerMMh = gl.canvas.height / screen.fovMM[1];
|
|
30711
31471
|
const pixPerMMmin = Math.min(pixPerMMw, pixPerMMh);
|
|
@@ -30852,9 +31612,32 @@ var Niivue = class _Niivue {
|
|
|
30852
31612
|
if (isNaN(customMM)) {
|
|
30853
31613
|
this.drawCrosshairs3D(false, 0.15, obj.modelViewProjectionMatrix, true, this.opts.isSliceMM);
|
|
30854
31614
|
}
|
|
30855
|
-
|
|
31615
|
+
if (isStretchToScreen) {
|
|
31616
|
+
this.drawSliceOrientationText(leftTopWidthHeight, axCorSag);
|
|
31617
|
+
}
|
|
30856
31618
|
this.readyForSync = true;
|
|
30857
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
|
+
}
|
|
30858
31641
|
// not included in public docs
|
|
30859
31642
|
// determine 3D model view projection matrix
|
|
30860
31643
|
calculateMvpMatrix(_unused, leftTopWidthHeight = [0, 0, 0, 0], azimuth, elevation) {
|
|
@@ -31465,7 +32248,7 @@ var Niivue = class _Niivue {
|
|
|
31465
32248
|
* @param style - label style
|
|
31466
32249
|
* @param point - 3D point on the model
|
|
31467
32250
|
*/
|
|
31468
|
-
addLabel(text, style, points) {
|
|
32251
|
+
addLabel(text, style, points, anchor, onClick) {
|
|
31469
32252
|
const defaultStyle = {
|
|
31470
32253
|
textColor: this.opts.legendTextColor,
|
|
31471
32254
|
textScale: 1,
|
|
@@ -31477,7 +32260,7 @@ var Niivue = class _Niivue {
|
|
|
31477
32260
|
bulletColor: this.opts.legendTextColor
|
|
31478
32261
|
};
|
|
31479
32262
|
const labelStyle = style ? { ...defaultStyle, ...style } : { ...defaultStyle };
|
|
31480
|
-
const label = new NVLabel3D(text, labelStyle, points);
|
|
32263
|
+
const label = new NVLabel3D(text, { ...labelStyle }, points, anchor, onClick);
|
|
31481
32264
|
this.document.labels.push(label);
|
|
31482
32265
|
return label;
|
|
31483
32266
|
}
|
|
@@ -31495,6 +32278,60 @@ var Niivue = class _Niivue {
|
|
|
31495
32278
|
return screenPoint;
|
|
31496
32279
|
}
|
|
31497
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
|
+
}
|
|
31498
32335
|
log.debug("screenPoint", screenPoint);
|
|
31499
32336
|
const panelHeight = this.getLegendPanelHeight();
|
|
31500
32337
|
const panelWidth = this.getLegendPanelWidth();
|
|
@@ -31504,9 +32341,7 @@ var Niivue = class _Niivue {
|
|
|
31504
32341
|
if (screenPoint[0] < left || screenPoint[1] < top || screenPoint[0] > left + panelWidth || screenPoint[1] > top + panelHeight) {
|
|
31505
32342
|
return null;
|
|
31506
32343
|
}
|
|
31507
|
-
const
|
|
31508
|
-
const size = this.opts.textHeight * Math.min(this.gl.canvas.height, this.gl.canvas.width) * scale;
|
|
31509
|
-
const labels = this.getAllLabels();
|
|
32344
|
+
const labels = this.getConnectomeLabels();
|
|
31510
32345
|
for (const label of labels) {
|
|
31511
32346
|
const labelSize = this.opts.textHeight * this.gl.canvas.height * label.style.textScale;
|
|
31512
32347
|
const textHeight = this.textHeight(labelSize, label.text);
|
|
@@ -31535,7 +32370,7 @@ var Niivue = class _Niivue {
|
|
|
31535
32370
|
}
|
|
31536
32371
|
}
|
|
31537
32372
|
// not included in public docs
|
|
31538
|
-
draw3DLabel(label, pos, mvpMatrix, leftTopWidthHeight, bulletMargin
|
|
32373
|
+
draw3DLabel(label, pos, mvpMatrix, leftTopWidthHeight, bulletMargin, legendWidth, secondPass) {
|
|
31539
32374
|
const text = label.text;
|
|
31540
32375
|
const left = pos[0];
|
|
31541
32376
|
const top = pos[1];
|
|
@@ -31552,22 +32387,24 @@ var Niivue = class _Niivue {
|
|
|
31552
32387
|
this.drawCircle([rectLeft, rectTop, bulletSize, bulletSize], label.style.bulletColor);
|
|
31553
32388
|
}
|
|
31554
32389
|
let textLeft = left;
|
|
31555
|
-
if (
|
|
31556
|
-
|
|
31557
|
-
|
|
31558
|
-
|
|
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
|
+
}
|
|
31559
32399
|
} else {
|
|
31560
|
-
|
|
31561
|
-
textLeft += (remaining - textWidth) / 2;
|
|
32400
|
+
textLeft += bulletMargin;
|
|
31562
32401
|
}
|
|
31563
|
-
} else {
|
|
31564
|
-
textLeft += bulletMargin;
|
|
31565
32402
|
}
|
|
31566
32403
|
this.drawText([textLeft, top], text, label.style.textScale, label.style.textColor);
|
|
31567
32404
|
}
|
|
31568
32405
|
// not included in public docs
|
|
31569
32406
|
draw3DLabels(mvpMatrix, leftTopWidthHeight, secondPass = false) {
|
|
31570
|
-
const labels = this.
|
|
32407
|
+
const labels = this.getConnectomeLabels();
|
|
31571
32408
|
if (!this.opts.showLegend || labels.length === 0) {
|
|
31572
32409
|
return;
|
|
31573
32410
|
}
|
|
@@ -31605,6 +32442,51 @@ var Niivue = class _Niivue {
|
|
|
31605
32442
|
}
|
|
31606
32443
|
}
|
|
31607
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
|
+
}
|
|
31608
32490
|
// not included in public docs
|
|
31609
32491
|
draw3D(leftTopWidthHeight = [0, 0, 0, 0], mvpMatrix = null, modelMatrix = null, normalMatrix = null, azimuth = null, elevation = 0) {
|
|
31610
32492
|
const isMosaic = azimuth !== null;
|
|
@@ -32480,6 +33362,12 @@ var Niivue = class _Niivue {
|
|
|
32480
33362
|
}
|
|
32481
33363
|
const isDrawPenDown = isFinite(this.drawPenLocation[0]) && this.opts.drawingEnabled;
|
|
32482
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
|
+
}
|
|
32483
33371
|
if (typeof this.opts.multiplanarPadPixels !== "number") {
|
|
32484
33372
|
log.debug("multiplanarPadPixels must be numeric");
|
|
32485
33373
|
}
|
|
@@ -32517,38 +33405,43 @@ var Niivue = class _Niivue {
|
|
|
32517
33405
|
isDrawGrid = true;
|
|
32518
33406
|
}
|
|
32519
33407
|
}
|
|
33408
|
+
let ltwh = ltwh2x2;
|
|
32520
33409
|
if (isDrawColumn) {
|
|
32521
|
-
|
|
33410
|
+
ltwh = ltwh1x3;
|
|
32522
33411
|
if (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh1x4[4] >= ltwh1x3[4]) {
|
|
32523
33412
|
ltwh = ltwh1x4;
|
|
32524
33413
|
} else {
|
|
32525
33414
|
isDraw3D = false;
|
|
32526
33415
|
}
|
|
32527
|
-
const sX = volScale[0] * ltwh[4];
|
|
32528
|
-
const sY = volScale[1] * ltwh[4];
|
|
32529
|
-
const sZ = volScale[2] * ltwh[4];
|
|
32530
|
-
const sMx = mx * ltwh[4];
|
|
32531
|
-
this.draw2D([ltwh[0], ltwh[1], sX, sY], 0);
|
|
32532
|
-
this.draw2D([ltwh[0], ltwh[1] + sY + pad, sX, sZ], 1);
|
|
32533
|
-
this.draw2D([ltwh[0], ltwh[1] + sY + pad + sZ + pad, sY, sZ], 2);
|
|
32534
|
-
if (isDraw3D) {
|
|
32535
|
-
this.draw3D([ltwh[0], ltwh[1] + sY + sZ + sZ + pad * 3, sMx, sMx]);
|
|
32536
|
-
}
|
|
32537
33416
|
} else if (isDrawRow) {
|
|
32538
|
-
|
|
33417
|
+
ltwh = ltwh3x1;
|
|
32539
33418
|
if (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh4x1[4] >= ltwh3x1[4]) {
|
|
32540
33419
|
ltwh = ltwh4x1;
|
|
32541
33420
|
} else {
|
|
32542
33421
|
isDraw3D = false;
|
|
32543
33422
|
}
|
|
32544
|
-
|
|
32545
|
-
|
|
32546
|
-
|
|
32547
|
-
|
|
32548
|
-
|
|
32549
|
-
|
|
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]);
|
|
33434
|
+
if (isDraw3D) {
|
|
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]);
|
|
32550
33442
|
if (isDraw3D) {
|
|
32551
|
-
|
|
33443
|
+
const sMx = mx * ltwh[4];
|
|
33444
|
+
this.draw3D([ltwh[0] + sX + sX + sY + pad * 3, ltwh[1], sMx, sMx]);
|
|
32552
33445
|
}
|
|
32553
33446
|
} else if (isDrawGrid) {
|
|
32554
33447
|
if (!isShowRender) {
|
|
@@ -32557,13 +33450,9 @@ var Niivue = class _Niivue {
|
|
|
32557
33450
|
if (this.opts.multiplanarShowRender === 2 /* AUTO */) {
|
|
32558
33451
|
isDraw3D = true;
|
|
32559
33452
|
}
|
|
32560
|
-
|
|
32561
|
-
|
|
32562
|
-
|
|
32563
|
-
const sZ = volScale[2] * ltwh[4];
|
|
32564
|
-
this.draw2D([ltwh[0], ltwh[1] + sZ + pad, sX, sY], 0);
|
|
32565
|
-
this.draw2D([ltwh[0], ltwh[1], sX, sZ], 1);
|
|
32566
|
-
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]);
|
|
32567
33456
|
if (isDraw3D) {
|
|
32568
33457
|
this.draw3D([ltwh[0] + sX + pad, ltwh[1] + sZ + pad, sY, sY]);
|
|
32569
33458
|
}
|
|
@@ -32627,11 +33516,30 @@ var Niivue = class _Niivue {
|
|
|
32627
33516
|
width,
|
|
32628
33517
|
height
|
|
32629
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
|
+
}
|
|
32630
33537
|
}
|
|
32631
33538
|
const pos = this.frac2mm([this.scene.crosshairPos[0], this.scene.crosshairPos[1], this.scene.crosshairPos[2]]);
|
|
32632
33539
|
posString = pos[0].toFixed(2) + "\xD7" + pos[1].toFixed(2) + "\xD7" + pos[2].toFixed(2);
|
|
32633
33540
|
this.readyForSync = true;
|
|
32634
33541
|
this.sync();
|
|
33542
|
+
this.drawAnchoredLabels();
|
|
32635
33543
|
return posString;
|
|
32636
33544
|
}
|
|
32637
33545
|
// not included in public docs
|
|
@@ -32666,6 +33574,7 @@ export {
|
|
|
32666
33574
|
DEFAULT_OPTIONS,
|
|
32667
33575
|
DRAG_MODE,
|
|
32668
33576
|
INITIAL_SCENE_DATA,
|
|
33577
|
+
LabelAnchorPoint,
|
|
32669
33578
|
LabelLineTerminator,
|
|
32670
33579
|
LabelTextAlignment,
|
|
32671
33580
|
MULTIPLANAR_TYPE,
|
|
@@ -32679,6 +33588,7 @@ export {
|
|
|
32679
33588
|
NVMeshFromUrlOptions,
|
|
32680
33589
|
NVMeshLayerDefaults,
|
|
32681
33590
|
NVMeshLoaders,
|
|
33591
|
+
NVMeshUtilities,
|
|
32682
33592
|
NVUtilities,
|
|
32683
33593
|
Niivue,
|
|
32684
33594
|
SHOW_RENDER,
|