@niivue/niivue 0.45.1 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.d.ts +105 -16
- package/build/index.js +909 -184
- package/build/index.js.map +1 -1
- package/build/index.min.js +1 -1
- package/dist/index.d.ts +105 -16
- package/dist/index.js +1140 -361
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/niivue.umd.js +66 -27
- package/package.json +2 -2
- package/src/index.html +85 -4
- package/src/niivue/index.ts +955 -156
- package/src/niivue.css +0 -1
- package/src/nvconnectome.ts +81 -39
- package/src/nvdocument.ts +50 -7
- package/src/nvimage/index.ts +27 -13
- package/src/nvmesh-loaders.ts +1 -1
- package/src/shader-srcs.ts +41 -0
- package/src/types.ts +13 -0
- package/LICENSE +0 -25
- package/README.md +0 -72
- package/src/cow.iwm.cbor +0 -0
- package/src/fslmean.iwi.cbor +0 -0
package/build/index.js
CHANGED
|
@@ -10,7 +10,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
10
10
|
import { mat4 as mat46, vec2, vec3 as vec311, vec4 as vec48 } from "gl-matrix";
|
|
11
11
|
|
|
12
12
|
// package.json
|
|
13
|
-
var version = "0.
|
|
13
|
+
var version = "0.47.0";
|
|
14
14
|
|
|
15
15
|
// src/logger.ts
|
|
16
16
|
var _Log = class _Log {
|
|
@@ -806,6 +806,46 @@ out vec4 color;
|
|
|
806
806
|
void main() {
|
|
807
807
|
color = lineColor;
|
|
808
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
|
+
}`;
|
|
809
849
|
var vertColorbarShader = `#version 300 es
|
|
810
850
|
#line 490
|
|
811
851
|
layout(location=0) in vec3 pos;
|
|
@@ -12328,10 +12368,10 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
12328
12368
|
});
|
|
12329
12369
|
}
|
|
12330
12370
|
const dpv32 = [];
|
|
12331
|
-
for (let
|
|
12371
|
+
for (let i2 = 0; i2 < dpv.length; i2++) {
|
|
12332
12372
|
dpv32.push({
|
|
12333
|
-
id: dpv[
|
|
12334
|
-
vals: Float32Array.from(dpv[
|
|
12373
|
+
id: dpv[i2].id,
|
|
12374
|
+
vals: Float32Array.from(dpv[i2].vals)
|
|
12335
12375
|
});
|
|
12336
12376
|
}
|
|
12337
12377
|
offsetPt0[noffset++] = npt;
|
|
@@ -14445,6 +14485,7 @@ var NVMeshLoaders = class _NVMeshLoaders {
|
|
|
14445
14485
|
plen = reader.getInt32(voxoffset += 4, isLittleEndian) - 1;
|
|
14446
14486
|
break;
|
|
14447
14487
|
case TAG_OLD_SURF_GEOM:
|
|
14488
|
+
// these don't take lengths at all
|
|
14448
14489
|
case TAG_OLD_USEREALRAS:
|
|
14449
14490
|
plen = 0;
|
|
14450
14491
|
break;
|
|
@@ -20221,6 +20262,11 @@ var NVImage = class _NVImage {
|
|
|
20221
20262
|
case "comments":
|
|
20222
20263
|
hdr.description = items[0].substring(0, Math.min(79, items[0].length));
|
|
20223
20264
|
break;
|
|
20265
|
+
/* case 'command_history':
|
|
20266
|
+
if (items[0].startsWith('dwi2tensor')) {
|
|
20267
|
+
isTensor = true
|
|
20268
|
+
}
|
|
20269
|
+
break */
|
|
20224
20270
|
case "RepetitionTime":
|
|
20225
20271
|
TR = parseFloat(items[0]);
|
|
20226
20272
|
break;
|
|
@@ -20942,11 +20988,13 @@ var NVImage = class _NVImage {
|
|
|
20942
20988
|
this.onOpacityChange(this);
|
|
20943
20989
|
}
|
|
20944
20990
|
}
|
|
20945
|
-
|
|
20946
|
-
|
|
20947
|
-
|
|
20948
|
-
|
|
20949
|
-
|
|
20991
|
+
/**
|
|
20992
|
+
* set contrast/brightness to robust range (2%..98%)
|
|
20993
|
+
* @param vol - volume for estimate (use -1 to use estimate on all loaded volumes; use INFINITY for current volume)
|
|
20994
|
+
* @sets volume brightness and returns array [pct2, pct98, mnScale, mxScale]
|
|
20995
|
+
* @see {@link https://niivue.github.io/niivue/features/timeseries2.html | live demo usage}
|
|
20996
|
+
*/
|
|
20997
|
+
calMinMax(vol = Number.POSITIVE_INFINITY) {
|
|
20950
20998
|
if (!this.hdr) {
|
|
20951
20999
|
throw new Error("hdr undefined");
|
|
20952
21000
|
}
|
|
@@ -20957,10 +21005,20 @@ var NVImage = class _NVImage {
|
|
|
20957
21005
|
let mx = Number.NEGATIVE_INFINITY;
|
|
20958
21006
|
let nZero = 0;
|
|
20959
21007
|
let nNan = 0;
|
|
20960
|
-
|
|
21008
|
+
let voxEnd = this.img.length;
|
|
21009
|
+
let voxStart = 0;
|
|
21010
|
+
const nVox3D = this.hdr.dims[1] * this.hdr.dims[2] * this.hdr.dims[3];
|
|
21011
|
+
const nVol = Math.floor(voxEnd / nVox3D);
|
|
21012
|
+
if (vol >= nVol) {
|
|
21013
|
+
vol = this.frame4D;
|
|
21014
|
+
}
|
|
21015
|
+
if (vol >= 0 && vol < nVol) {
|
|
21016
|
+
voxStart = vol * nVox3D;
|
|
21017
|
+
voxEnd = voxStart + nVox3D;
|
|
21018
|
+
}
|
|
20961
21019
|
const isFastCalc = this.img.constructor !== Float64Array && this.img.constructor !== Float32Array && this.ignoreZeroVoxels;
|
|
20962
21020
|
if (isFastCalc) {
|
|
20963
|
-
for (let i =
|
|
21021
|
+
for (let i = voxStart; i < voxEnd; i++) {
|
|
20964
21022
|
mn = Math.min(this.img[i], mn);
|
|
20965
21023
|
mx = Math.max(this.img[i], mx);
|
|
20966
21024
|
if (this.img[i] === 0) {
|
|
@@ -20968,7 +21026,7 @@ var NVImage = class _NVImage {
|
|
|
20968
21026
|
}
|
|
20969
21027
|
}
|
|
20970
21028
|
} else {
|
|
20971
|
-
for (let i =
|
|
21029
|
+
for (let i = voxStart; i < voxEnd; i++) {
|
|
20972
21030
|
if (isNaN(this.img[i])) {
|
|
20973
21031
|
nNan++;
|
|
20974
21032
|
continue;
|
|
@@ -21013,7 +21071,7 @@ var NVImage = class _NVImage {
|
|
|
21013
21071
|
this.robust_max = this.cal_max;
|
|
21014
21072
|
return [cmMin, cmMax, cmMin, cmMax];
|
|
21015
21073
|
}
|
|
21016
|
-
const percentZero = 100 * nZero /
|
|
21074
|
+
const percentZero = 100 * nZero / (voxEnd - voxStart);
|
|
21017
21075
|
let isOverrideIgnoreZeroVoxels = false;
|
|
21018
21076
|
if (percentZero > 60 && !this.ignoreZeroVoxels) {
|
|
21019
21077
|
log.warn(`${Math.round(percentZero)}% of voxels are zero: ignoring zeros for cal_max`);
|
|
@@ -21024,7 +21082,7 @@ var NVImage = class _NVImage {
|
|
|
21024
21082
|
nZero = 0;
|
|
21025
21083
|
}
|
|
21026
21084
|
nZero += nNan;
|
|
21027
|
-
const n2pct = Math.round((
|
|
21085
|
+
const n2pct = Math.round((voxEnd - voxStart - nZero) * this.percentileFrac);
|
|
21028
21086
|
if (n2pct < 1 || mn === mx) {
|
|
21029
21087
|
log.debug("no variability in image intensity?");
|
|
21030
21088
|
this.cal_min = mnScale;
|
|
@@ -21042,11 +21100,11 @@ var NVImage = class _NVImage {
|
|
|
21042
21100
|
hist[i] = 0;
|
|
21043
21101
|
}
|
|
21044
21102
|
if (isFastCalc) {
|
|
21045
|
-
for (let i =
|
|
21103
|
+
for (let i = voxStart; i < voxEnd; i++) {
|
|
21046
21104
|
hist[Math.round((this.img[i] - mn) * scl)]++;
|
|
21047
21105
|
}
|
|
21048
21106
|
} else if (this.ignoreZeroVoxels) {
|
|
21049
|
-
for (let i =
|
|
21107
|
+
for (let i = voxStart; i < voxEnd; i++) {
|
|
21050
21108
|
if (this.img[i] === 0) {
|
|
21051
21109
|
continue;
|
|
21052
21110
|
}
|
|
@@ -21056,7 +21114,7 @@ var NVImage = class _NVImage {
|
|
|
21056
21114
|
hist[Math.round((this.img[i] - mn) * scl)]++;
|
|
21057
21115
|
}
|
|
21058
21116
|
} else {
|
|
21059
|
-
for (let i =
|
|
21117
|
+
for (let i = voxStart; i < voxEnd; i++) {
|
|
21060
21118
|
if (isNaN(this.img[i])) {
|
|
21061
21119
|
continue;
|
|
21062
21120
|
}
|
|
@@ -22250,12 +22308,14 @@ var DRAG_MODE = /* @__PURE__ */ ((DRAG_MODE2) => {
|
|
|
22250
22308
|
DRAG_MODE2[DRAG_MODE2["pan"] = 3] = "pan";
|
|
22251
22309
|
DRAG_MODE2[DRAG_MODE2["slicer3D"] = 4] = "slicer3D";
|
|
22252
22310
|
DRAG_MODE2[DRAG_MODE2["callbackOnly"] = 5] = "callbackOnly";
|
|
22311
|
+
DRAG_MODE2[DRAG_MODE2["roiSelection"] = 6] = "roiSelection";
|
|
22253
22312
|
return DRAG_MODE2;
|
|
22254
22313
|
})(DRAG_MODE || {});
|
|
22255
22314
|
var DEFAULT_OPTIONS = {
|
|
22256
22315
|
textHeight: 0.06,
|
|
22257
22316
|
colorbarHeight: 0.05,
|
|
22258
22317
|
crosshairWidth: 1,
|
|
22318
|
+
crosshairWidthUnit: "voxels",
|
|
22259
22319
|
crosshairGap: 0,
|
|
22260
22320
|
rulerWidth: 4,
|
|
22261
22321
|
show3Dcrosshair: false,
|
|
@@ -22281,6 +22341,7 @@ var DEFAULT_OPTIONS = {
|
|
|
22281
22341
|
isRuler: false,
|
|
22282
22342
|
isColorbar: false,
|
|
22283
22343
|
isOrientCube: false,
|
|
22344
|
+
tileMargin: 0,
|
|
22284
22345
|
multiplanarPadPixels: 0,
|
|
22285
22346
|
// @deprecated
|
|
22286
22347
|
multiplanarForceRender: false,
|
|
@@ -22293,6 +22354,8 @@ var DEFAULT_OPTIONS = {
|
|
|
22293
22354
|
yoke3Dto2DZoom: false,
|
|
22294
22355
|
isDepthPickMesh: false,
|
|
22295
22356
|
isCornerOrientationText: false,
|
|
22357
|
+
heroImageFraction: 0,
|
|
22358
|
+
heroSliceType: 4 /* RENDER */,
|
|
22296
22359
|
sagittalNoseLeft: false,
|
|
22297
22360
|
isSliceMM: false,
|
|
22298
22361
|
isV1SliceShader: false,
|
|
@@ -22319,20 +22382,49 @@ var DEFAULT_OPTIONS = {
|
|
|
22319
22382
|
renderOverlayBlend: 1,
|
|
22320
22383
|
sliceMosaicString: "",
|
|
22321
22384
|
centerMosaic: false,
|
|
22385
|
+
penSize: 1,
|
|
22386
|
+
// in voxels, since all drawing is done using bitmap indices
|
|
22322
22387
|
clickToSegment: false,
|
|
22323
|
-
clickToSegmentRadius:
|
|
22324
|
-
|
|
22325
|
-
clickToSegmentBright: true
|
|
22388
|
+
clickToSegmentRadius: 3,
|
|
22389
|
+
// in mm
|
|
22390
|
+
clickToSegmentBright: true,
|
|
22391
|
+
clickToSegmentAutoIntensity: false,
|
|
22392
|
+
// new option, but keep clickToSegmentBright for backwards compatibility
|
|
22393
|
+
clickToSegmentIntensityMax: NaN,
|
|
22394
|
+
// NaN will use auto threshold (default flood fill behavior from before)
|
|
22395
|
+
clickToSegmentIntensityMin: NaN,
|
|
22396
|
+
// NaN will use auto threshold (default flood fill behavior from before)
|
|
22397
|
+
// 0 will use auto threshold (default flood fill behavior from before)
|
|
22398
|
+
// Take the voxel intensity at the click point and use this percentage +/- to threshold the flood fill operation.
|
|
22399
|
+
// If greater than 0, clickedVoxelIntensity +/- clickedVoxelIntensity * clickToSegmentPercent will be used
|
|
22400
|
+
// for the clickToSegmentIntensityMin and clickToSegmentIntensityMax values.
|
|
22401
|
+
clickToSegmentPercent: 0,
|
|
22402
|
+
clickToSegmentMaxDistanceMM: Number.POSITIVE_INFINITY,
|
|
22403
|
+
// default value is infinity for backwards compatibility with flood fill routine.
|
|
22404
|
+
clickToSegmentIs2D: false,
|
|
22405
|
+
selectionBoxLineThickness: 4,
|
|
22406
|
+
selectionBoxIsOutline: false,
|
|
22407
|
+
scrollRequiresFocus: false,
|
|
22408
|
+
// determines if the cavas need to be focused to scroll
|
|
22409
|
+
showMeasureUnits: true,
|
|
22410
|
+
// e.g. 20.2 vs 20.2 mm
|
|
22411
|
+
measureTextJustify: "center",
|
|
22412
|
+
// start, center, end
|
|
22413
|
+
measureTextColor: [1, 0, 0, 1],
|
|
22414
|
+
// red
|
|
22415
|
+
measureLineColor: [1, 0, 0, 1],
|
|
22416
|
+
// red
|
|
22417
|
+
measureTextHeight: 0.03
|
|
22326
22418
|
};
|
|
22327
22419
|
var INITIAL_SCENE_DATA = {
|
|
22328
22420
|
gamma: 1,
|
|
22329
22421
|
azimuth: 110,
|
|
22330
22422
|
elevation: 10,
|
|
22331
|
-
crosshairPos: vec38.
|
|
22423
|
+
crosshairPos: vec38.fromValues(0.5, 0.5, 0.5),
|
|
22332
22424
|
clipPlane: [0, 0, 0, 0],
|
|
22333
22425
|
clipPlaneDepthAziElev: [2, 0, 0],
|
|
22334
22426
|
volScaleMultiplier: 1,
|
|
22335
|
-
pan2Dxyzmm: vec46.
|
|
22427
|
+
pan2Dxyzmm: vec46.fromValues(0, 0, 0, 1),
|
|
22336
22428
|
clipThick: 2,
|
|
22337
22429
|
clipVolumeLow: [0, 0, 0],
|
|
22338
22430
|
clipVolumeHigh: [1, 1, 1]
|
|
@@ -22980,8 +23072,27 @@ var NVConnectome = class _NVConnectome extends NVMesh2 {
|
|
|
22980
23072
|
const nodes = this.nodes;
|
|
22981
23073
|
if (nodes && nodes.length > 0) {
|
|
22982
23074
|
const largest = nodes.reduce((a, b) => a.sizeValue > b.sizeValue ? a : b).sizeValue;
|
|
22983
|
-
|
|
22984
|
-
|
|
23075
|
+
let min, max;
|
|
23076
|
+
if (typeof this.nodeMinColor !== "undefined" && isFinite(this.nodeMinColor)) {
|
|
23077
|
+
min = this.nodeMinColor;
|
|
23078
|
+
} else {
|
|
23079
|
+
min = nodes[0].colorValue;
|
|
23080
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
23081
|
+
if (nodes[i].colorValue < min) {
|
|
23082
|
+
min = nodes[i].colorValue;
|
|
23083
|
+
}
|
|
23084
|
+
}
|
|
23085
|
+
}
|
|
23086
|
+
if (typeof this.nodeMaxColor !== "undefined" && isFinite(this.nodeMaxColor)) {
|
|
23087
|
+
max = this.nodeMaxColor;
|
|
23088
|
+
} else {
|
|
23089
|
+
max = nodes[0].colorValue;
|
|
23090
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
23091
|
+
if (nodes[i].colorValue > max) {
|
|
23092
|
+
max = nodes[i].colorValue;
|
|
23093
|
+
}
|
|
23094
|
+
}
|
|
23095
|
+
}
|
|
22985
23096
|
const lut = cmapper.colormap(this.nodeColormap, this.colormapInvert);
|
|
22986
23097
|
const lutNeg = cmapper.colormap(this.nodeColormapNegative, this.colormapInvert);
|
|
22987
23098
|
const hasNeg = "nodeColormapNegative" in this;
|
|
@@ -23112,14 +23223,29 @@ var NVConnectome = class _NVConnectome extends NVMesh2 {
|
|
|
23112
23223
|
let lut = cmapper.colormap(this.nodeColormap, this.colormapInvert);
|
|
23113
23224
|
let lutNeg = cmapper.colormap(this.nodeColormapNegative, this.colormapInvert);
|
|
23114
23225
|
let hasNeg = "nodeColormapNegative" in this;
|
|
23115
|
-
if (this.nodeMinColor === void 0
|
|
23116
|
-
|
|
23226
|
+
if (this.nodeMinColor === void 0) {
|
|
23227
|
+
this.nodeMinColor = NaN;
|
|
23228
|
+
}
|
|
23229
|
+
if (this.nodeMaxColor === void 0) {
|
|
23230
|
+
this.nodeMaxColor = NaN;
|
|
23117
23231
|
}
|
|
23118
|
-
if (this.edgeMin === void 0
|
|
23119
|
-
|
|
23232
|
+
if (this.edgeMin === void 0) {
|
|
23233
|
+
this.edgeMin = NaN;
|
|
23234
|
+
}
|
|
23235
|
+
if (this.edgeMax === void 0) {
|
|
23236
|
+
this.edgeMax = NaN;
|
|
23120
23237
|
}
|
|
23121
23238
|
let min = this.nodeMinColor;
|
|
23122
23239
|
let max = this.nodeMaxColor;
|
|
23240
|
+
if (!isFinite(min) || !isFinite(min)) {
|
|
23241
|
+
const nodes2 = this.nodes;
|
|
23242
|
+
min = nodes2[0].colorValue;
|
|
23243
|
+
max = nodes2[0].colorValue;
|
|
23244
|
+
for (let i = 0; i < nodes2.length; i++) {
|
|
23245
|
+
min = Math.min(min, nodes2[i].colorValue);
|
|
23246
|
+
max = Math.max(max, nodes2[i].colorValue);
|
|
23247
|
+
}
|
|
23248
|
+
}
|
|
23123
23249
|
const nodes = this.nodes;
|
|
23124
23250
|
const nNode = nodes.length;
|
|
23125
23251
|
for (let i = 0; i < nNode; i++) {
|
|
@@ -23152,35 +23278,45 @@ var NVConnectome = class _NVConnectome extends NVMesh2 {
|
|
|
23152
23278
|
lut = cmapper.colormap(this.edgeColormap, this.colormapInvert);
|
|
23153
23279
|
lutNeg = cmapper.colormap(this.edgeColormapNegative, this.colormapInvert);
|
|
23154
23280
|
hasNeg = "edgeColormapNegative" in this;
|
|
23155
|
-
min = this.edgeMin;
|
|
23156
|
-
max = this.edgeMax;
|
|
23157
23281
|
const edges = this.edges;
|
|
23158
|
-
|
|
23159
|
-
|
|
23160
|
-
|
|
23161
|
-
if (
|
|
23162
|
-
|
|
23163
|
-
|
|
23164
|
-
|
|
23165
|
-
|
|
23166
|
-
|
|
23167
|
-
|
|
23168
|
-
|
|
23169
|
-
|
|
23282
|
+
if (edges !== void 0 && edges.length > 0) {
|
|
23283
|
+
min = this.edgeMin;
|
|
23284
|
+
max = this.edgeMax;
|
|
23285
|
+
if (!isFinite(min) || !isFinite(min)) {
|
|
23286
|
+
min = edges[0].colorValue;
|
|
23287
|
+
max = edges[0].colorValue;
|
|
23288
|
+
for (let i = 0; i < edges.length; i++) {
|
|
23289
|
+
min = Math.min(min, edges[i].colorValue);
|
|
23290
|
+
max = Math.max(max, edges[i].colorValue);
|
|
23291
|
+
}
|
|
23292
|
+
}
|
|
23293
|
+
for (const edge of edges) {
|
|
23294
|
+
let color = edge.colorValue;
|
|
23295
|
+
const isNeg = hasNeg && color < 0;
|
|
23296
|
+
if (isNeg) {
|
|
23297
|
+
color = -color;
|
|
23298
|
+
}
|
|
23299
|
+
const radius = color * this.edgeScale;
|
|
23300
|
+
if (radius <= 0) {
|
|
23170
23301
|
continue;
|
|
23171
23302
|
}
|
|
23172
|
-
|
|
23173
|
-
|
|
23174
|
-
|
|
23175
|
-
|
|
23176
|
-
|
|
23177
|
-
|
|
23178
|
-
|
|
23179
|
-
|
|
23303
|
+
if (min < max) {
|
|
23304
|
+
if (color < min) {
|
|
23305
|
+
continue;
|
|
23306
|
+
}
|
|
23307
|
+
color = (color - min) / (max - min);
|
|
23308
|
+
} else {
|
|
23309
|
+
color = 1;
|
|
23310
|
+
}
|
|
23311
|
+
color = Math.round(Math.max(Math.min(255, color * 255))) * 4;
|
|
23312
|
+
let rgba = [lut[color], lut[color + 1], lut[color + 2], 255];
|
|
23313
|
+
if (isNeg) {
|
|
23314
|
+
rgba = [lutNeg[color], lutNeg[color + 1], lutNeg[color + 2], 255];
|
|
23315
|
+
}
|
|
23316
|
+
const pti = vec39.fromValues(nodes[edge.first].x, nodes[edge.first].y, nodes[edge.first].z);
|
|
23317
|
+
const ptj = vec39.fromValues(nodes[edge.second].x, nodes[edge.second].y, nodes[edge.second].z);
|
|
23318
|
+
NiivueObject3D.makeColoredCylinder(pts, tris, rgba255, pti, ptj, radius, rgba);
|
|
23180
23319
|
}
|
|
23181
|
-
const pti = vec39.fromValues(nodes[edge.first].x, nodes[edge.first].y, nodes[edge.first].z);
|
|
23182
|
-
const ptj = vec39.fromValues(nodes[edge.second].x, nodes[edge.second].y, nodes[edge.second].z);
|
|
23183
|
-
NiivueObject3D.makeColoredCylinder(pts, tris, rgba255, pti, ptj, radius, rgba);
|
|
23184
23320
|
}
|
|
23185
23321
|
const pts32 = new Float32Array(pts);
|
|
23186
23322
|
const tris32 = new Uint32Array(tris);
|
|
@@ -24019,7 +24155,7 @@ var defaultSaveImageOptions = {
|
|
|
24019
24155
|
isSaveDrawing: false,
|
|
24020
24156
|
volumeByIndex: 0
|
|
24021
24157
|
};
|
|
24022
|
-
var Niivue = class
|
|
24158
|
+
var Niivue = class {
|
|
24023
24159
|
/**
|
|
24024
24160
|
* @param options - options object to set modifiable Niivue properties
|
|
24025
24161
|
*/
|
|
@@ -24050,6 +24186,12 @@ var Niivue = class _Niivue {
|
|
|
24050
24186
|
// the color lookup table for drawing
|
|
24051
24187
|
__publicField(this, "drawOpacity", 0.8);
|
|
24052
24188
|
// opacity of drawing (default)
|
|
24189
|
+
__publicField(this, "clickToSegmentIsGrowing", false);
|
|
24190
|
+
// flag to indicate if the clickToSegment flood fill growing is in progress with left mouse down + drag
|
|
24191
|
+
__publicField(this, "clickToSegmentGrowingBitmap", null);
|
|
24192
|
+
// the bitmap of the growing flood fill
|
|
24193
|
+
__publicField(this, "clickToSegmentXY", [0, 0]);
|
|
24194
|
+
// the x,y location of the clickToSegment flood fill
|
|
24053
24195
|
__publicField(this, "renderDrawAmbientOcclusion", 0.4);
|
|
24054
24196
|
__publicField(this, "colorbarHeight", 0);
|
|
24055
24197
|
// height in pixels, set when colorbar is drawn
|
|
@@ -24067,6 +24209,7 @@ var Niivue = class _Niivue {
|
|
|
24067
24209
|
__publicField(this, "orientCubeShader");
|
|
24068
24210
|
__publicField(this, "orientCubeShaderVAO", null);
|
|
24069
24211
|
__publicField(this, "rectShader");
|
|
24212
|
+
__publicField(this, "rectOutlineShader");
|
|
24070
24213
|
__publicField(this, "renderShader");
|
|
24071
24214
|
__publicField(this, "lineShader");
|
|
24072
24215
|
__publicField(this, "line3DShader");
|
|
@@ -24119,7 +24262,20 @@ var Niivue = class _Niivue {
|
|
|
24119
24262
|
__publicField(this, "extentsMax");
|
|
24120
24263
|
// ResizeObserver
|
|
24121
24264
|
__publicField(this, "resizeObserver", null);
|
|
24122
|
-
|
|
24265
|
+
// syncOpts: Record<string, unknown> = {}
|
|
24266
|
+
__publicField(this, "syncOpts", {
|
|
24267
|
+
"3d": false,
|
|
24268
|
+
// legacy option
|
|
24269
|
+
"2d": false,
|
|
24270
|
+
// legacy option
|
|
24271
|
+
zoomPan: false,
|
|
24272
|
+
cal_min: false,
|
|
24273
|
+
cal_max: false,
|
|
24274
|
+
clipPlane: false,
|
|
24275
|
+
gamma: false,
|
|
24276
|
+
sliceType: false,
|
|
24277
|
+
crosshair: false
|
|
24278
|
+
});
|
|
24123
24279
|
__publicField(this, "readyForSync", false);
|
|
24124
24280
|
// UI Data
|
|
24125
24281
|
__publicField(this, "uiData", {
|
|
@@ -24178,7 +24334,8 @@ var Niivue = class _Niivue {
|
|
|
24178
24334
|
vols: [0],
|
|
24179
24335
|
// e.g. timeline for background volume only, e.g. [0,2] for first and third volumes
|
|
24180
24336
|
autoSizeMultiplanar: false,
|
|
24181
|
-
normalizeValues: false
|
|
24337
|
+
normalizeValues: false,
|
|
24338
|
+
isRangeCalMinMax: false
|
|
24182
24339
|
});
|
|
24183
24340
|
__publicField(this, "meshShaders", [
|
|
24184
24341
|
{
|
|
@@ -24578,8 +24735,16 @@ var Niivue = class _Niivue {
|
|
|
24578
24735
|
this.canvas.style.display = "block";
|
|
24579
24736
|
this.canvas.width = this.canvas.offsetWidth;
|
|
24580
24737
|
this.canvas.height = this.canvas.offsetHeight;
|
|
24581
|
-
window.addEventListener("resize",
|
|
24582
|
-
|
|
24738
|
+
window.addEventListener("resize", () => {
|
|
24739
|
+
requestAnimationFrame(() => {
|
|
24740
|
+
this.resizeListener();
|
|
24741
|
+
});
|
|
24742
|
+
});
|
|
24743
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
24744
|
+
requestAnimationFrame(() => {
|
|
24745
|
+
this.resizeListener();
|
|
24746
|
+
});
|
|
24747
|
+
});
|
|
24583
24748
|
this.resizeObserver.observe(this.canvas.parentElement);
|
|
24584
24749
|
}
|
|
24585
24750
|
this.registerInteractions();
|
|
@@ -24598,6 +24763,9 @@ var Niivue = class _Niivue {
|
|
|
24598
24763
|
* @see {@link https://niivue.github.io/niivue/features/sync.mesh.html | live demo usage}
|
|
24599
24764
|
*/
|
|
24600
24765
|
syncWith(otherNV, syncOpts = { "2d": true, "3d": true }) {
|
|
24766
|
+
if (!(otherNV instanceof Array)) {
|
|
24767
|
+
otherNV = [otherNV];
|
|
24768
|
+
}
|
|
24601
24769
|
this.otherNV = otherNV;
|
|
24602
24770
|
this.syncOpts = { ...syncOpts };
|
|
24603
24771
|
}
|
|
@@ -24613,9 +24781,55 @@ var Niivue = class _Niivue {
|
|
|
24613
24781
|
* @see {@link https://niivue.github.io/niivue/features/sync.mesh.html | live demo usage}
|
|
24614
24782
|
*/
|
|
24615
24783
|
broadcastTo(otherNV, syncOpts = { "2d": true, "3d": true }) {
|
|
24784
|
+
if (!(otherNV instanceof Array)) {
|
|
24785
|
+
otherNV = [otherNV];
|
|
24786
|
+
}
|
|
24616
24787
|
this.otherNV = otherNV;
|
|
24617
24788
|
this.syncOpts = syncOpts;
|
|
24618
24789
|
}
|
|
24790
|
+
doSync3d(otherNV) {
|
|
24791
|
+
otherNV.scene.renderAzimuth = this.scene.renderAzimuth;
|
|
24792
|
+
otherNV.scene.renderElevation = this.scene.renderElevation;
|
|
24793
|
+
otherNV.scene.volScaleMultiplier = this.scene.volScaleMultiplier;
|
|
24794
|
+
}
|
|
24795
|
+
// both crosshair and zoomPan
|
|
24796
|
+
doSync2d(otherNV) {
|
|
24797
|
+
const thisMM = this.frac2mm(this.scene.crosshairPos);
|
|
24798
|
+
otherNV.scene.crosshairPos = otherNV.mm2frac(thisMM);
|
|
24799
|
+
otherNV.scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24800
|
+
}
|
|
24801
|
+
doSyncGamma(otherNV) {
|
|
24802
|
+
const thisGamma = this.scene.gamma;
|
|
24803
|
+
const otherGamma = otherNV.scene.gamma;
|
|
24804
|
+
if (thisGamma !== otherGamma) {
|
|
24805
|
+
otherNV.setGamma(thisGamma);
|
|
24806
|
+
}
|
|
24807
|
+
}
|
|
24808
|
+
doSyncZoomPan(otherNV) {
|
|
24809
|
+
otherNV.scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24810
|
+
}
|
|
24811
|
+
doSyncCrosshair(otherNV) {
|
|
24812
|
+
const thisMM = this.frac2mm(this.scene.crosshairPos);
|
|
24813
|
+
otherNV.scene.crosshairPos = otherNV.mm2frac(thisMM);
|
|
24814
|
+
}
|
|
24815
|
+
doSyncCalMin(otherNV) {
|
|
24816
|
+
if (this.volumes[0].cal_min !== otherNV.volumes[0].cal_min) {
|
|
24817
|
+
otherNV.volumes[0].cal_min = this.volumes[0].cal_min;
|
|
24818
|
+
otherNV.updateGLVolume();
|
|
24819
|
+
}
|
|
24820
|
+
}
|
|
24821
|
+
doSyncCalMax(otherNV) {
|
|
24822
|
+
if (this.volumes[0].cal_max !== otherNV.volumes[0].cal_max) {
|
|
24823
|
+
otherNV.volumes[0].cal_max = this.volumes[0].cal_max;
|
|
24824
|
+
otherNV.updateGLVolume();
|
|
24825
|
+
}
|
|
24826
|
+
}
|
|
24827
|
+
doSyncSliceType(otherNV) {
|
|
24828
|
+
otherNV.setSliceType(this.opts.sliceType);
|
|
24829
|
+
}
|
|
24830
|
+
doSyncClipPlane(otherNV) {
|
|
24831
|
+
otherNV.setClipPlane(this.scene.clipPlaneDepthAziElev);
|
|
24832
|
+
}
|
|
24619
24833
|
/**
|
|
24620
24834
|
* Sync the scene controls (orientation, crosshair location, etc.) from one Niivue instance to another. useful for using one canvas to drive another.
|
|
24621
24835
|
* @internal
|
|
@@ -24632,46 +24846,39 @@ var Niivue = class _Niivue {
|
|
|
24632
24846
|
if (!this.gl.canvas.matches(":focus")) {
|
|
24633
24847
|
return;
|
|
24634
24848
|
}
|
|
24635
|
-
|
|
24636
|
-
|
|
24637
|
-
|
|
24638
|
-
|
|
24639
|
-
if (
|
|
24640
|
-
this.otherNV
|
|
24849
|
+
for (let i = 0; i < this.otherNV.length; i++) {
|
|
24850
|
+
if (this.otherNV[i] === this) {
|
|
24851
|
+
continue;
|
|
24852
|
+
}
|
|
24853
|
+
if (this.syncOpts.gamma) {
|
|
24854
|
+
this.doSyncGamma(this.otherNV[i]);
|
|
24855
|
+
}
|
|
24856
|
+
if (this.syncOpts.crosshair) {
|
|
24857
|
+
this.doSyncCrosshair(this.otherNV[i]);
|
|
24858
|
+
}
|
|
24859
|
+
if (this.syncOpts.zoomPan) {
|
|
24860
|
+
this.doSyncZoomPan(this.otherNV[i]);
|
|
24861
|
+
}
|
|
24862
|
+
if (this.syncOpts.sliceType) {
|
|
24863
|
+
this.doSyncSliceType(this.otherNV[i]);
|
|
24864
|
+
}
|
|
24865
|
+
if (this.syncOpts.cal_min) {
|
|
24866
|
+
this.doSyncCalMin(this.otherNV[i]);
|
|
24867
|
+
}
|
|
24868
|
+
if (this.syncOpts.cal_max) {
|
|
24869
|
+
this.doSyncCalMax(this.otherNV[i]);
|
|
24870
|
+
}
|
|
24871
|
+
if (this.syncOpts.clipPlane) {
|
|
24872
|
+
this.doSyncClipPlane(this.otherNV[i]);
|
|
24641
24873
|
}
|
|
24642
24874
|
if (this.syncOpts["2d"]) {
|
|
24643
|
-
this.
|
|
24644
|
-
this.otherNV.scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24875
|
+
this.doSync2d(this.otherNV[i]);
|
|
24645
24876
|
}
|
|
24646
24877
|
if (this.syncOpts["3d"]) {
|
|
24647
|
-
this.
|
|
24648
|
-
this.otherNV.scene.renderElevation = this.scene.renderElevation;
|
|
24649
|
-
this.otherNV.scene.volScaleMultiplier = this.scene.volScaleMultiplier;
|
|
24650
|
-
}
|
|
24651
|
-
this.otherNV.drawScene();
|
|
24652
|
-
this.otherNV.createOnLocationChange();
|
|
24653
|
-
} else if (Array.isArray(this.otherNV)) {
|
|
24654
|
-
for (let i = 0; i < this.otherNV.length; i++) {
|
|
24655
|
-
if (this.otherNV[i] === this) {
|
|
24656
|
-
continue;
|
|
24657
|
-
}
|
|
24658
|
-
const thisGamma = this.scene.gamma;
|
|
24659
|
-
const otherGamma = this.otherNV[i].scene.gamma;
|
|
24660
|
-
if (thisGamma !== otherGamma) {
|
|
24661
|
-
this.otherNV[i].setGamma(thisGamma);
|
|
24662
|
-
}
|
|
24663
|
-
if (this.syncOpts["2d"]) {
|
|
24664
|
-
this.otherNV[i].scene.crosshairPos = this.otherNV[i].mm2frac(thisMM);
|
|
24665
|
-
this.otherNV[i].scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24666
|
-
}
|
|
24667
|
-
if (this.syncOpts["3d"]) {
|
|
24668
|
-
this.otherNV[i].scene.renderAzimuth = this.scene.renderAzimuth;
|
|
24669
|
-
this.otherNV[i].scene.renderElevation = this.scene.renderElevation;
|
|
24670
|
-
this.otherNV[i].scene.volScaleMultiplier = this.scene.volScaleMultiplier;
|
|
24671
|
-
}
|
|
24672
|
-
this.otherNV[i].drawScene();
|
|
24673
|
-
this.otherNV[i].createOnLocationChange();
|
|
24878
|
+
this.doSync3d(this.otherNV[i]);
|
|
24674
24879
|
}
|
|
24880
|
+
this.otherNV[i].drawScene();
|
|
24881
|
+
this.otherNV[i].createOnLocationChange();
|
|
24675
24882
|
}
|
|
24676
24883
|
}
|
|
24677
24884
|
/** Not documented publicly for now
|
|
@@ -24766,6 +24973,8 @@ var Niivue = class _Niivue {
|
|
|
24766
24973
|
this.drawPenLocation = [NaN, NaN, NaN];
|
|
24767
24974
|
this.drawPenAxCorSag = -1;
|
|
24768
24975
|
this.uiData.mousedown = true;
|
|
24976
|
+
this.setDragStart(0, 0);
|
|
24977
|
+
this.setDragEnd(0, 0);
|
|
24769
24978
|
log.debug("mouse down");
|
|
24770
24979
|
log.debug(e);
|
|
24771
24980
|
const pos = this.getNoPaddingNoBorderCanvasRelativeMousePosition(e, this.gl.canvas);
|
|
@@ -24773,6 +24982,9 @@ var Niivue = class _Niivue {
|
|
|
24773
24982
|
return;
|
|
24774
24983
|
}
|
|
24775
24984
|
const [x, y] = [pos.x * this.uiData.dpr, pos.y * this.uiData.dpr];
|
|
24985
|
+
if (this.opts.clickToSegment) {
|
|
24986
|
+
this.clickToSegmentXY = [x, y];
|
|
24987
|
+
}
|
|
24776
24988
|
const label = this.getLabelAtPoint([x, y]);
|
|
24777
24989
|
if (label) {
|
|
24778
24990
|
if (label.onClick) {
|
|
@@ -24973,8 +25185,15 @@ var Niivue = class _Niivue {
|
|
|
24973
25185
|
if (this.drawPenFillPts.length > 0) {
|
|
24974
25186
|
this.drawPenFilled();
|
|
24975
25187
|
} else if (this.drawPenAxCorSag >= 0) {
|
|
24976
|
-
this.
|
|
25188
|
+
if (this.opts.clickToSegment) {
|
|
25189
|
+
const sumBitmap = this.sumBitmap(this.drawBitmap);
|
|
25190
|
+
const sumSeg = this.sumBitmap(this.clickToSegmentGrowingBitmap);
|
|
25191
|
+
if (sumSeg > sumBitmap) {
|
|
25192
|
+
this.updateBitmapFromClickToSegment();
|
|
25193
|
+
}
|
|
25194
|
+
}
|
|
24977
25195
|
}
|
|
25196
|
+
this.drawAddUndoBitmap();
|
|
24978
25197
|
this.drawPenLocation = [NaN, NaN, NaN];
|
|
24979
25198
|
this.drawPenAxCorSag = -1;
|
|
24980
25199
|
if (isFunction(this.onMouseUp)) {
|
|
@@ -24988,6 +25207,9 @@ var Niivue = class _Niivue {
|
|
|
24988
25207
|
const fracStart = this.canvasPos2frac([this.uiData.dragStart[0], this.uiData.dragStart[1]]);
|
|
24989
25208
|
const fracEnd = this.canvasPos2frac([this.uiData.dragEnd[0], this.uiData.dragEnd[1]]);
|
|
24990
25209
|
this.generateMouseUpCallback(fracStart, fracEnd);
|
|
25210
|
+
if (this.opts.dragMode === 6 /* roiSelection */) {
|
|
25211
|
+
return;
|
|
25212
|
+
}
|
|
24991
25213
|
if (this.opts.dragMode !== 1 /* contrast */) {
|
|
24992
25214
|
return;
|
|
24993
25215
|
}
|
|
@@ -25290,6 +25512,35 @@ var Niivue = class _Niivue {
|
|
|
25290
25512
|
if (this.thumbnailVisible) {
|
|
25291
25513
|
return;
|
|
25292
25514
|
}
|
|
25515
|
+
const dragStartSum = this.uiData.dragStart.reduce((a, b) => a + b, 0);
|
|
25516
|
+
const dragEndSum = this.uiData.dragEnd.reduce((a, b) => a + b, 0);
|
|
25517
|
+
const validDrag = dragStartSum > 0 && dragEndSum > 0;
|
|
25518
|
+
if (this.opts.dragMode === 6 /* roiSelection */ && validDrag) {
|
|
25519
|
+
const delta = e.deltaY > 0 ? 1 : -1;
|
|
25520
|
+
if (this.uiData.dragStart[0] < this.uiData.dragEnd[0]) {
|
|
25521
|
+
this.uiData.dragStart[0] -= delta;
|
|
25522
|
+
this.uiData.dragEnd[0] += delta;
|
|
25523
|
+
} else {
|
|
25524
|
+
this.uiData.dragStart[0] += delta;
|
|
25525
|
+
this.uiData.dragEnd[0] -= delta;
|
|
25526
|
+
}
|
|
25527
|
+
if (this.uiData.dragStart[1] < this.uiData.dragEnd[1]) {
|
|
25528
|
+
this.uiData.dragStart[1] -= delta;
|
|
25529
|
+
this.uiData.dragEnd[1] += delta;
|
|
25530
|
+
} else {
|
|
25531
|
+
this.uiData.dragStart[1] += delta;
|
|
25532
|
+
this.uiData.dragEnd[1] -= delta;
|
|
25533
|
+
}
|
|
25534
|
+
this.uiData.isDragging = true;
|
|
25535
|
+
this.drawScene();
|
|
25536
|
+
this.uiData.isDragging = false;
|
|
25537
|
+
const tileIdx = this.tileIndex(this.uiData.dragStart[0], this.uiData.dragStart[1]);
|
|
25538
|
+
this.generateMouseUpCallback(
|
|
25539
|
+
this.screenXY2TextureFrac(this.uiData.dragStart[0], this.uiData.dragStart[1], tileIdx),
|
|
25540
|
+
this.screenXY2TextureFrac(this.uiData.dragEnd[0], this.uiData.dragEnd[1], tileIdx)
|
|
25541
|
+
);
|
|
25542
|
+
return;
|
|
25543
|
+
}
|
|
25293
25544
|
const rect = this.canvas.getBoundingClientRect();
|
|
25294
25545
|
if (e.deltaY < 0) {
|
|
25295
25546
|
this.sliceScroll2D(-0.01, e.clientX - rect.left, e.clientY - rect.top);
|
|
@@ -25734,6 +25985,16 @@ var Niivue = class _Niivue {
|
|
|
25734
25985
|
this.opts.isCornerOrientationText = isCornerOrientationText;
|
|
25735
25986
|
this.updateGLVolume();
|
|
25736
25987
|
}
|
|
25988
|
+
/**
|
|
25989
|
+
* determine proportion of screen real estate devoted to rendering in multiplanar view.
|
|
25990
|
+
* @param fraction - proportion of screen devoted to primary (hero) image (0 to disable)
|
|
25991
|
+
* @example niivue.setHeroImage(0.5)
|
|
25992
|
+
* @see {@link https://niivue.github.io/niivue/features/layout.html | live demo usage}
|
|
25993
|
+
*/
|
|
25994
|
+
setHeroImage(fraction) {
|
|
25995
|
+
this.opts.heroImageFraction = fraction;
|
|
25996
|
+
this.drawScene();
|
|
25997
|
+
}
|
|
25737
25998
|
/**
|
|
25738
25999
|
* control whether 2D slices use radiological or neurological convention.
|
|
25739
26000
|
* @param isRadiologicalConvention - new display convention
|
|
@@ -26783,6 +27044,10 @@ var Niivue = class _Niivue {
|
|
|
26783
27044
|
}
|
|
26784
27045
|
// not included in public docs
|
|
26785
27046
|
sliceScroll2D(posChange, x, y, isDelta = true) {
|
|
27047
|
+
if (this.opts.scrollRequiresFocus && this.canvas !== document.activeElement) {
|
|
27048
|
+
log.warn("Canvas element does not have focus. Scroll events will not be processed.");
|
|
27049
|
+
return;
|
|
27050
|
+
}
|
|
26786
27051
|
if (this.inGraphTile(x, y)) {
|
|
26787
27052
|
let vol = this.volumes[0].frame4D;
|
|
26788
27053
|
if (posChange > 0) {
|
|
@@ -27474,6 +27739,7 @@ var Niivue = class _Niivue {
|
|
|
27474
27739
|
}
|
|
27475
27740
|
const vx = this.back.dims[1] * this.back.dims[2] * this.back.dims[3];
|
|
27476
27741
|
this.drawBitmap = new Uint8Array(vx);
|
|
27742
|
+
this.clickToSegmentGrowingBitmap = new Uint8Array(vx);
|
|
27477
27743
|
this.drawClearAllUndoBitmaps();
|
|
27478
27744
|
this.drawAddUndoBitmap();
|
|
27479
27745
|
this.drawTexture = this.r8Tex(this.drawTexture, TEXTURE7_DRAW, this.back.dims, true);
|
|
@@ -27647,6 +27913,23 @@ var Niivue = class _Niivue {
|
|
|
27647
27913
|
y = Math.min(Math.max(y, 0), dy - 1);
|
|
27648
27914
|
z = Math.min(Math.max(z, 0), dz - 1);
|
|
27649
27915
|
this.drawBitmap[x + y * dx + z * dx * dy] = penValue;
|
|
27916
|
+
const isAx = this.drawPenAxCorSag === 0;
|
|
27917
|
+
const isCor = this.drawPenAxCorSag === 1;
|
|
27918
|
+
const isSag = this.drawPenAxCorSag === 2;
|
|
27919
|
+
if (this.opts.penSize > 1) {
|
|
27920
|
+
const halfPenSize = Math.floor(this.opts.penSize / 2);
|
|
27921
|
+
for (let i = -halfPenSize; i <= halfPenSize; i++) {
|
|
27922
|
+
for (let j = -halfPenSize; j <= halfPenSize; j++) {
|
|
27923
|
+
if (isAx) {
|
|
27924
|
+
this.drawBitmap[x + i + (y + j) * dx + z * dx * dy] = penValue;
|
|
27925
|
+
} else if (isCor) {
|
|
27926
|
+
this.drawBitmap[x + i + y * dx + (z + j) * dx * dy] = penValue;
|
|
27927
|
+
} else if (isSag) {
|
|
27928
|
+
this.drawBitmap[x + (y + j) * dx + (z + i) * dx * dy] = penValue;
|
|
27929
|
+
}
|
|
27930
|
+
}
|
|
27931
|
+
}
|
|
27932
|
+
}
|
|
27650
27933
|
}
|
|
27651
27934
|
// not included in public docs
|
|
27652
27935
|
// create line between to voxels in drawing
|
|
@@ -27728,6 +28011,98 @@ var Niivue = class _Niivue {
|
|
|
27728
28011
|
}
|
|
27729
28012
|
}
|
|
27730
28013
|
}
|
|
28014
|
+
/**
|
|
28015
|
+
* Performs a 1-voxel binary dilation on a connected cluster within the drawing mask using the drawFloodFillCore function.
|
|
28016
|
+
*
|
|
28017
|
+
* @param seedXYZ - voxel index of the seed voxel in the mask array.
|
|
28018
|
+
* @param neighbors - Number of neighbors to consider for connectivity and dilation (6, 18, or 26).
|
|
28019
|
+
*/
|
|
28020
|
+
drawingBinaryDilationWithSeed(seedXYZ, neighbors = 6) {
|
|
28021
|
+
try {
|
|
28022
|
+
let xyz2vx = function(pt) {
|
|
28023
|
+
return pt[0] + pt[1] * nx + pt[2] * nxy;
|
|
28024
|
+
};
|
|
28025
|
+
const mask = this.drawBitmap;
|
|
28026
|
+
const xDim = this.back.dims[1];
|
|
28027
|
+
const yDim = this.back.dims[2];
|
|
28028
|
+
const zDim = this.back.dims[3];
|
|
28029
|
+
const nx = xDim;
|
|
28030
|
+
const nxy = xDim * yDim;
|
|
28031
|
+
const totalVoxels = nxy * zDim;
|
|
28032
|
+
const seedIndex = xyz2vx(seedXYZ);
|
|
28033
|
+
if (seedIndex < 0 || seedIndex >= totalVoxels) {
|
|
28034
|
+
throw new Error("Seed index is out of bounds.");
|
|
28035
|
+
}
|
|
28036
|
+
const seedValue = mask[seedIndex];
|
|
28037
|
+
if (seedValue === 0) {
|
|
28038
|
+
throw new Error("Seed voxel is not part of a filled cluster.");
|
|
28039
|
+
}
|
|
28040
|
+
const img = mask.slice();
|
|
28041
|
+
for (let i = 0; i < totalVoxels; i++) {
|
|
28042
|
+
img[i] = img[i] === seedValue ? 1 : 0;
|
|
28043
|
+
}
|
|
28044
|
+
this.drawFloodFillCore(img, seedIndex, neighbors);
|
|
28045
|
+
const outputMask = mask.slice();
|
|
28046
|
+
const neighborOffsets = [];
|
|
28047
|
+
const offsets6 = [-nxy, nxy, -xDim, xDim, -1, 1];
|
|
28048
|
+
neighborOffsets.push(...offsets6);
|
|
28049
|
+
if (neighbors > 6) {
|
|
28050
|
+
neighborOffsets.push(
|
|
28051
|
+
-xDim - 1,
|
|
28052
|
+
-xDim + 1,
|
|
28053
|
+
xDim - 1,
|
|
28054
|
+
xDim + 1,
|
|
28055
|
+
-nxy - xDim,
|
|
28056
|
+
-nxy + xDim,
|
|
28057
|
+
-nxy - 1,
|
|
28058
|
+
-nxy + 1,
|
|
28059
|
+
nxy - xDim,
|
|
28060
|
+
nxy + xDim,
|
|
28061
|
+
nxy - 1,
|
|
28062
|
+
nxy + 1
|
|
28063
|
+
);
|
|
28064
|
+
}
|
|
28065
|
+
if (neighbors > 18) {
|
|
28066
|
+
neighborOffsets.push(
|
|
28067
|
+
-nxy - xDim - 1,
|
|
28068
|
+
-nxy - xDim + 1,
|
|
28069
|
+
-nxy + xDim - 1,
|
|
28070
|
+
-nxy + xDim + 1,
|
|
28071
|
+
nxy - xDim - 1,
|
|
28072
|
+
nxy - xDim + 1,
|
|
28073
|
+
nxy + xDim - 1,
|
|
28074
|
+
nxy + xDim + 1
|
|
28075
|
+
);
|
|
28076
|
+
}
|
|
28077
|
+
for (let idx = 0; idx < totalVoxels; idx++) {
|
|
28078
|
+
if (img[idx] === 2) {
|
|
28079
|
+
const x = idx % xDim;
|
|
28080
|
+
const y = Math.floor(idx % nxy / xDim);
|
|
28081
|
+
const z = Math.floor(idx / nxy);
|
|
28082
|
+
for (const offset of neighborOffsets) {
|
|
28083
|
+
const neighborIdx = idx + offset;
|
|
28084
|
+
if (neighborIdx < 0 || neighborIdx >= totalVoxels) {
|
|
28085
|
+
continue;
|
|
28086
|
+
}
|
|
28087
|
+
const nx2 = neighborIdx % xDim;
|
|
28088
|
+
const ny = Math.floor(neighborIdx % nxy / xDim);
|
|
28089
|
+
const nz = Math.floor(neighborIdx / nxy);
|
|
28090
|
+
if (Math.abs(nx2 - x) > 1 || Math.abs(ny - y) > 1 || Math.abs(nz - z) > 1) {
|
|
28091
|
+
continue;
|
|
28092
|
+
}
|
|
28093
|
+
if (mask[neighborIdx] === 0) {
|
|
28094
|
+
outputMask[neighborIdx] = seedValue;
|
|
28095
|
+
}
|
|
28096
|
+
}
|
|
28097
|
+
}
|
|
28098
|
+
}
|
|
28099
|
+
this.drawBitmap = outputMask;
|
|
28100
|
+
this.drawAddUndoBitmap();
|
|
28101
|
+
this.refreshDrawing(true);
|
|
28102
|
+
} catch (error) {
|
|
28103
|
+
log.error("Error in drawingBinaryDilationWithSeed:", error);
|
|
28104
|
+
}
|
|
28105
|
+
}
|
|
27731
28106
|
// a voxel can be defined as having 6, 18 or 26 neighbors:
|
|
27732
28107
|
// 6: neighbors share faces (distance=1)
|
|
27733
28108
|
// 18: neighbors share faces (distance=1) or edges (1.4)
|
|
@@ -27809,7 +28184,7 @@ var Niivue = class _Niivue {
|
|
|
27809
28184
|
}
|
|
27810
28185
|
// not included in public docs
|
|
27811
28186
|
// set all connected voxels in drawing to new color
|
|
27812
|
-
drawFloodFill(seedXYZ, newColor = 0, growSelectedCluster = 0, forceMin = NaN, forceMax = NaN, neighbors = 6) {
|
|
28187
|
+
drawFloodFill(seedXYZ, newColor = 0, growSelectedCluster = 0, forceMin = NaN, forceMax = NaN, neighbors = 6, maxDistanceMM = Number.POSITIVE_INFINITY, is2D = false) {
|
|
27813
28188
|
if (!this.drawBitmap) {
|
|
27814
28189
|
throw new Error("drawBitmap undefined");
|
|
27815
28190
|
}
|
|
@@ -27827,13 +28202,46 @@ var Niivue = class _Niivue {
|
|
|
27827
28202
|
const nx = dims[0];
|
|
27828
28203
|
const nxy = nx * dims[1];
|
|
27829
28204
|
const nxyz = nxy * dims[2];
|
|
27830
|
-
|
|
28205
|
+
let img = this.drawBitmap.slice();
|
|
28206
|
+
let drawBitmap = this.drawBitmap;
|
|
28207
|
+
if (this.clickToSegmentIsGrowing) {
|
|
28208
|
+
img = this.clickToSegmentGrowingBitmap.slice();
|
|
28209
|
+
drawBitmap = this.clickToSegmentGrowingBitmap;
|
|
28210
|
+
}
|
|
27831
28211
|
if (img.length !== nxy * dims[2]) {
|
|
27832
28212
|
return;
|
|
27833
28213
|
}
|
|
28214
|
+
let constrainXYZ = -1;
|
|
28215
|
+
if (is2D && this.drawPenAxCorSag === 0 /* AXIAL */) {
|
|
28216
|
+
constrainXYZ = 2;
|
|
28217
|
+
} else if (is2D && this.drawPenAxCorSag === 1 /* CORONAL */) {
|
|
28218
|
+
constrainXYZ = 1;
|
|
28219
|
+
} else if (is2D && this.drawPenAxCorSag === 2 /* SAGITTAL */) {
|
|
28220
|
+
constrainXYZ = 0;
|
|
28221
|
+
}
|
|
28222
|
+
function vx2xyz(vx) {
|
|
28223
|
+
const Z = Math.floor(vx / nxy);
|
|
28224
|
+
const Y = Math.floor((vx - Z * nxy) / nx);
|
|
28225
|
+
const X = Math.floor(vx % nx);
|
|
28226
|
+
return [X, Y, Z];
|
|
28227
|
+
}
|
|
27834
28228
|
function xyz2vx(pt) {
|
|
27835
28229
|
return pt[0] + pt[1] * nx + pt[2] * nxy;
|
|
27836
28230
|
}
|
|
28231
|
+
const vx2mm = (xyz) => {
|
|
28232
|
+
return this.vox2mm(xyz, this.back.matRAS);
|
|
28233
|
+
};
|
|
28234
|
+
const seedMM = vx2mm(seedXYZ);
|
|
28235
|
+
const maxDistanceMM2 = maxDistanceMM ** 2;
|
|
28236
|
+
function isWithinDistance(vx) {
|
|
28237
|
+
const xzyVox = vx2xyz(vx);
|
|
28238
|
+
if (constrainXYZ >= 0 && xzyVox[constrainXYZ] !== seedXYZ[constrainXYZ]) {
|
|
28239
|
+
return false;
|
|
28240
|
+
}
|
|
28241
|
+
const xyzMM = vx2mm(xzyVox);
|
|
28242
|
+
const dist2 = (xyzMM[0] - seedMM[0]) ** 2 + (xyzMM[1] - seedMM[1]) ** 2 + (xyzMM[2] - seedMM[2]) ** 2;
|
|
28243
|
+
return dist2 <= maxDistanceMM2;
|
|
28244
|
+
}
|
|
27837
28245
|
const seedVx = xyz2vx(seedXYZ);
|
|
27838
28246
|
const seedColor = img[seedVx];
|
|
27839
28247
|
if (seedColor === newColor) {
|
|
@@ -27846,7 +28254,10 @@ var Niivue = class _Niivue {
|
|
|
27846
28254
|
}
|
|
27847
28255
|
for (let i = 1; i < nxyz; i++) {
|
|
27848
28256
|
img[i] = 0;
|
|
27849
|
-
if (
|
|
28257
|
+
if (drawBitmap[i] === seedColor) {
|
|
28258
|
+
if (!isWithinDistance(i)) {
|
|
28259
|
+
continue;
|
|
28260
|
+
}
|
|
27850
28261
|
img[i] = 1;
|
|
27851
28262
|
}
|
|
27852
28263
|
}
|
|
@@ -27876,6 +28287,9 @@ var Niivue = class _Niivue {
|
|
|
27876
28287
|
for (let i = 1; i < nxyz; i++) {
|
|
27877
28288
|
img[i] = 0;
|
|
27878
28289
|
if (backImg[i] >= mn && backImg[i] <= mx) {
|
|
28290
|
+
if (!isWithinDistance(i)) {
|
|
28291
|
+
continue;
|
|
28292
|
+
}
|
|
27879
28293
|
img[i] = 1;
|
|
27880
28294
|
}
|
|
27881
28295
|
}
|
|
@@ -27884,11 +28298,19 @@ var Niivue = class _Niivue {
|
|
|
27884
28298
|
}
|
|
27885
28299
|
for (let i = 1; i < nxyz; i++) {
|
|
27886
28300
|
if (img[i] === 2) {
|
|
27887
|
-
|
|
28301
|
+
drawBitmap[i] = newColor;
|
|
27888
28302
|
}
|
|
27889
28303
|
}
|
|
27890
|
-
this.
|
|
27891
|
-
|
|
28304
|
+
if (!this.clickToSegmentIsGrowing) {
|
|
28305
|
+
this.drawBitmap = drawBitmap.slice();
|
|
28306
|
+
if (!this.opts.clickToSegment) {
|
|
28307
|
+
this.drawAddUndoBitmap();
|
|
28308
|
+
}
|
|
28309
|
+
this.refreshDrawing(true, this.clickToSegmentIsGrowing);
|
|
28310
|
+
} else {
|
|
28311
|
+
this.clickToSegmentGrowingBitmap = drawBitmap;
|
|
28312
|
+
this.refreshDrawing(true, this.clickToSegmentIsGrowing);
|
|
28313
|
+
}
|
|
27892
28314
|
}
|
|
27893
28315
|
// not included in public docs
|
|
27894
28316
|
// given series of line segments, connect first and last
|
|
@@ -28048,6 +28470,7 @@ var Niivue = class _Niivue {
|
|
|
28048
28470
|
this.drawClearAllUndoBitmaps();
|
|
28049
28471
|
this.rgbaTex(this.drawTexture, TEXTURE7_DRAW, [2, 2, 2, 2], true);
|
|
28050
28472
|
this.drawBitmap = null;
|
|
28473
|
+
this.clickToSegmentGrowingBitmap = null;
|
|
28051
28474
|
this.drawScene();
|
|
28052
28475
|
}
|
|
28053
28476
|
/**
|
|
@@ -28056,7 +28479,7 @@ var Niivue = class _Niivue {
|
|
|
28056
28479
|
* @example niivue.refreshDrawing();
|
|
28057
28480
|
* @see {@link https://niivue.github.io/niivue/features/cactus.html | live demo usage}
|
|
28058
28481
|
*/
|
|
28059
|
-
refreshDrawing(isForceRedraw = true) {
|
|
28482
|
+
refreshDrawing(isForceRedraw = true, useClickToSegmentBitmap = false) {
|
|
28060
28483
|
if (!this.back?.dims) {
|
|
28061
28484
|
throw new Error("back.dims undefined");
|
|
28062
28485
|
}
|
|
@@ -28085,7 +28508,7 @@ var Niivue = class _Niivue {
|
|
|
28085
28508
|
dims[3],
|
|
28086
28509
|
this.gl.RED,
|
|
28087
28510
|
this.gl.UNSIGNED_BYTE,
|
|
28088
|
-
this.drawBitmap
|
|
28511
|
+
useClickToSegmentBitmap ? this.clickToSegmentGrowingBitmap : this.drawBitmap
|
|
28089
28512
|
);
|
|
28090
28513
|
if (isForceRedraw) {
|
|
28091
28514
|
this.drawScene();
|
|
@@ -28497,6 +28920,8 @@ var Niivue = class _Niivue {
|
|
|
28497
28920
|
gl.bindVertexArray(this.unusedVAO);
|
|
28498
28921
|
this.rectShader = new Shader(gl, vertRectShader, fragRectShader);
|
|
28499
28922
|
this.rectShader.use(gl);
|
|
28923
|
+
this.rectOutlineShader = new Shader(gl, vertRectShader, fragRectOutlineShader);
|
|
28924
|
+
this.rectOutlineShader.use(gl);
|
|
28500
28925
|
this.lineShader = new Shader(gl, vertLineShader, fragRectShader);
|
|
28501
28926
|
this.lineShader.use(gl);
|
|
28502
28927
|
this.line3DShader = new Shader(gl, vertLine3DShader, fragRectShader);
|
|
@@ -28649,14 +29074,37 @@ var Niivue = class _Niivue {
|
|
|
28649
29074
|
}
|
|
28650
29075
|
/**
|
|
28651
29076
|
* basic statistics for selected voxel-based image
|
|
28652
|
-
* @param
|
|
28653
|
-
*
|
|
28654
|
-
*
|
|
28655
|
-
*
|
|
29077
|
+
* @param options - an object containing the following properties:
|
|
29078
|
+
* - layer: selects image to describe
|
|
29079
|
+
* - masks: optional binary images to filter voxels
|
|
29080
|
+
* - drawingIsMask: a boolean indicating if the drawing is used as a mask
|
|
29081
|
+
* - roiIsMask: a boolean indicating if the ROI is used as a mask
|
|
29082
|
+
* - startVox: the starting voxel coordinates
|
|
29083
|
+
* - endVox: the ending voxel coordinates
|
|
29084
|
+
* @returns numeric values to describe image or regions of images
|
|
29085
|
+
* @example
|
|
29086
|
+
* niivue.getDescriptives({
|
|
29087
|
+
* layer: 0,
|
|
29088
|
+
* masks: [],
|
|
29089
|
+
* drawingIsMask: true, // drawingIsMask and roiIsMask are mutually exclusive
|
|
29090
|
+
* roiIsMask: false,
|
|
29091
|
+
* startVox: [10, 20, 30], // ignored if roiIsMask is false
|
|
29092
|
+
* endVox: [40, 50, 60] // ignored if roiIsMask is false
|
|
29093
|
+
* });
|
|
28656
29094
|
* @see {@link https://niivue.github.io/niivue/features/draw2.html | live demo usage}
|
|
28657
29095
|
*/
|
|
28658
|
-
getDescriptives(
|
|
29096
|
+
getDescriptives(options) {
|
|
29097
|
+
const {
|
|
29098
|
+
layer = 0,
|
|
29099
|
+
masks = [],
|
|
29100
|
+
drawingIsMask = false,
|
|
29101
|
+
roiIsMask = false,
|
|
29102
|
+
startVox = [0, 0, 0],
|
|
29103
|
+
endVox = [0, 0, 0]
|
|
29104
|
+
} = options;
|
|
29105
|
+
let area = null;
|
|
28659
29106
|
const hdr = this.volumes[layer].hdr;
|
|
29107
|
+
const pixDimsRAS = this.volumes[layer].pixDimsRAS;
|
|
28660
29108
|
let slope = hdr.scl_slope;
|
|
28661
29109
|
if (isNaN(slope)) {
|
|
28662
29110
|
slope = 1;
|
|
@@ -28694,6 +29142,65 @@ var Niivue = class _Niivue {
|
|
|
28694
29142
|
mask[i] = 0;
|
|
28695
29143
|
}
|
|
28696
29144
|
}
|
|
29145
|
+
} else if (masks.length < 1 && roiIsMask) {
|
|
29146
|
+
mask.fill(0);
|
|
29147
|
+
console.log("startVox", startVox);
|
|
29148
|
+
console.log("endVox", endVox);
|
|
29149
|
+
let constantDim = -1;
|
|
29150
|
+
if (startVox[0] === endVox[0]) {
|
|
29151
|
+
constantDim = 0;
|
|
29152
|
+
} else if (startVox[1] === endVox[1]) {
|
|
29153
|
+
constantDim = 1;
|
|
29154
|
+
} else if (startVox[2] === endVox[2]) {
|
|
29155
|
+
constantDim = 2;
|
|
29156
|
+
} else {
|
|
29157
|
+
console.error("Error: No constant dimension found.");
|
|
29158
|
+
return;
|
|
29159
|
+
}
|
|
29160
|
+
const dims = [0, 1, 2];
|
|
29161
|
+
const varDims = dims.filter((dim) => dim !== constantDim);
|
|
29162
|
+
const centerVox = [];
|
|
29163
|
+
centerVox[constantDim] = startVox[constantDim];
|
|
29164
|
+
centerVox[varDims[0]] = (startVox[varDims[0]] + endVox[varDims[0]]) / 2;
|
|
29165
|
+
centerVox[varDims[1]] = (startVox[varDims[1]] + endVox[varDims[1]]) / 2;
|
|
29166
|
+
const radiusX = Math.abs(endVox[varDims[0]] - startVox[varDims[0]]) / 2;
|
|
29167
|
+
const radiusY = Math.abs(endVox[varDims[1]] - startVox[varDims[1]]) / 2;
|
|
29168
|
+
const xdim = hdr.dims[1];
|
|
29169
|
+
const ydim = hdr.dims[2];
|
|
29170
|
+
const minVarDim0 = Math.max(0, Math.floor(centerVox[varDims[0]] - radiusX));
|
|
29171
|
+
const maxVarDim0 = Math.min(hdr.dims[varDims[0] + 1] - 1, Math.ceil(centerVox[varDims[0]] + radiusX));
|
|
29172
|
+
const minVarDim1 = Math.max(0, Math.floor(centerVox[varDims[1]] - radiusY));
|
|
29173
|
+
const maxVarDim1 = Math.min(hdr.dims[varDims[1] + 1] - 1, Math.ceil(centerVox[varDims[1]] + radiusY));
|
|
29174
|
+
const constDimVal = centerVox[constantDim];
|
|
29175
|
+
if (constDimVal < 0 || constDimVal >= hdr.dims[constantDim + 1]) {
|
|
29176
|
+
console.error("Error: Constant dimension value is out of bounds.");
|
|
29177
|
+
return;
|
|
29178
|
+
}
|
|
29179
|
+
for (let i = minVarDim0; i <= maxVarDim0; i++) {
|
|
29180
|
+
for (let j = minVarDim1; j <= maxVarDim1; j++) {
|
|
29181
|
+
const voxel = [];
|
|
29182
|
+
voxel[constantDim] = constDimVal;
|
|
29183
|
+
voxel[varDims[0]] = i;
|
|
29184
|
+
voxel[varDims[1]] = j;
|
|
29185
|
+
const di = (voxel[varDims[0]] - centerVox[varDims[0]]) / radiusX;
|
|
29186
|
+
const dj = (voxel[varDims[1]] - centerVox[varDims[1]]) / radiusY;
|
|
29187
|
+
const distSq = di * di + dj * dj;
|
|
29188
|
+
if (distSq <= 1) {
|
|
29189
|
+
const x = voxel[0];
|
|
29190
|
+
const y = voxel[1];
|
|
29191
|
+
const z = voxel[2];
|
|
29192
|
+
const index = z * xdim * ydim + y * xdim + x;
|
|
29193
|
+
mask[index] = 1;
|
|
29194
|
+
}
|
|
29195
|
+
}
|
|
29196
|
+
}
|
|
29197
|
+
const voxelArea = pixDimsRAS[varDims[0] + 1] * pixDimsRAS[varDims[1] + 1];
|
|
29198
|
+
const numMaskedVoxels = mask.reduce((count, value) => count + (value === 1 ? 1 : 0), 0);
|
|
29199
|
+
area = numMaskedVoxels * voxelArea;
|
|
29200
|
+
const radiusX_mm = radiusX * pixDimsRAS[varDims[0] + 1];
|
|
29201
|
+
const radiusY_mm = radiusY * pixDimsRAS[varDims[1] + 1];
|
|
29202
|
+
const areaEllipse = Math.PI * radiusX_mm * radiusY_mm;
|
|
29203
|
+
area = areaEllipse;
|
|
28697
29204
|
}
|
|
28698
29205
|
let k = 0;
|
|
28699
29206
|
let M = 0;
|
|
@@ -28747,7 +29254,8 @@ var Niivue = class _Niivue {
|
|
|
28747
29254
|
cal_min: this.volumes[layer].cal_min,
|
|
28748
29255
|
cal_max: this.volumes[layer].cal_max,
|
|
28749
29256
|
robust_min: this.volumes[layer].robust_min,
|
|
28750
|
-
robust_max: this.volumes[layer].robust_max
|
|
29257
|
+
robust_max: this.volumes[layer].robust_max,
|
|
29258
|
+
area
|
|
28751
29259
|
};
|
|
28752
29260
|
}
|
|
28753
29261
|
// not included in public docs
|
|
@@ -30225,6 +30733,29 @@ var Niivue = class _Niivue {
|
|
|
30225
30733
|
const pos = [(x - this.graph.LTWH[0]) / this.graph.LTWH[2], (y - this.graph.LTWH[1]) / this.graph.LTWH[3]];
|
|
30226
30734
|
return pos[0] > 0 && pos[1] > 0 && pos[0] <= 1 && pos[1] <= 1;
|
|
30227
30735
|
}
|
|
30736
|
+
// update drawBitmap if it differs from clickTosegmentBitmap
|
|
30737
|
+
updateBitmapFromClickToSegment() {
|
|
30738
|
+
if (this.clickToSegmentGrowingBitmap === null) {
|
|
30739
|
+
return;
|
|
30740
|
+
}
|
|
30741
|
+
if (this.drawBitmap === null) {
|
|
30742
|
+
return;
|
|
30743
|
+
}
|
|
30744
|
+
if (this.clickToSegmentGrowingBitmap.length !== this.drawBitmap.length) {
|
|
30745
|
+
return;
|
|
30746
|
+
}
|
|
30747
|
+
const nvx = this.drawBitmap.length;
|
|
30748
|
+
for (let i = 0; i < nvx; i++) {
|
|
30749
|
+
this.drawBitmap[i] = this.clickToSegmentGrowingBitmap[i];
|
|
30750
|
+
}
|
|
30751
|
+
}
|
|
30752
|
+
sumBitmap(img) {
|
|
30753
|
+
let sum = 0;
|
|
30754
|
+
for (let i = 0; i < img.length; i++) {
|
|
30755
|
+
sum += img[i];
|
|
30756
|
+
}
|
|
30757
|
+
return sum;
|
|
30758
|
+
}
|
|
30228
30759
|
// not included in public docs
|
|
30229
30760
|
// handle mouse click event on canvas
|
|
30230
30761
|
mouseClick(x, y, posChange = 0, isDelta = true) {
|
|
@@ -30274,7 +30805,7 @@ var Niivue = class _Niivue {
|
|
|
30274
30805
|
if (axCorSag > 2 /* SAGITTAL */) {
|
|
30275
30806
|
continue;
|
|
30276
30807
|
}
|
|
30277
|
-
|
|
30808
|
+
let texFrac = this.screenXY2TextureFrac(x, y, i, false);
|
|
30278
30809
|
if (texFrac[0] < 0) {
|
|
30279
30810
|
continue;
|
|
30280
30811
|
}
|
|
@@ -30301,31 +30832,75 @@ var Niivue = class _Niivue {
|
|
|
30301
30832
|
this.scene.crosshairPos = vec311.clone(texFrac);
|
|
30302
30833
|
}
|
|
30303
30834
|
if (this.opts.drawingEnabled) {
|
|
30304
|
-
|
|
30305
|
-
const ptMM = this.frac2mm(this.scene.crosshairPos);
|
|
30835
|
+
let pt = this.frac2vox(this.scene.crosshairPos);
|
|
30306
30836
|
if (this.opts.clickToSegment) {
|
|
30307
|
-
|
|
30308
|
-
|
|
30837
|
+
texFrac = this.screenXY2TextureFrac(this.clickToSegmentXY[0], this.clickToSegmentXY[1], i, false);
|
|
30838
|
+
pt = this.frac2vox(texFrac);
|
|
30839
|
+
let diff = 0;
|
|
30840
|
+
let threshold = this.opts.clickToSegmentPercent + diff;
|
|
30841
|
+
if (this.uiData.mousedown) {
|
|
30842
|
+
const xDiff = (this.clickToSegmentXY[0] - x) / this.gl.canvas.width;
|
|
30843
|
+
const yDiff = (this.clickToSegmentXY[1] - y) / this.gl.canvas.height;
|
|
30844
|
+
diff = Math.max(Math.abs(xDiff), Math.abs(yDiff));
|
|
30845
|
+
threshold = this.opts.clickToSegmentPercent + diff;
|
|
30846
|
+
}
|
|
30847
|
+
let voxelIntensity = this.back.getValue(pt[0], pt[1], pt[2]);
|
|
30848
|
+
if (this.opts.clickToSegmentAutoIntensity) {
|
|
30849
|
+
if (threshold !== 0) {
|
|
30850
|
+
if (voxelIntensity === 0) {
|
|
30851
|
+
voxelIntensity = 0.01;
|
|
30852
|
+
}
|
|
30853
|
+
this.opts.clickToSegmentIntensityMax = voxelIntensity * (1 + threshold);
|
|
30854
|
+
this.opts.clickToSegmentIntensityMin = voxelIntensity * (1 - threshold);
|
|
30855
|
+
}
|
|
30856
|
+
if (voxelIntensity > (this.back.cal_min + this.back.cal_max) * 0.5) {
|
|
30857
|
+
this.opts.clickToSegmentBright = true;
|
|
30858
|
+
} else {
|
|
30859
|
+
this.opts.clickToSegmentBright = false;
|
|
30860
|
+
}
|
|
30861
|
+
}
|
|
30309
30862
|
const brightOrDark = this.opts.clickToSegmentBright ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
|
|
30310
|
-
this.drawPenFillPts = [];
|
|
30311
30863
|
this.drawPenAxCorSag = axCorSag;
|
|
30312
|
-
|
|
30313
|
-
const
|
|
30314
|
-
const
|
|
30315
|
-
|
|
30316
|
-
|
|
30317
|
-
const
|
|
30318
|
-
const
|
|
30319
|
-
|
|
30320
|
-
|
|
30321
|
-
this.
|
|
30322
|
-
|
|
30323
|
-
|
|
30324
|
-
}
|
|
30864
|
+
if (this.drawPenAxCorSag === 0 /* AXIAL */) {
|
|
30865
|
+
const pixDims = [this.back.pixDimsRAS[0], this.back.pixDimsRAS[1]];
|
|
30866
|
+
const minPixDim = Math.min(...pixDims);
|
|
30867
|
+
this.opts.penSize = Math.ceil(this.opts.clickToSegmentRadius / minPixDim);
|
|
30868
|
+
} else if (this.drawPenAxCorSag === 1 /* CORONAL */) {
|
|
30869
|
+
const pixDims = [this.back.pixDimsRAS[0], this.back.pixDimsRAS[2]];
|
|
30870
|
+
const minPixDim = Math.min(...pixDims);
|
|
30871
|
+
this.opts.penSize = Math.ceil(this.opts.clickToSegmentRadius / minPixDim);
|
|
30872
|
+
} else if (this.drawPenAxCorSag === 2 /* SAGITTAL */) {
|
|
30873
|
+
const pixDims = [this.back.pixDimsRAS[1], this.back.pixDimsRAS[2]];
|
|
30874
|
+
const minPixDim = Math.min(...pixDims);
|
|
30875
|
+
this.opts.penSize = Math.ceil(this.opts.clickToSegmentRadius / minPixDim);
|
|
30325
30876
|
}
|
|
30877
|
+
this.drawPt(pt[0], pt[1], pt[2], this.opts.penValue);
|
|
30878
|
+
if (diff !== 0) {
|
|
30879
|
+
this.clickToSegmentIsGrowing = true;
|
|
30880
|
+
this.clickToSegmentGrowingBitmap = this.drawBitmap.slice();
|
|
30881
|
+
} else {
|
|
30882
|
+
this.clickToSegmentIsGrowing = false;
|
|
30883
|
+
}
|
|
30884
|
+
this.drawFloodFill(
|
|
30885
|
+
[pt[0], pt[1], pt[2]],
|
|
30886
|
+
0,
|
|
30887
|
+
brightOrDark,
|
|
30888
|
+
this.opts.clickToSegmentIntensityMin,
|
|
30889
|
+
this.opts.clickToSegmentIntensityMax,
|
|
30890
|
+
this.opts.floodFillNeighbors,
|
|
30891
|
+
this.opts.clickToSegmentMaxDistanceMM,
|
|
30892
|
+
this.opts.clickToSegmentIs2D
|
|
30893
|
+
);
|
|
30326
30894
|
this.drawScene();
|
|
30327
30895
|
this.createOnLocationChange(axCorSag);
|
|
30328
|
-
|
|
30896
|
+
if (this.clickToSegmentIsGrowing) {
|
|
30897
|
+
return;
|
|
30898
|
+
}
|
|
30899
|
+
const info = this.getDescriptives({
|
|
30900
|
+
layer: 0,
|
|
30901
|
+
masks: [],
|
|
30902
|
+
drawingIsMask: true
|
|
30903
|
+
});
|
|
30329
30904
|
this.onClickToSegment({ mL: info.volumeML, mm3: info.volumeMM3 });
|
|
30330
30905
|
return;
|
|
30331
30906
|
}
|
|
@@ -30385,30 +30960,39 @@ var Niivue = class _Niivue {
|
|
|
30385
30960
|
}
|
|
30386
30961
|
const frac10cm = 100 / fovMM[0];
|
|
30387
30962
|
const pix10cm = frac10cm * ltwh[2];
|
|
30388
|
-
const
|
|
30389
|
-
const
|
|
30963
|
+
const pix1cm = Math.max(Math.round(pix10cm * 0.1), 2);
|
|
30964
|
+
const pixLeft = Math.floor(ltwh[0] + 0.5 * ltwh[2] - 0.5 * pix10cm);
|
|
30965
|
+
const thick = Number(this.opts.rulerWidth);
|
|
30966
|
+
const pixTop = Math.floor(ltwh[1] + ltwh[3] - pix1cm) + 0.5 * thick;
|
|
30390
30967
|
const startXYendXY = [pixLeft, pixTop, pixLeft + pix10cm, pixTop];
|
|
30391
|
-
|
|
30968
|
+
let outlineColor = [0, 0, 0, 1];
|
|
30969
|
+
if (this.opts.rulerColor[0] + this.opts.rulerColor[1] + this.opts.rulerColor[2] < 0.8) {
|
|
30970
|
+
outlineColor = [1, 1, 1, 1];
|
|
30971
|
+
}
|
|
30972
|
+
this.drawRuler10cm(startXYendXY, outlineColor, thick + 1);
|
|
30973
|
+
this.drawRuler10cm(startXYendXY, this.opts.rulerColor, thick);
|
|
30392
30974
|
}
|
|
30393
30975
|
// not included in public docs
|
|
30394
30976
|
// draw 10cm ruler at desired coordinates
|
|
30395
|
-
drawRuler10cm(startXYendXY) {
|
|
30977
|
+
drawRuler10cm(startXYendXY, rulerColor, rulerWidth = 1) {
|
|
30396
30978
|
if (!this.lineShader) {
|
|
30397
30979
|
throw new Error("lineShader undefined");
|
|
30398
30980
|
}
|
|
30399
30981
|
this.gl.bindVertexArray(this.genericVAO);
|
|
30400
30982
|
this.lineShader.use(this.gl);
|
|
30401
|
-
this.gl.uniform4fv(this.lineShader.uniforms.lineColor,
|
|
30983
|
+
this.gl.uniform4fv(this.lineShader.uniforms.lineColor, rulerColor);
|
|
30402
30984
|
this.gl.uniform2fv(this.lineShader.uniforms.canvasWidthHeight, [this.gl.canvas.width, this.gl.canvas.height]);
|
|
30403
|
-
this.gl.uniform1f(this.lineShader.uniforms.thickness,
|
|
30985
|
+
this.gl.uniform1f(this.lineShader.uniforms.thickness, rulerWidth);
|
|
30404
30986
|
this.gl.uniform4fv(this.lineShader.uniforms.startXYendXY, startXYendXY);
|
|
30405
30987
|
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
30406
30988
|
const w1cm = -0.1 * (startXYendXY[0] - startXYendXY[2]);
|
|
30407
|
-
const b = startXYendXY[1];
|
|
30408
|
-
const t = b -
|
|
30409
|
-
const t2 = b -
|
|
30989
|
+
const b = startXYendXY[1] - Math.floor(0.5 * this.opts.rulerWidth);
|
|
30990
|
+
const t = Math.floor(b - 0.35 * w1cm);
|
|
30991
|
+
const t2 = Math.floor(b - 0.7 * w1cm);
|
|
30410
30992
|
for (let i = 0; i < 11; i++) {
|
|
30411
|
-
|
|
30993
|
+
let l = startXYendXY[0] + i * w1cm;
|
|
30994
|
+
l = Math.max(l, startXYendXY[0] + 0.5 * rulerWidth);
|
|
30995
|
+
l = Math.min(l, startXYendXY[2] - 0.5 * rulerWidth);
|
|
30412
30996
|
const xyxy = [l, b, l, t];
|
|
30413
30997
|
if (i % 5 === 0) {
|
|
30414
30998
|
xyxy[3] = t2;
|
|
@@ -30486,6 +31070,24 @@ var Niivue = class _Niivue {
|
|
|
30486
31070
|
// not included in public docs
|
|
30487
31071
|
// draw line between start/end points and text to report length
|
|
30488
31072
|
drawMeasurementTool(startXYendXY) {
|
|
31073
|
+
function extendTo(x0, y0, x1, y1, distance) {
|
|
31074
|
+
const x = x0 - x1;
|
|
31075
|
+
const y = y0 - y1;
|
|
31076
|
+
if (x === 0 && y === 0) {
|
|
31077
|
+
return {
|
|
31078
|
+
origin: [x1 + distance, y1],
|
|
31079
|
+
terminus: [x1 + distance, y1]
|
|
31080
|
+
};
|
|
31081
|
+
}
|
|
31082
|
+
const c = Math.sqrt(x * x + y * y);
|
|
31083
|
+
const dX = distance * x / c;
|
|
31084
|
+
const dY = distance * y / c;
|
|
31085
|
+
return {
|
|
31086
|
+
origin: [x0 + dX, y0 + dY],
|
|
31087
|
+
// next to start point
|
|
31088
|
+
terminus: [x1 - dX, y1 - dY]
|
|
31089
|
+
};
|
|
31090
|
+
}
|
|
30489
31091
|
const gl = this.gl;
|
|
30490
31092
|
gl.bindVertexArray(this.genericVAO);
|
|
30491
31093
|
gl.depthFunc(gl.ALWAYS);
|
|
@@ -30500,9 +31102,9 @@ var Niivue = class _Niivue {
|
|
|
30500
31102
|
gl.uniform1f(this.lineShader.uniforms.thickness, this.opts.rulerWidth);
|
|
30501
31103
|
gl.uniform4fv(this.lineShader.uniforms.startXYendXY, startXYendXY);
|
|
30502
31104
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
30503
|
-
const
|
|
30504
|
-
|
|
30505
|
-
gl.uniform4fv(this.lineShader.uniforms.lineColor,
|
|
31105
|
+
const measureLineColor = this.opts.measureLineColor;
|
|
31106
|
+
measureLineColor[3] = 1;
|
|
31107
|
+
gl.uniform4fv(this.lineShader.uniforms.lineColor, measureLineColor);
|
|
30506
31108
|
const w = this.opts.rulerWidth;
|
|
30507
31109
|
gl.uniform1f(this.lineShader.uniforms.thickness, w * 2);
|
|
30508
31110
|
let sXYeXY = [startXYendXY[0], startXYendXY[1] - w, startXYendXY[0], startXYendXY[1] + w];
|
|
@@ -30528,8 +31130,30 @@ var Niivue = class _Niivue {
|
|
|
30528
31130
|
if (lenMM > 99) {
|
|
30529
31131
|
decimals = 0;
|
|
30530
31132
|
}
|
|
30531
|
-
|
|
30532
|
-
this.
|
|
31133
|
+
let stringMM = lenMM.toFixed(decimals);
|
|
31134
|
+
if (this.opts.showMeasureUnits) {
|
|
31135
|
+
stringMM = `${stringMM} mm`;
|
|
31136
|
+
}
|
|
31137
|
+
let textCoords = startXYendXY;
|
|
31138
|
+
const [x0, y0, x1, y1] = startXYendXY;
|
|
31139
|
+
const { origin, terminus } = extendTo(x0, y0, x1, y1, 30);
|
|
31140
|
+
switch (this.opts.measureTextJustify) {
|
|
31141
|
+
case "start":
|
|
31142
|
+
textCoords = [...origin, ...origin.map((point) => point + 1)];
|
|
31143
|
+
break;
|
|
31144
|
+
case "end":
|
|
31145
|
+
textCoords = textCoords = [...terminus, ...terminus.map((point) => point + 1)];
|
|
31146
|
+
break;
|
|
31147
|
+
default:
|
|
31148
|
+
textCoords = startXYendXY;
|
|
31149
|
+
break;
|
|
31150
|
+
}
|
|
31151
|
+
this.drawTextBetween(
|
|
31152
|
+
textCoords,
|
|
31153
|
+
stringMM,
|
|
31154
|
+
this.opts.measureTextHeight / this.opts.textHeight,
|
|
31155
|
+
this.opts.measureTextColor
|
|
31156
|
+
);
|
|
30533
31157
|
}
|
|
30534
31158
|
gl.bindVertexArray(this.unusedVAO);
|
|
30535
31159
|
}
|
|
@@ -30543,20 +31167,42 @@ var Niivue = class _Niivue {
|
|
|
30543
31167
|
if (!this.rectShader) {
|
|
30544
31168
|
throw new Error("rectShader undefined");
|
|
30545
31169
|
}
|
|
30546
|
-
this.
|
|
30547
|
-
|
|
30548
|
-
|
|
30549
|
-
|
|
30550
|
-
|
|
30551
|
-
this.
|
|
30552
|
-
|
|
30553
|
-
|
|
30554
|
-
|
|
30555
|
-
|
|
30556
|
-
|
|
30557
|
-
|
|
30558
|
-
|
|
30559
|
-
|
|
31170
|
+
if (!this.opts.selectionBoxIsOutline) {
|
|
31171
|
+
this.rectShader.use(this.gl);
|
|
31172
|
+
this.gl.enable(this.gl.BLEND);
|
|
31173
|
+
this.gl.uniform4fv(this.rectShader.uniforms.lineColor, lineColor);
|
|
31174
|
+
this.gl.uniform2fv(this.rectShader.uniforms.canvasWidthHeight, [this.gl.canvas.width, this.gl.canvas.height]);
|
|
31175
|
+
this.gl.uniform4f(
|
|
31176
|
+
this.rectShader.uniforms.leftTopWidthHeight,
|
|
31177
|
+
leftTopWidthHeight[0],
|
|
31178
|
+
leftTopWidthHeight[1],
|
|
31179
|
+
leftTopWidthHeight[2],
|
|
31180
|
+
leftTopWidthHeight[3]
|
|
31181
|
+
);
|
|
31182
|
+
this.gl.bindVertexArray(this.genericVAO);
|
|
31183
|
+
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
31184
|
+
this.gl.bindVertexArray(this.unusedVAO);
|
|
31185
|
+
} else {
|
|
31186
|
+
this.drawCircle(leftTopWidthHeight, lineColor, 0.1);
|
|
31187
|
+
this.rectOutlineShader.use(this.gl);
|
|
31188
|
+
this.gl.enable(this.gl.BLEND);
|
|
31189
|
+
this.gl.uniform1f(this.rectOutlineShader.uniforms.thickness, this.opts.selectionBoxLineThickness);
|
|
31190
|
+
this.gl.uniform4fv(this.rectOutlineShader.uniforms.lineColor, lineColor);
|
|
31191
|
+
this.gl.uniform2fv(this.rectOutlineShader.uniforms.canvasWidthHeight, [
|
|
31192
|
+
this.gl.canvas.width,
|
|
31193
|
+
this.gl.canvas.height
|
|
31194
|
+
]);
|
|
31195
|
+
this.gl.uniform4f(
|
|
31196
|
+
this.rectOutlineShader.uniforms.leftTopWidthHeight,
|
|
31197
|
+
leftTopWidthHeight[0],
|
|
31198
|
+
leftTopWidthHeight[1],
|
|
31199
|
+
leftTopWidthHeight[2],
|
|
31200
|
+
leftTopWidthHeight[3]
|
|
31201
|
+
);
|
|
31202
|
+
this.gl.bindVertexArray(this.genericVAO);
|
|
31203
|
+
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
31204
|
+
this.gl.bindVertexArray(this.unusedVAO);
|
|
31205
|
+
}
|
|
30560
31206
|
}
|
|
30561
31207
|
drawCircle(leftTopWidthHeight, circleColor = this.opts.fontColor, fillPercent = 1) {
|
|
30562
31208
|
if (!this.circleShader) {
|
|
@@ -30582,6 +31228,10 @@ var Niivue = class _Niivue {
|
|
|
30582
31228
|
// not included in public docs
|
|
30583
31229
|
// draw a rectangle at desired location
|
|
30584
31230
|
drawSelectionBox(leftTopWidthHeight) {
|
|
31231
|
+
if (this.opts.dragMode === 6 /* roiSelection */) {
|
|
31232
|
+
this.drawCircle(leftTopWidthHeight, this.opts.selectionBoxColor, 0.1);
|
|
31233
|
+
return;
|
|
31234
|
+
}
|
|
30585
31235
|
this.drawRect(leftTopWidthHeight, this.opts.selectionBoxColor);
|
|
30586
31236
|
}
|
|
30587
31237
|
// not included in public docs
|
|
@@ -31361,6 +32011,18 @@ var Niivue = class _Niivue {
|
|
|
31361
32011
|
}
|
|
31362
32012
|
draw2D(leftTopWidthHeight, axCorSag, customMM = NaN, imageWidthHeight = [NaN, NaN]) {
|
|
31363
32013
|
const padLeftTop = [NaN, NaN];
|
|
32014
|
+
if (imageWidthHeight[0] === Infinity) {
|
|
32015
|
+
const volScale = this.sliceScale().volScale;
|
|
32016
|
+
let scale = this.scaleSlice(volScale[0], volScale[1], [0, 0], [leftTopWidthHeight[2], leftTopWidthHeight[3]]);
|
|
32017
|
+
if (axCorSag === 1 /* CORONAL */) {
|
|
32018
|
+
scale = this.scaleSlice(volScale[0], volScale[2], [0, 0], [leftTopWidthHeight[2], leftTopWidthHeight[3]]);
|
|
32019
|
+
}
|
|
32020
|
+
if (axCorSag === 2 /* SAGITTAL */) {
|
|
32021
|
+
scale = this.scaleSlice(volScale[1], volScale[2], [0, 0], [leftTopWidthHeight[2], leftTopWidthHeight[3]]);
|
|
32022
|
+
}
|
|
32023
|
+
imageWidthHeight[0] = scale[2];
|
|
32024
|
+
imageWidthHeight[1] = scale[3];
|
|
32025
|
+
}
|
|
31364
32026
|
if (isNaN(imageWidthHeight[0])) {
|
|
31365
32027
|
this.draw2DMain(leftTopWidthHeight, axCorSag, customMM);
|
|
31366
32028
|
} else {
|
|
@@ -31572,10 +32234,10 @@ var Niivue = class _Niivue {
|
|
|
31572
32234
|
if (graph.opacity <= 0 || graph.LTWH[2] <= 5 || graph.LTWH[3] <= 5) {
|
|
31573
32235
|
return;
|
|
31574
32236
|
}
|
|
31575
|
-
if (graph.LTWH[0] + graph.LTWH[2] > this.gl.canvas.width) {
|
|
32237
|
+
if (Math.floor(graph.LTWH[0] + graph.LTWH[2]) > this.gl.canvas.width) {
|
|
31576
32238
|
return;
|
|
31577
32239
|
}
|
|
31578
|
-
if (graph.LTWH[1] + graph.LTWH[3] > this.gl.canvas.height) {
|
|
32240
|
+
if (Math.floor(graph.LTWH[1] + graph.LTWH[3]) > this.gl.canvas.height) {
|
|
31579
32241
|
return;
|
|
31580
32242
|
}
|
|
31581
32243
|
graph.backColor = [0.15, 0.15, 0.15, graph.opacity];
|
|
@@ -31645,6 +32307,12 @@ var Niivue = class _Niivue {
|
|
|
31645
32307
|
mx = Math.max(v, mx);
|
|
31646
32308
|
}
|
|
31647
32309
|
}
|
|
32310
|
+
const volMn = this.volumes[vols[0]].cal_min;
|
|
32311
|
+
const volMx = this.volumes[vols[0]].cal_max;
|
|
32312
|
+
if (graph.isRangeCalMinMax && volMn < volMx && isFinite(volMn) && isFinite(volMx)) {
|
|
32313
|
+
mn = volMn;
|
|
32314
|
+
mx = volMx;
|
|
32315
|
+
}
|
|
31648
32316
|
if (graph.normalizeValues && mx > mn) {
|
|
31649
32317
|
const range = mx - mn;
|
|
31650
32318
|
for (let j = 0; j < graph.lines.length; j++) {
|
|
@@ -32400,22 +33068,28 @@ var Niivue = class _Niivue {
|
|
|
32400
33068
|
}
|
|
32401
33069
|
const gl = this.gl;
|
|
32402
33070
|
const mm = this.frac2mm(this.scene.crosshairPos, 0, isSliceMM);
|
|
33071
|
+
let radius = 1;
|
|
33072
|
+
const [mn, mx, range] = this.sceneExtentsMinMax(isSliceMM);
|
|
33073
|
+
if (this.volumes.length > 0) {
|
|
33074
|
+
if (!this.back) {
|
|
33075
|
+
throw new Error("back undefined");
|
|
33076
|
+
}
|
|
33077
|
+
radius = 0.5 * Math.min(Math.min(this.back.pixDims[1], this.back.pixDims[2]), this.back.pixDims[3]);
|
|
33078
|
+
} else if (range[0] < 50 || range[0] > 1e3) {
|
|
33079
|
+
radius = range[0] * 0.02;
|
|
33080
|
+
}
|
|
33081
|
+
radius *= this.opts.crosshairWidth;
|
|
33082
|
+
if (this.opts?.crosshairWidthUnit === "percent") {
|
|
33083
|
+
radius = range[0] * this.opts.crosshairWidth * 0.5 * 0.01;
|
|
33084
|
+
}
|
|
33085
|
+
if (this.opts?.crosshairWidthUnit === "mm") {
|
|
33086
|
+
radius = this.opts.crosshairWidth * 0.5;
|
|
33087
|
+
}
|
|
32403
33088
|
if (this.crosshairs3D === null || this.crosshairs3D.mm[0] !== mm[0] || this.crosshairs3D.mm[1] !== mm[1] || this.crosshairs3D.mm[2] !== mm[2]) {
|
|
32404
33089
|
if (this.crosshairs3D !== null) {
|
|
32405
33090
|
gl.deleteBuffer(this.crosshairs3D.indexBuffer);
|
|
32406
33091
|
gl.deleteBuffer(this.crosshairs3D.vertexBuffer);
|
|
32407
33092
|
}
|
|
32408
|
-
const [mn, mx, range] = this.sceneExtentsMinMax(isSliceMM);
|
|
32409
|
-
let radius = 1;
|
|
32410
|
-
if (this.volumes.length > 0) {
|
|
32411
|
-
if (!this.back) {
|
|
32412
|
-
throw new Error("back undefined");
|
|
32413
|
-
}
|
|
32414
|
-
radius = 0.5 * Math.min(Math.min(this.back.pixDims[1], this.back.pixDims[2]), this.back.pixDims[3]);
|
|
32415
|
-
} else if (range[0] < 50 || range[0] > 1e3) {
|
|
32416
|
-
radius = range[0] * 0.02;
|
|
32417
|
-
}
|
|
32418
|
-
radius *= this.opts.crosshairWidth;
|
|
32419
33093
|
this.crosshairs3D = NiivueObject3D.generateCrosshairs(this.gl, 1, mm, mn, mx, radius, 20, this.opts.crosshairGap);
|
|
32420
33094
|
this.crosshairs3D.mm = mm;
|
|
32421
33095
|
}
|
|
@@ -32434,6 +33108,7 @@ var Niivue = class _Niivue {
|
|
|
32434
33108
|
);
|
|
32435
33109
|
}
|
|
32436
33110
|
gl.uniformMatrix4fv(crosshairsShader.uniforms.mvpMtx, false, mvpMtx);
|
|
33111
|
+
gl.disable(gl.CULL_FACE);
|
|
32437
33112
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.crosshairs3D.indexBuffer);
|
|
32438
33113
|
gl.enable(gl.DEPTH_TEST);
|
|
32439
33114
|
const color = [...this.opts.crosshairColor];
|
|
@@ -32584,9 +33259,9 @@ var Niivue = class _Niivue {
|
|
|
32584
33259
|
}
|
|
32585
33260
|
// not included in public docs
|
|
32586
33261
|
// note: we also have a "sliceScale" method, which could be confusing
|
|
32587
|
-
scaleSlice(w, h,
|
|
32588
|
-
const canvasW = this.effectiveCanvasWidth() -
|
|
32589
|
-
const canvasH = this.effectiveCanvasHeight() -
|
|
33262
|
+
scaleSlice(w, h, padPixelsWH = [0, 0], canvasWH = [0, 0]) {
|
|
33263
|
+
const canvasW = canvasWH[0] === 0 ? this.effectiveCanvasWidth() - padPixelsWH[0] : canvasWH[0] - padPixelsWH[0];
|
|
33264
|
+
const canvasH = canvasWH[1] === 0 ? this.effectiveCanvasHeight() - padPixelsWH[1] : canvasWH[1] - padPixelsWH[1];
|
|
32590
33265
|
let scalePix = canvasW / w;
|
|
32591
33266
|
if (h * scalePix > canvasH) {
|
|
32592
33267
|
scalePix = canvasH / h;
|
|
@@ -33035,7 +33710,6 @@ var Niivue = class _Niivue {
|
|
|
33035
33710
|
if (this.volumes.length === 0 || typeof this.volumes[0].dims === "undefined") {
|
|
33036
33711
|
if (this.meshes.length > 0) {
|
|
33037
33712
|
this.screenSlices = [];
|
|
33038
|
-
this.opts.sliceType = 4 /* RENDER */;
|
|
33039
33713
|
this.draw3D();
|
|
33040
33714
|
if (this.opts.isColorbar) {
|
|
33041
33715
|
this.drawColorbar();
|
|
@@ -33079,6 +33753,8 @@ var Niivue = class _Niivue {
|
|
|
33079
33753
|
if (this.sliceMosaicString.length > 0) {
|
|
33080
33754
|
this.drawMosaic(this.sliceMosaicString);
|
|
33081
33755
|
} else {
|
|
33756
|
+
const heroImageWH = [0, 0];
|
|
33757
|
+
let isHeroImage = false;
|
|
33082
33758
|
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
|
|
33083
33759
|
this.screenSlices = [];
|
|
33084
33760
|
if (this.opts.sliceType === 0 /* AXIAL */) {
|
|
@@ -33088,6 +33764,9 @@ var Niivue = class _Niivue {
|
|
|
33088
33764
|
} else if (this.opts.sliceType === 2 /* SAGITTAL */) {
|
|
33089
33765
|
this.draw2D([0, 0, 0, 0], 2);
|
|
33090
33766
|
} else {
|
|
33767
|
+
let padPixelsWH = function(cols, rows) {
|
|
33768
|
+
return [(cols - 1) * pad + cols * innerPad, (rows - 1) * pad + rows * innerPad];
|
|
33769
|
+
};
|
|
33091
33770
|
let isShowRender = false;
|
|
33092
33771
|
if (this.opts.multiplanarForceRender) {
|
|
33093
33772
|
isShowRender = true;
|
|
@@ -33114,20 +33793,42 @@ var Niivue = class _Niivue {
|
|
|
33114
33793
|
log.debug("multiplanarPadPixels must be numeric");
|
|
33115
33794
|
}
|
|
33116
33795
|
const pad = parseFloat(`${this.opts.multiplanarPadPixels}`);
|
|
33117
|
-
|
|
33796
|
+
let innerPad = this.opts.tileMargin;
|
|
33797
|
+
if (innerPad < 0) {
|
|
33798
|
+
innerPad = 2 * (2 + Math.ceil(Math.max(this.opts.textHeight, 0.01) * Math.min(this.gl.canvas.height, this.gl.canvas.width)));
|
|
33799
|
+
}
|
|
33800
|
+
let canvasWH = [this.effectiveCanvasWidth(), this.effectiveCanvasHeight()];
|
|
33801
|
+
if (this.opts.heroImageFraction > 0 && this.opts.heroImageFraction < 1) {
|
|
33802
|
+
isShowRender = false;
|
|
33803
|
+
isHeroImage = true;
|
|
33804
|
+
if (canvasWH[0] > canvasWH[1] && this.opts.multiplanarLayout !== 3 /* ROW */) {
|
|
33805
|
+
heroImageWH[0] = canvasWH[0] * this.opts.heroImageFraction;
|
|
33806
|
+
} else {
|
|
33807
|
+
heroImageWH[1] = canvasWH[1] * this.opts.heroImageFraction;
|
|
33808
|
+
}
|
|
33809
|
+
canvasWH = [canvasWH[0] - heroImageWH[0], canvasWH[1] - heroImageWH[1]];
|
|
33810
|
+
}
|
|
33811
|
+
const ltwh2x2 = this.scaleSlice(
|
|
33812
|
+
volScale[0] + volScale[1],
|
|
33813
|
+
volScale[1] + volScale[2],
|
|
33814
|
+
padPixelsWH(2, 2),
|
|
33815
|
+
canvasWH
|
|
33816
|
+
);
|
|
33118
33817
|
const mx = Math.max(Math.max(volScale[1], volScale[2]), volScale[0]);
|
|
33119
33818
|
const ltwh3x1 = this.scaleSlice(
|
|
33120
33819
|
volScale[0] + volScale[0] + volScale[1],
|
|
33121
33820
|
Math.max(volScale[1], volScale[2]),
|
|
33122
|
-
|
|
33821
|
+
padPixelsWH(3, 1),
|
|
33822
|
+
canvasWH
|
|
33123
33823
|
);
|
|
33124
33824
|
const ltwh4x1 = this.scaleSlice(
|
|
33125
33825
|
volScale[0] + volScale[0] + volScale[1] + mx,
|
|
33126
33826
|
Math.max(volScale[1], volScale[2]),
|
|
33127
|
-
|
|
33827
|
+
padPixelsWH(4, 1),
|
|
33828
|
+
canvasWH
|
|
33128
33829
|
);
|
|
33129
|
-
const ltwh1x3 = this.scaleSlice(mx, volScale[1] + volScale[2] + volScale[2],
|
|
33130
|
-
const ltwh1x4 = this.scaleSlice(mx, volScale[1] + volScale[2] + volScale[2] + mx,
|
|
33830
|
+
const ltwh1x3 = this.scaleSlice(mx, volScale[1] + volScale[2] + volScale[2], padPixelsWH(1, 3), canvasWH);
|
|
33831
|
+
const ltwh1x4 = this.scaleSlice(mx, volScale[1] + volScale[2] + volScale[2] + mx, padPixelsWH(1, 4), canvasWH);
|
|
33131
33832
|
let isDraw3D = !isDrawPenDown && (maxVols < 2 || !isDrawGraph);
|
|
33132
33833
|
let isDrawColumn = false;
|
|
33133
33834
|
let isDrawGrid = false;
|
|
@@ -33150,22 +33851,39 @@ var Niivue = class _Niivue {
|
|
|
33150
33851
|
let ltwh = ltwh2x2;
|
|
33151
33852
|
if (isDrawColumn) {
|
|
33152
33853
|
ltwh = ltwh1x3;
|
|
33153
|
-
if (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh1x4[4] >= ltwh1x3[4]) {
|
|
33854
|
+
if (!isHeroImage && (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh1x4[4] >= ltwh1x3[4])) {
|
|
33154
33855
|
ltwh = ltwh1x4;
|
|
33155
33856
|
} else {
|
|
33156
33857
|
isDraw3D = false;
|
|
33157
33858
|
}
|
|
33158
33859
|
} else if (isDrawRow) {
|
|
33159
33860
|
ltwh = ltwh3x1;
|
|
33160
|
-
if (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh4x1[4] >= ltwh3x1[4]) {
|
|
33861
|
+
if (!isHeroImage && (isShowRender || this.opts.multiplanarShowRender === 2 /* AUTO */ && ltwh4x1[4] >= ltwh3x1[4])) {
|
|
33161
33862
|
ltwh = ltwh4x1;
|
|
33162
33863
|
} else {
|
|
33163
33864
|
isDraw3D = false;
|
|
33164
33865
|
}
|
|
33165
33866
|
}
|
|
33166
|
-
|
|
33167
|
-
|
|
33168
|
-
|
|
33867
|
+
if (isHeroImage) {
|
|
33868
|
+
const heroW = heroImageWH[0] === 0 ? this.effectiveCanvasWidth() : heroImageWH[0];
|
|
33869
|
+
const heroH = heroImageWH[1] === 0 ? this.effectiveCanvasHeight() : heroImageWH[1];
|
|
33870
|
+
if (this.opts?.heroSliceType === 0 /* AXIAL */ || this.opts?.heroSliceType === 1 /* CORONAL */ || this.opts?.heroSliceType === 2 /* SAGITTAL */) {
|
|
33871
|
+
this.draw2D([0, 0, heroW, heroH], this.opts.heroSliceType, NaN, [Infinity, Infinity]);
|
|
33872
|
+
} else {
|
|
33873
|
+
const ltwh2 = ltwh.slice();
|
|
33874
|
+
const canvasW = this.effectiveCanvasWidth();
|
|
33875
|
+
if (heroW === canvasW) {
|
|
33876
|
+
ltwh2[0] = 0;
|
|
33877
|
+
}
|
|
33878
|
+
this.draw3D([ltwh2[0], 0, heroW, heroH]);
|
|
33879
|
+
}
|
|
33880
|
+
ltwh[0] += heroImageWH[0];
|
|
33881
|
+
ltwh[1] += heroImageWH[1];
|
|
33882
|
+
isDraw3D = false;
|
|
33883
|
+
}
|
|
33884
|
+
const sX = volScale[0] * ltwh[4] + innerPad;
|
|
33885
|
+
const sY = volScale[1] * ltwh[4] + innerPad;
|
|
33886
|
+
const sZ = volScale[2] * ltwh[4] + innerPad;
|
|
33169
33887
|
const actualX = actualScale[0] * ltwh[4];
|
|
33170
33888
|
const actualY = actualScale[1] * ltwh[4];
|
|
33171
33889
|
const actualZ = actualScale[2] * ltwh[4];
|
|
@@ -33192,6 +33910,9 @@ var Niivue = class _Niivue {
|
|
|
33192
33910
|
if (this.opts.multiplanarShowRender === 2 /* AUTO */) {
|
|
33193
33911
|
isDraw3D = true;
|
|
33194
33912
|
}
|
|
33913
|
+
if (isHeroImage) {
|
|
33914
|
+
isDraw3D = false;
|
|
33915
|
+
}
|
|
33195
33916
|
this.draw2D([ltwh[0], ltwh[1] + sZ + pad, sX, sY], 0, NaN, [actualX, actualY]);
|
|
33196
33917
|
this.draw2D([ltwh[0], ltwh[1], sX, sZ], 1, NaN, [actualX, actualZ]);
|
|
33197
33918
|
this.draw2D([ltwh[0] + sX + pad, ltwh[1], sY, sZ], 2, NaN, [actualY, actualZ]);
|
|
@@ -33258,10 +33979,14 @@ var Niivue = class _Niivue {
|
|
|
33258
33979
|
width,
|
|
33259
33980
|
height
|
|
33260
33981
|
]);
|
|
33982
|
+
return;
|
|
33261
33983
|
}
|
|
33262
33984
|
if (this.opts.clickToSegment) {
|
|
33263
33985
|
const x = this.mousePos[0];
|
|
33264
33986
|
const y = this.mousePos[1];
|
|
33987
|
+
if (this.inRenderTile(x, y) >= 0) {
|
|
33988
|
+
return;
|
|
33989
|
+
}
|
|
33265
33990
|
const tileIdx = this.tileIndex(x, y);
|
|
33266
33991
|
if (tileIdx > -1) {
|
|
33267
33992
|
const fovMM = this.screenSlices[tileIdx].fovMM;
|