@niivue/niivue 0.45.1 → 0.46.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 +87 -14
- package/build/index.js +648 -110
- package/build/index.js.map +1 -1
- package/build/index.min.js +1 -1
- package/dist/index.d.ts +87 -14
- package/dist/index.js +650 -112
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/niivue.umd.js +66 -27
- package/package.json +1 -1
- package/src/index.html +3 -1
- package/src/niivue/index.ts +745 -111
- package/src/niivue.css +0 -1
- package/src/nvdocument.ts +31 -7
- package/src/nvimage/index.ts +27 -13
- package/src/shader-srcs.ts +41 -0
- package/src/types.ts +13 -0
- 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.46.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;
|
|
@@ -20942,11 +20982,13 @@ var NVImage = class _NVImage {
|
|
|
20942
20982
|
this.onOpacityChange(this);
|
|
20943
20983
|
}
|
|
20944
20984
|
}
|
|
20945
|
-
|
|
20946
|
-
|
|
20947
|
-
|
|
20948
|
-
|
|
20949
|
-
|
|
20985
|
+
/**
|
|
20986
|
+
* set contrast/brightness to robust range (2%..98%)
|
|
20987
|
+
* @param vol - volume for estimate (use -1 to use estimate on all loaded volumes; use INFINITY for current volume)
|
|
20988
|
+
* @sets volume brightness and returns array [pct2, pct98, mnScale, mxScale]
|
|
20989
|
+
* @see {@link https://niivue.github.io/niivue/features/timeseries2.html | live demo usage}
|
|
20990
|
+
*/
|
|
20991
|
+
calMinMax(vol = Number.POSITIVE_INFINITY) {
|
|
20950
20992
|
if (!this.hdr) {
|
|
20951
20993
|
throw new Error("hdr undefined");
|
|
20952
20994
|
}
|
|
@@ -20957,10 +20999,20 @@ var NVImage = class _NVImage {
|
|
|
20957
20999
|
let mx = Number.NEGATIVE_INFINITY;
|
|
20958
21000
|
let nZero = 0;
|
|
20959
21001
|
let nNan = 0;
|
|
20960
|
-
|
|
21002
|
+
let voxEnd = this.img.length;
|
|
21003
|
+
let voxStart = 0;
|
|
21004
|
+
const nVox3D = this.hdr.dims[1] * this.hdr.dims[2] * this.hdr.dims[3];
|
|
21005
|
+
const nVol = Math.floor(voxEnd / nVox3D);
|
|
21006
|
+
if (vol >= nVol) {
|
|
21007
|
+
vol = this.frame4D;
|
|
21008
|
+
}
|
|
21009
|
+
if (vol >= 0 && vol < nVol) {
|
|
21010
|
+
voxStart = vol * nVox3D;
|
|
21011
|
+
voxEnd = voxStart + nVox3D;
|
|
21012
|
+
}
|
|
20961
21013
|
const isFastCalc = this.img.constructor !== Float64Array && this.img.constructor !== Float32Array && this.ignoreZeroVoxels;
|
|
20962
21014
|
if (isFastCalc) {
|
|
20963
|
-
for (let i =
|
|
21015
|
+
for (let i = voxStart; i < voxEnd; i++) {
|
|
20964
21016
|
mn = Math.min(this.img[i], mn);
|
|
20965
21017
|
mx = Math.max(this.img[i], mx);
|
|
20966
21018
|
if (this.img[i] === 0) {
|
|
@@ -20968,7 +21020,7 @@ var NVImage = class _NVImage {
|
|
|
20968
21020
|
}
|
|
20969
21021
|
}
|
|
20970
21022
|
} else {
|
|
20971
|
-
for (let i =
|
|
21023
|
+
for (let i = voxStart; i < voxEnd; i++) {
|
|
20972
21024
|
if (isNaN(this.img[i])) {
|
|
20973
21025
|
nNan++;
|
|
20974
21026
|
continue;
|
|
@@ -21013,7 +21065,7 @@ var NVImage = class _NVImage {
|
|
|
21013
21065
|
this.robust_max = this.cal_max;
|
|
21014
21066
|
return [cmMin, cmMax, cmMin, cmMax];
|
|
21015
21067
|
}
|
|
21016
|
-
const percentZero = 100 * nZero /
|
|
21068
|
+
const percentZero = 100 * nZero / (voxEnd - voxStart);
|
|
21017
21069
|
let isOverrideIgnoreZeroVoxels = false;
|
|
21018
21070
|
if (percentZero > 60 && !this.ignoreZeroVoxels) {
|
|
21019
21071
|
log.warn(`${Math.round(percentZero)}% of voxels are zero: ignoring zeros for cal_max`);
|
|
@@ -21024,7 +21076,7 @@ var NVImage = class _NVImage {
|
|
|
21024
21076
|
nZero = 0;
|
|
21025
21077
|
}
|
|
21026
21078
|
nZero += nNan;
|
|
21027
|
-
const n2pct = Math.round((
|
|
21079
|
+
const n2pct = Math.round((voxEnd - voxStart - nZero) * this.percentileFrac);
|
|
21028
21080
|
if (n2pct < 1 || mn === mx) {
|
|
21029
21081
|
log.debug("no variability in image intensity?");
|
|
21030
21082
|
this.cal_min = mnScale;
|
|
@@ -21042,11 +21094,11 @@ var NVImage = class _NVImage {
|
|
|
21042
21094
|
hist[i] = 0;
|
|
21043
21095
|
}
|
|
21044
21096
|
if (isFastCalc) {
|
|
21045
|
-
for (let i =
|
|
21097
|
+
for (let i = voxStart; i < voxEnd; i++) {
|
|
21046
21098
|
hist[Math.round((this.img[i] - mn) * scl)]++;
|
|
21047
21099
|
}
|
|
21048
21100
|
} else if (this.ignoreZeroVoxels) {
|
|
21049
|
-
for (let i =
|
|
21101
|
+
for (let i = voxStart; i < voxEnd; i++) {
|
|
21050
21102
|
if (this.img[i] === 0) {
|
|
21051
21103
|
continue;
|
|
21052
21104
|
}
|
|
@@ -21056,7 +21108,7 @@ var NVImage = class _NVImage {
|
|
|
21056
21108
|
hist[Math.round((this.img[i] - mn) * scl)]++;
|
|
21057
21109
|
}
|
|
21058
21110
|
} else {
|
|
21059
|
-
for (let i =
|
|
21111
|
+
for (let i = voxStart; i < voxEnd; i++) {
|
|
21060
21112
|
if (isNaN(this.img[i])) {
|
|
21061
21113
|
continue;
|
|
21062
21114
|
}
|
|
@@ -22250,6 +22302,7 @@ var DRAG_MODE = /* @__PURE__ */ ((DRAG_MODE2) => {
|
|
|
22250
22302
|
DRAG_MODE2[DRAG_MODE2["pan"] = 3] = "pan";
|
|
22251
22303
|
DRAG_MODE2[DRAG_MODE2["slicer3D"] = 4] = "slicer3D";
|
|
22252
22304
|
DRAG_MODE2[DRAG_MODE2["callbackOnly"] = 5] = "callbackOnly";
|
|
22305
|
+
DRAG_MODE2[DRAG_MODE2["roiSelection"] = 6] = "roiSelection";
|
|
22253
22306
|
return DRAG_MODE2;
|
|
22254
22307
|
})(DRAG_MODE || {});
|
|
22255
22308
|
var DEFAULT_OPTIONS = {
|
|
@@ -22319,20 +22372,40 @@ var DEFAULT_OPTIONS = {
|
|
|
22319
22372
|
renderOverlayBlend: 1,
|
|
22320
22373
|
sliceMosaicString: "",
|
|
22321
22374
|
centerMosaic: false,
|
|
22375
|
+
penSize: 1,
|
|
22376
|
+
// in voxels, since all drawing is done using bitmap indices
|
|
22322
22377
|
clickToSegment: false,
|
|
22323
|
-
clickToSegmentRadius:
|
|
22324
|
-
|
|
22325
|
-
clickToSegmentBright: true
|
|
22378
|
+
clickToSegmentRadius: 3,
|
|
22379
|
+
// in mm
|
|
22380
|
+
clickToSegmentBright: true,
|
|
22381
|
+
clickToSegmentAutoIntensity: false,
|
|
22382
|
+
// new option, but keep clickToSegmentBright for backwards compatibility
|
|
22383
|
+
clickToSegmentIntensityMax: NaN,
|
|
22384
|
+
// NaN will use auto threshold (default flood fill behavior from before)
|
|
22385
|
+
clickToSegmentIntensityMin: NaN,
|
|
22386
|
+
// NaN will use auto threshold (default flood fill behavior from before)
|
|
22387
|
+
// 0 will use auto threshold (default flood fill behavior from before)
|
|
22388
|
+
// Take the voxel intensity at the click point and use this percentage +/- to threshold the flood fill operation.
|
|
22389
|
+
// If greater than 0, clickedVoxelIntensity +/- clickedVoxelIntensity * clickToSegmentPercent will be used
|
|
22390
|
+
// for the clickToSegmentIntensityMin and clickToSegmentIntensityMax values.
|
|
22391
|
+
clickToSegmentPercent: 0,
|
|
22392
|
+
clickToSegmentMaxDistanceMM: Number.POSITIVE_INFINITY,
|
|
22393
|
+
// default value is infinity for backwards compatibility with flood fill routine.
|
|
22394
|
+
clickToSegmentIs2D: false,
|
|
22395
|
+
selectionBoxLineThickness: 4,
|
|
22396
|
+
selectionBoxIsOutline: false,
|
|
22397
|
+
scrollRequiresFocus: false
|
|
22398
|
+
// determines if the cavas need to be focused to scroll
|
|
22326
22399
|
};
|
|
22327
22400
|
var INITIAL_SCENE_DATA = {
|
|
22328
22401
|
gamma: 1,
|
|
22329
22402
|
azimuth: 110,
|
|
22330
22403
|
elevation: 10,
|
|
22331
|
-
crosshairPos: vec38.
|
|
22404
|
+
crosshairPos: vec38.fromValues(0.5, 0.5, 0.5),
|
|
22332
22405
|
clipPlane: [0, 0, 0, 0],
|
|
22333
22406
|
clipPlaneDepthAziElev: [2, 0, 0],
|
|
22334
22407
|
volScaleMultiplier: 1,
|
|
22335
|
-
pan2Dxyzmm: vec46.
|
|
22408
|
+
pan2Dxyzmm: vec46.fromValues(0, 0, 0, 1),
|
|
22336
22409
|
clipThick: 2,
|
|
22337
22410
|
clipVolumeLow: [0, 0, 0],
|
|
22338
22411
|
clipVolumeHigh: [1, 1, 1]
|
|
@@ -24019,7 +24092,7 @@ var defaultSaveImageOptions = {
|
|
|
24019
24092
|
isSaveDrawing: false,
|
|
24020
24093
|
volumeByIndex: 0
|
|
24021
24094
|
};
|
|
24022
|
-
var Niivue = class
|
|
24095
|
+
var Niivue = class {
|
|
24023
24096
|
/**
|
|
24024
24097
|
* @param options - options object to set modifiable Niivue properties
|
|
24025
24098
|
*/
|
|
@@ -24050,6 +24123,12 @@ var Niivue = class _Niivue {
|
|
|
24050
24123
|
// the color lookup table for drawing
|
|
24051
24124
|
__publicField(this, "drawOpacity", 0.8);
|
|
24052
24125
|
// opacity of drawing (default)
|
|
24126
|
+
__publicField(this, "clickToSegmentIsGrowing", false);
|
|
24127
|
+
// flag to indicate if the clickToSegment flood fill growing is in progress with left mouse down + drag
|
|
24128
|
+
__publicField(this, "clickToSegmentGrowingBitmap", null);
|
|
24129
|
+
// the bitmap of the growing flood fill
|
|
24130
|
+
__publicField(this, "clickToSegmentXY", [0, 0]);
|
|
24131
|
+
// the x,y location of the clickToSegment flood fill
|
|
24053
24132
|
__publicField(this, "renderDrawAmbientOcclusion", 0.4);
|
|
24054
24133
|
__publicField(this, "colorbarHeight", 0);
|
|
24055
24134
|
// height in pixels, set when colorbar is drawn
|
|
@@ -24067,6 +24146,7 @@ var Niivue = class _Niivue {
|
|
|
24067
24146
|
__publicField(this, "orientCubeShader");
|
|
24068
24147
|
__publicField(this, "orientCubeShaderVAO", null);
|
|
24069
24148
|
__publicField(this, "rectShader");
|
|
24149
|
+
__publicField(this, "rectOutlineShader");
|
|
24070
24150
|
__publicField(this, "renderShader");
|
|
24071
24151
|
__publicField(this, "lineShader");
|
|
24072
24152
|
__publicField(this, "line3DShader");
|
|
@@ -24119,7 +24199,20 @@ var Niivue = class _Niivue {
|
|
|
24119
24199
|
__publicField(this, "extentsMax");
|
|
24120
24200
|
// ResizeObserver
|
|
24121
24201
|
__publicField(this, "resizeObserver", null);
|
|
24122
|
-
|
|
24202
|
+
// syncOpts: Record<string, unknown> = {}
|
|
24203
|
+
__publicField(this, "syncOpts", {
|
|
24204
|
+
"3d": false,
|
|
24205
|
+
// legacy option
|
|
24206
|
+
"2d": false,
|
|
24207
|
+
// legacy option
|
|
24208
|
+
zoomPan: false,
|
|
24209
|
+
cal_min: false,
|
|
24210
|
+
cal_max: false,
|
|
24211
|
+
clipPlane: false,
|
|
24212
|
+
gamma: false,
|
|
24213
|
+
sliceType: false,
|
|
24214
|
+
crosshair: false
|
|
24215
|
+
});
|
|
24123
24216
|
__publicField(this, "readyForSync", false);
|
|
24124
24217
|
// UI Data
|
|
24125
24218
|
__publicField(this, "uiData", {
|
|
@@ -24178,7 +24271,8 @@ var Niivue = class _Niivue {
|
|
|
24178
24271
|
vols: [0],
|
|
24179
24272
|
// e.g. timeline for background volume only, e.g. [0,2] for first and third volumes
|
|
24180
24273
|
autoSizeMultiplanar: false,
|
|
24181
|
-
normalizeValues: false
|
|
24274
|
+
normalizeValues: false,
|
|
24275
|
+
isRangeCalMinMax: false
|
|
24182
24276
|
});
|
|
24183
24277
|
__publicField(this, "meshShaders", [
|
|
24184
24278
|
{
|
|
@@ -24578,8 +24672,16 @@ var Niivue = class _Niivue {
|
|
|
24578
24672
|
this.canvas.style.display = "block";
|
|
24579
24673
|
this.canvas.width = this.canvas.offsetWidth;
|
|
24580
24674
|
this.canvas.height = this.canvas.offsetHeight;
|
|
24581
|
-
window.addEventListener("resize",
|
|
24582
|
-
|
|
24675
|
+
window.addEventListener("resize", () => {
|
|
24676
|
+
requestAnimationFrame(() => {
|
|
24677
|
+
this.resizeListener();
|
|
24678
|
+
});
|
|
24679
|
+
});
|
|
24680
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
24681
|
+
requestAnimationFrame(() => {
|
|
24682
|
+
this.resizeListener();
|
|
24683
|
+
});
|
|
24684
|
+
});
|
|
24583
24685
|
this.resizeObserver.observe(this.canvas.parentElement);
|
|
24584
24686
|
}
|
|
24585
24687
|
this.registerInteractions();
|
|
@@ -24598,6 +24700,9 @@ var Niivue = class _Niivue {
|
|
|
24598
24700
|
* @see {@link https://niivue.github.io/niivue/features/sync.mesh.html | live demo usage}
|
|
24599
24701
|
*/
|
|
24600
24702
|
syncWith(otherNV, syncOpts = { "2d": true, "3d": true }) {
|
|
24703
|
+
if (!(otherNV instanceof Array)) {
|
|
24704
|
+
otherNV = [otherNV];
|
|
24705
|
+
}
|
|
24601
24706
|
this.otherNV = otherNV;
|
|
24602
24707
|
this.syncOpts = { ...syncOpts };
|
|
24603
24708
|
}
|
|
@@ -24613,9 +24718,55 @@ var Niivue = class _Niivue {
|
|
|
24613
24718
|
* @see {@link https://niivue.github.io/niivue/features/sync.mesh.html | live demo usage}
|
|
24614
24719
|
*/
|
|
24615
24720
|
broadcastTo(otherNV, syncOpts = { "2d": true, "3d": true }) {
|
|
24721
|
+
if (!(otherNV instanceof Array)) {
|
|
24722
|
+
otherNV = [otherNV];
|
|
24723
|
+
}
|
|
24616
24724
|
this.otherNV = otherNV;
|
|
24617
24725
|
this.syncOpts = syncOpts;
|
|
24618
24726
|
}
|
|
24727
|
+
doSync3d(otherNV) {
|
|
24728
|
+
otherNV.scene.renderAzimuth = this.scene.renderAzimuth;
|
|
24729
|
+
otherNV.scene.renderElevation = this.scene.renderElevation;
|
|
24730
|
+
otherNV.scene.volScaleMultiplier = this.scene.volScaleMultiplier;
|
|
24731
|
+
}
|
|
24732
|
+
// both crosshair and zoomPan
|
|
24733
|
+
doSync2d(otherNV) {
|
|
24734
|
+
const thisMM = this.frac2mm(this.scene.crosshairPos);
|
|
24735
|
+
otherNV.scene.crosshairPos = otherNV.mm2frac(thisMM);
|
|
24736
|
+
otherNV.scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24737
|
+
}
|
|
24738
|
+
doSyncGamma(otherNV) {
|
|
24739
|
+
const thisGamma = this.scene.gamma;
|
|
24740
|
+
const otherGamma = otherNV.scene.gamma;
|
|
24741
|
+
if (thisGamma !== otherGamma) {
|
|
24742
|
+
otherNV.setGamma(thisGamma);
|
|
24743
|
+
}
|
|
24744
|
+
}
|
|
24745
|
+
doSyncZoomPan(otherNV) {
|
|
24746
|
+
otherNV.scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24747
|
+
}
|
|
24748
|
+
doSyncCrosshair(otherNV) {
|
|
24749
|
+
const thisMM = this.frac2mm(this.scene.crosshairPos);
|
|
24750
|
+
otherNV.scene.crosshairPos = otherNV.mm2frac(thisMM);
|
|
24751
|
+
}
|
|
24752
|
+
doSyncCalMin(otherNV) {
|
|
24753
|
+
if (this.volumes[0].cal_min !== otherNV.volumes[0].cal_min) {
|
|
24754
|
+
otherNV.volumes[0].cal_min = this.volumes[0].cal_min;
|
|
24755
|
+
otherNV.updateGLVolume();
|
|
24756
|
+
}
|
|
24757
|
+
}
|
|
24758
|
+
doSyncCalMax(otherNV) {
|
|
24759
|
+
if (this.volumes[0].cal_max !== otherNV.volumes[0].cal_max) {
|
|
24760
|
+
otherNV.volumes[0].cal_max = this.volumes[0].cal_max;
|
|
24761
|
+
otherNV.updateGLVolume();
|
|
24762
|
+
}
|
|
24763
|
+
}
|
|
24764
|
+
doSyncSliceType(otherNV) {
|
|
24765
|
+
otherNV.setSliceType(this.opts.sliceType);
|
|
24766
|
+
}
|
|
24767
|
+
doSyncClipPlane(otherNV) {
|
|
24768
|
+
otherNV.setClipPlane(this.scene.clipPlaneDepthAziElev);
|
|
24769
|
+
}
|
|
24619
24770
|
/**
|
|
24620
24771
|
* Sync the scene controls (orientation, crosshair location, etc.) from one Niivue instance to another. useful for using one canvas to drive another.
|
|
24621
24772
|
* @internal
|
|
@@ -24632,46 +24783,39 @@ var Niivue = class _Niivue {
|
|
|
24632
24783
|
if (!this.gl.canvas.matches(":focus")) {
|
|
24633
24784
|
return;
|
|
24634
24785
|
}
|
|
24635
|
-
|
|
24636
|
-
|
|
24637
|
-
|
|
24638
|
-
|
|
24639
|
-
if (
|
|
24640
|
-
this.otherNV
|
|
24786
|
+
for (let i = 0; i < this.otherNV.length; i++) {
|
|
24787
|
+
if (this.otherNV[i] === this) {
|
|
24788
|
+
continue;
|
|
24789
|
+
}
|
|
24790
|
+
if (this.syncOpts.gamma) {
|
|
24791
|
+
this.doSyncGamma(this.otherNV[i]);
|
|
24792
|
+
}
|
|
24793
|
+
if (this.syncOpts.crosshair) {
|
|
24794
|
+
this.doSyncCrosshair(this.otherNV[i]);
|
|
24795
|
+
}
|
|
24796
|
+
if (this.syncOpts.zoomPan) {
|
|
24797
|
+
this.doSyncZoomPan(this.otherNV[i]);
|
|
24798
|
+
}
|
|
24799
|
+
if (this.syncOpts.sliceType) {
|
|
24800
|
+
this.doSyncSliceType(this.otherNV[i]);
|
|
24801
|
+
}
|
|
24802
|
+
if (this.syncOpts.cal_min) {
|
|
24803
|
+
this.doSyncCalMin(this.otherNV[i]);
|
|
24804
|
+
}
|
|
24805
|
+
if (this.syncOpts.cal_max) {
|
|
24806
|
+
this.doSyncCalMax(this.otherNV[i]);
|
|
24807
|
+
}
|
|
24808
|
+
if (this.syncOpts.clipPlane) {
|
|
24809
|
+
this.doSyncClipPlane(this.otherNV[i]);
|
|
24641
24810
|
}
|
|
24642
24811
|
if (this.syncOpts["2d"]) {
|
|
24643
|
-
this.
|
|
24644
|
-
this.otherNV.scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24812
|
+
this.doSync2d(this.otherNV[i]);
|
|
24645
24813
|
}
|
|
24646
24814
|
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();
|
|
24815
|
+
this.doSync3d(this.otherNV[i]);
|
|
24674
24816
|
}
|
|
24817
|
+
this.otherNV[i].drawScene();
|
|
24818
|
+
this.otherNV[i].createOnLocationChange();
|
|
24675
24819
|
}
|
|
24676
24820
|
}
|
|
24677
24821
|
/** Not documented publicly for now
|
|
@@ -24766,6 +24910,8 @@ var Niivue = class _Niivue {
|
|
|
24766
24910
|
this.drawPenLocation = [NaN, NaN, NaN];
|
|
24767
24911
|
this.drawPenAxCorSag = -1;
|
|
24768
24912
|
this.uiData.mousedown = true;
|
|
24913
|
+
this.setDragStart(0, 0);
|
|
24914
|
+
this.setDragEnd(0, 0);
|
|
24769
24915
|
log.debug("mouse down");
|
|
24770
24916
|
log.debug(e);
|
|
24771
24917
|
const pos = this.getNoPaddingNoBorderCanvasRelativeMousePosition(e, this.gl.canvas);
|
|
@@ -24773,6 +24919,9 @@ var Niivue = class _Niivue {
|
|
|
24773
24919
|
return;
|
|
24774
24920
|
}
|
|
24775
24921
|
const [x, y] = [pos.x * this.uiData.dpr, pos.y * this.uiData.dpr];
|
|
24922
|
+
if (this.opts.clickToSegment) {
|
|
24923
|
+
this.clickToSegmentXY = [x, y];
|
|
24924
|
+
}
|
|
24776
24925
|
const label = this.getLabelAtPoint([x, y]);
|
|
24777
24926
|
if (label) {
|
|
24778
24927
|
if (label.onClick) {
|
|
@@ -24973,8 +25122,15 @@ var Niivue = class _Niivue {
|
|
|
24973
25122
|
if (this.drawPenFillPts.length > 0) {
|
|
24974
25123
|
this.drawPenFilled();
|
|
24975
25124
|
} else if (this.drawPenAxCorSag >= 0) {
|
|
24976
|
-
this.
|
|
25125
|
+
if (this.opts.clickToSegment) {
|
|
25126
|
+
const sumBitmap = this.sumBitmap(this.drawBitmap);
|
|
25127
|
+
const sumSeg = this.sumBitmap(this.clickToSegmentGrowingBitmap);
|
|
25128
|
+
if (sumSeg > sumBitmap) {
|
|
25129
|
+
this.updateBitmapFromClickToSegment();
|
|
25130
|
+
}
|
|
25131
|
+
}
|
|
24977
25132
|
}
|
|
25133
|
+
this.drawAddUndoBitmap();
|
|
24978
25134
|
this.drawPenLocation = [NaN, NaN, NaN];
|
|
24979
25135
|
this.drawPenAxCorSag = -1;
|
|
24980
25136
|
if (isFunction(this.onMouseUp)) {
|
|
@@ -24988,6 +25144,9 @@ var Niivue = class _Niivue {
|
|
|
24988
25144
|
const fracStart = this.canvasPos2frac([this.uiData.dragStart[0], this.uiData.dragStart[1]]);
|
|
24989
25145
|
const fracEnd = this.canvasPos2frac([this.uiData.dragEnd[0], this.uiData.dragEnd[1]]);
|
|
24990
25146
|
this.generateMouseUpCallback(fracStart, fracEnd);
|
|
25147
|
+
if (this.opts.dragMode === 6 /* roiSelection */) {
|
|
25148
|
+
return;
|
|
25149
|
+
}
|
|
24991
25150
|
if (this.opts.dragMode !== 1 /* contrast */) {
|
|
24992
25151
|
return;
|
|
24993
25152
|
}
|
|
@@ -25290,6 +25449,35 @@ var Niivue = class _Niivue {
|
|
|
25290
25449
|
if (this.thumbnailVisible) {
|
|
25291
25450
|
return;
|
|
25292
25451
|
}
|
|
25452
|
+
const dragStartSum = this.uiData.dragStart.reduce((a, b) => a + b, 0);
|
|
25453
|
+
const dragEndSum = this.uiData.dragEnd.reduce((a, b) => a + b, 0);
|
|
25454
|
+
const validDrag = dragStartSum > 0 && dragEndSum > 0;
|
|
25455
|
+
if (this.opts.dragMode === 6 /* roiSelection */ && validDrag) {
|
|
25456
|
+
const delta = e.deltaY > 0 ? 1 : -1;
|
|
25457
|
+
if (this.uiData.dragStart[0] < this.uiData.dragEnd[0]) {
|
|
25458
|
+
this.uiData.dragStart[0] -= delta;
|
|
25459
|
+
this.uiData.dragEnd[0] += delta;
|
|
25460
|
+
} else {
|
|
25461
|
+
this.uiData.dragStart[0] += delta;
|
|
25462
|
+
this.uiData.dragEnd[0] -= delta;
|
|
25463
|
+
}
|
|
25464
|
+
if (this.uiData.dragStart[1] < this.uiData.dragEnd[1]) {
|
|
25465
|
+
this.uiData.dragStart[1] -= delta;
|
|
25466
|
+
this.uiData.dragEnd[1] += delta;
|
|
25467
|
+
} else {
|
|
25468
|
+
this.uiData.dragStart[1] += delta;
|
|
25469
|
+
this.uiData.dragEnd[1] -= delta;
|
|
25470
|
+
}
|
|
25471
|
+
this.uiData.isDragging = true;
|
|
25472
|
+
this.drawScene();
|
|
25473
|
+
this.uiData.isDragging = false;
|
|
25474
|
+
const tileIdx = this.tileIndex(this.uiData.dragStart[0], this.uiData.dragStart[1]);
|
|
25475
|
+
this.generateMouseUpCallback(
|
|
25476
|
+
this.screenXY2TextureFrac(this.uiData.dragStart[0], this.uiData.dragStart[1], tileIdx),
|
|
25477
|
+
this.screenXY2TextureFrac(this.uiData.dragEnd[0], this.uiData.dragEnd[1], tileIdx)
|
|
25478
|
+
);
|
|
25479
|
+
return;
|
|
25480
|
+
}
|
|
25293
25481
|
const rect = this.canvas.getBoundingClientRect();
|
|
25294
25482
|
if (e.deltaY < 0) {
|
|
25295
25483
|
this.sliceScroll2D(-0.01, e.clientX - rect.left, e.clientY - rect.top);
|
|
@@ -26783,6 +26971,10 @@ var Niivue = class _Niivue {
|
|
|
26783
26971
|
}
|
|
26784
26972
|
// not included in public docs
|
|
26785
26973
|
sliceScroll2D(posChange, x, y, isDelta = true) {
|
|
26974
|
+
if (this.opts.scrollRequiresFocus && this.canvas !== document.activeElement) {
|
|
26975
|
+
log.warn("Canvas element does not have focus. Scroll events will not be processed.");
|
|
26976
|
+
return;
|
|
26977
|
+
}
|
|
26786
26978
|
if (this.inGraphTile(x, y)) {
|
|
26787
26979
|
let vol = this.volumes[0].frame4D;
|
|
26788
26980
|
if (posChange > 0) {
|
|
@@ -27474,6 +27666,7 @@ var Niivue = class _Niivue {
|
|
|
27474
27666
|
}
|
|
27475
27667
|
const vx = this.back.dims[1] * this.back.dims[2] * this.back.dims[3];
|
|
27476
27668
|
this.drawBitmap = new Uint8Array(vx);
|
|
27669
|
+
this.clickToSegmentGrowingBitmap = new Uint8Array(vx);
|
|
27477
27670
|
this.drawClearAllUndoBitmaps();
|
|
27478
27671
|
this.drawAddUndoBitmap();
|
|
27479
27672
|
this.drawTexture = this.r8Tex(this.drawTexture, TEXTURE7_DRAW, this.back.dims, true);
|
|
@@ -27647,6 +27840,23 @@ var Niivue = class _Niivue {
|
|
|
27647
27840
|
y = Math.min(Math.max(y, 0), dy - 1);
|
|
27648
27841
|
z = Math.min(Math.max(z, 0), dz - 1);
|
|
27649
27842
|
this.drawBitmap[x + y * dx + z * dx * dy] = penValue;
|
|
27843
|
+
const isAx = this.drawPenAxCorSag === 0;
|
|
27844
|
+
const isCor = this.drawPenAxCorSag === 1;
|
|
27845
|
+
const isSag = this.drawPenAxCorSag === 2;
|
|
27846
|
+
if (this.opts.penSize > 1) {
|
|
27847
|
+
const halfPenSize = Math.floor(this.opts.penSize / 2);
|
|
27848
|
+
for (let i = -halfPenSize; i <= halfPenSize; i++) {
|
|
27849
|
+
for (let j = -halfPenSize; j <= halfPenSize; j++) {
|
|
27850
|
+
if (isAx) {
|
|
27851
|
+
this.drawBitmap[x + i + (y + j) * dx + z * dx * dy] = penValue;
|
|
27852
|
+
} else if (isCor) {
|
|
27853
|
+
this.drawBitmap[x + i + y * dx + (z + j) * dx * dy] = penValue;
|
|
27854
|
+
} else if (isSag) {
|
|
27855
|
+
this.drawBitmap[x + (y + j) * dx + (z + i) * dx * dy] = penValue;
|
|
27856
|
+
}
|
|
27857
|
+
}
|
|
27858
|
+
}
|
|
27859
|
+
}
|
|
27650
27860
|
}
|
|
27651
27861
|
// not included in public docs
|
|
27652
27862
|
// create line between to voxels in drawing
|
|
@@ -27728,6 +27938,98 @@ var Niivue = class _Niivue {
|
|
|
27728
27938
|
}
|
|
27729
27939
|
}
|
|
27730
27940
|
}
|
|
27941
|
+
/**
|
|
27942
|
+
* Performs a 1-voxel binary dilation on a connected cluster within the drawing mask using the drawFloodFillCore function.
|
|
27943
|
+
*
|
|
27944
|
+
* @param seedXYZ - voxel index of the seed voxel in the mask array.
|
|
27945
|
+
* @param neighbors - Number of neighbors to consider for connectivity and dilation (6, 18, or 26).
|
|
27946
|
+
*/
|
|
27947
|
+
drawingBinaryDilationWithSeed(seedXYZ, neighbors = 6) {
|
|
27948
|
+
try {
|
|
27949
|
+
let xyz2vx = function(pt) {
|
|
27950
|
+
return pt[0] + pt[1] * nx + pt[2] * nxy;
|
|
27951
|
+
};
|
|
27952
|
+
const mask = this.drawBitmap;
|
|
27953
|
+
const xDim = this.back.dims[1];
|
|
27954
|
+
const yDim = this.back.dims[2];
|
|
27955
|
+
const zDim = this.back.dims[3];
|
|
27956
|
+
const nx = xDim;
|
|
27957
|
+
const nxy = xDim * yDim;
|
|
27958
|
+
const totalVoxels = nxy * zDim;
|
|
27959
|
+
const seedIndex = xyz2vx(seedXYZ);
|
|
27960
|
+
if (seedIndex < 0 || seedIndex >= totalVoxels) {
|
|
27961
|
+
throw new Error("Seed index is out of bounds.");
|
|
27962
|
+
}
|
|
27963
|
+
const seedValue = mask[seedIndex];
|
|
27964
|
+
if (seedValue === 0) {
|
|
27965
|
+
throw new Error("Seed voxel is not part of a filled cluster.");
|
|
27966
|
+
}
|
|
27967
|
+
const img = mask.slice();
|
|
27968
|
+
for (let i = 0; i < totalVoxels; i++) {
|
|
27969
|
+
img[i] = img[i] === seedValue ? 1 : 0;
|
|
27970
|
+
}
|
|
27971
|
+
this.drawFloodFillCore(img, seedIndex, neighbors);
|
|
27972
|
+
const outputMask = mask.slice();
|
|
27973
|
+
const neighborOffsets = [];
|
|
27974
|
+
const offsets6 = [-nxy, nxy, -xDim, xDim, -1, 1];
|
|
27975
|
+
neighborOffsets.push(...offsets6);
|
|
27976
|
+
if (neighbors > 6) {
|
|
27977
|
+
neighborOffsets.push(
|
|
27978
|
+
-xDim - 1,
|
|
27979
|
+
-xDim + 1,
|
|
27980
|
+
xDim - 1,
|
|
27981
|
+
xDim + 1,
|
|
27982
|
+
-nxy - xDim,
|
|
27983
|
+
-nxy + xDim,
|
|
27984
|
+
-nxy - 1,
|
|
27985
|
+
-nxy + 1,
|
|
27986
|
+
nxy - xDim,
|
|
27987
|
+
nxy + xDim,
|
|
27988
|
+
nxy - 1,
|
|
27989
|
+
nxy + 1
|
|
27990
|
+
);
|
|
27991
|
+
}
|
|
27992
|
+
if (neighbors > 18) {
|
|
27993
|
+
neighborOffsets.push(
|
|
27994
|
+
-nxy - xDim - 1,
|
|
27995
|
+
-nxy - xDim + 1,
|
|
27996
|
+
-nxy + xDim - 1,
|
|
27997
|
+
-nxy + xDim + 1,
|
|
27998
|
+
nxy - xDim - 1,
|
|
27999
|
+
nxy - xDim + 1,
|
|
28000
|
+
nxy + xDim - 1,
|
|
28001
|
+
nxy + xDim + 1
|
|
28002
|
+
);
|
|
28003
|
+
}
|
|
28004
|
+
for (let idx = 0; idx < totalVoxels; idx++) {
|
|
28005
|
+
if (img[idx] === 2) {
|
|
28006
|
+
const x = idx % xDim;
|
|
28007
|
+
const y = Math.floor(idx % nxy / xDim);
|
|
28008
|
+
const z = Math.floor(idx / nxy);
|
|
28009
|
+
for (const offset of neighborOffsets) {
|
|
28010
|
+
const neighborIdx = idx + offset;
|
|
28011
|
+
if (neighborIdx < 0 || neighborIdx >= totalVoxels) {
|
|
28012
|
+
continue;
|
|
28013
|
+
}
|
|
28014
|
+
const nx2 = neighborIdx % xDim;
|
|
28015
|
+
const ny = Math.floor(neighborIdx % nxy / xDim);
|
|
28016
|
+
const nz = Math.floor(neighborIdx / nxy);
|
|
28017
|
+
if (Math.abs(nx2 - x) > 1 || Math.abs(ny - y) > 1 || Math.abs(nz - z) > 1) {
|
|
28018
|
+
continue;
|
|
28019
|
+
}
|
|
28020
|
+
if (mask[neighborIdx] === 0) {
|
|
28021
|
+
outputMask[neighborIdx] = seedValue;
|
|
28022
|
+
}
|
|
28023
|
+
}
|
|
28024
|
+
}
|
|
28025
|
+
}
|
|
28026
|
+
this.drawBitmap = outputMask;
|
|
28027
|
+
this.drawAddUndoBitmap();
|
|
28028
|
+
this.refreshDrawing(true);
|
|
28029
|
+
} catch (error) {
|
|
28030
|
+
log.error("Error in drawingBinaryDilationWithSeed:", error);
|
|
28031
|
+
}
|
|
28032
|
+
}
|
|
27731
28033
|
// a voxel can be defined as having 6, 18 or 26 neighbors:
|
|
27732
28034
|
// 6: neighbors share faces (distance=1)
|
|
27733
28035
|
// 18: neighbors share faces (distance=1) or edges (1.4)
|
|
@@ -27809,7 +28111,7 @@ var Niivue = class _Niivue {
|
|
|
27809
28111
|
}
|
|
27810
28112
|
// not included in public docs
|
|
27811
28113
|
// set all connected voxels in drawing to new color
|
|
27812
|
-
drawFloodFill(seedXYZ, newColor = 0, growSelectedCluster = 0, forceMin = NaN, forceMax = NaN, neighbors = 6) {
|
|
28114
|
+
drawFloodFill(seedXYZ, newColor = 0, growSelectedCluster = 0, forceMin = NaN, forceMax = NaN, neighbors = 6, maxDistanceMM = Number.POSITIVE_INFINITY, is2D = false) {
|
|
27813
28115
|
if (!this.drawBitmap) {
|
|
27814
28116
|
throw new Error("drawBitmap undefined");
|
|
27815
28117
|
}
|
|
@@ -27827,13 +28129,46 @@ var Niivue = class _Niivue {
|
|
|
27827
28129
|
const nx = dims[0];
|
|
27828
28130
|
const nxy = nx * dims[1];
|
|
27829
28131
|
const nxyz = nxy * dims[2];
|
|
27830
|
-
|
|
28132
|
+
let img = this.drawBitmap.slice();
|
|
28133
|
+
let drawBitmap = this.drawBitmap;
|
|
28134
|
+
if (this.clickToSegmentIsGrowing) {
|
|
28135
|
+
img = this.clickToSegmentGrowingBitmap.slice();
|
|
28136
|
+
drawBitmap = this.clickToSegmentGrowingBitmap;
|
|
28137
|
+
}
|
|
27831
28138
|
if (img.length !== nxy * dims[2]) {
|
|
27832
28139
|
return;
|
|
27833
28140
|
}
|
|
28141
|
+
let constrainXYZ = -1;
|
|
28142
|
+
if (is2D && this.drawPenAxCorSag === 0 /* AXIAL */) {
|
|
28143
|
+
constrainXYZ = 2;
|
|
28144
|
+
} else if (is2D && this.drawPenAxCorSag === 1 /* CORONAL */) {
|
|
28145
|
+
constrainXYZ = 1;
|
|
28146
|
+
} else if (is2D && this.drawPenAxCorSag === 2 /* SAGITTAL */) {
|
|
28147
|
+
constrainXYZ = 0;
|
|
28148
|
+
}
|
|
28149
|
+
function vx2xyz(vx) {
|
|
28150
|
+
const Z = Math.floor(vx / nxy);
|
|
28151
|
+
const Y = Math.floor((vx - Z * nxy) / nx);
|
|
28152
|
+
const X = Math.floor(vx % nx);
|
|
28153
|
+
return [X, Y, Z];
|
|
28154
|
+
}
|
|
27834
28155
|
function xyz2vx(pt) {
|
|
27835
28156
|
return pt[0] + pt[1] * nx + pt[2] * nxy;
|
|
27836
28157
|
}
|
|
28158
|
+
const vx2mm = (xyz) => {
|
|
28159
|
+
return this.vox2mm(xyz, this.back.matRAS);
|
|
28160
|
+
};
|
|
28161
|
+
const seedMM = vx2mm(seedXYZ);
|
|
28162
|
+
const maxDistanceMM2 = maxDistanceMM ** 2;
|
|
28163
|
+
function isWithinDistance(vx) {
|
|
28164
|
+
const xzyVox = vx2xyz(vx);
|
|
28165
|
+
if (constrainXYZ >= 0 && xzyVox[constrainXYZ] !== seedXYZ[constrainXYZ]) {
|
|
28166
|
+
return false;
|
|
28167
|
+
}
|
|
28168
|
+
const xyzMM = vx2mm(xzyVox);
|
|
28169
|
+
const dist2 = (xyzMM[0] - seedMM[0]) ** 2 + (xyzMM[1] - seedMM[1]) ** 2 + (xyzMM[2] - seedMM[2]) ** 2;
|
|
28170
|
+
return dist2 <= maxDistanceMM2;
|
|
28171
|
+
}
|
|
27837
28172
|
const seedVx = xyz2vx(seedXYZ);
|
|
27838
28173
|
const seedColor = img[seedVx];
|
|
27839
28174
|
if (seedColor === newColor) {
|
|
@@ -27846,7 +28181,10 @@ var Niivue = class _Niivue {
|
|
|
27846
28181
|
}
|
|
27847
28182
|
for (let i = 1; i < nxyz; i++) {
|
|
27848
28183
|
img[i] = 0;
|
|
27849
|
-
if (
|
|
28184
|
+
if (drawBitmap[i] === seedColor) {
|
|
28185
|
+
if (!isWithinDistance(i)) {
|
|
28186
|
+
continue;
|
|
28187
|
+
}
|
|
27850
28188
|
img[i] = 1;
|
|
27851
28189
|
}
|
|
27852
28190
|
}
|
|
@@ -27876,6 +28214,9 @@ var Niivue = class _Niivue {
|
|
|
27876
28214
|
for (let i = 1; i < nxyz; i++) {
|
|
27877
28215
|
img[i] = 0;
|
|
27878
28216
|
if (backImg[i] >= mn && backImg[i] <= mx) {
|
|
28217
|
+
if (!isWithinDistance(i)) {
|
|
28218
|
+
continue;
|
|
28219
|
+
}
|
|
27879
28220
|
img[i] = 1;
|
|
27880
28221
|
}
|
|
27881
28222
|
}
|
|
@@ -27884,11 +28225,19 @@ var Niivue = class _Niivue {
|
|
|
27884
28225
|
}
|
|
27885
28226
|
for (let i = 1; i < nxyz; i++) {
|
|
27886
28227
|
if (img[i] === 2) {
|
|
27887
|
-
|
|
28228
|
+
drawBitmap[i] = newColor;
|
|
27888
28229
|
}
|
|
27889
28230
|
}
|
|
27890
|
-
this.
|
|
27891
|
-
|
|
28231
|
+
if (!this.clickToSegmentIsGrowing) {
|
|
28232
|
+
this.drawBitmap = drawBitmap.slice();
|
|
28233
|
+
if (!this.opts.clickToSegment) {
|
|
28234
|
+
this.drawAddUndoBitmap();
|
|
28235
|
+
}
|
|
28236
|
+
this.refreshDrawing(true, this.clickToSegmentIsGrowing);
|
|
28237
|
+
} else {
|
|
28238
|
+
this.clickToSegmentGrowingBitmap = drawBitmap;
|
|
28239
|
+
this.refreshDrawing(true, this.clickToSegmentIsGrowing);
|
|
28240
|
+
}
|
|
27892
28241
|
}
|
|
27893
28242
|
// not included in public docs
|
|
27894
28243
|
// given series of line segments, connect first and last
|
|
@@ -28048,6 +28397,7 @@ var Niivue = class _Niivue {
|
|
|
28048
28397
|
this.drawClearAllUndoBitmaps();
|
|
28049
28398
|
this.rgbaTex(this.drawTexture, TEXTURE7_DRAW, [2, 2, 2, 2], true);
|
|
28050
28399
|
this.drawBitmap = null;
|
|
28400
|
+
this.clickToSegmentGrowingBitmap = null;
|
|
28051
28401
|
this.drawScene();
|
|
28052
28402
|
}
|
|
28053
28403
|
/**
|
|
@@ -28056,7 +28406,7 @@ var Niivue = class _Niivue {
|
|
|
28056
28406
|
* @example niivue.refreshDrawing();
|
|
28057
28407
|
* @see {@link https://niivue.github.io/niivue/features/cactus.html | live demo usage}
|
|
28058
28408
|
*/
|
|
28059
|
-
refreshDrawing(isForceRedraw = true) {
|
|
28409
|
+
refreshDrawing(isForceRedraw = true, useClickToSegmentBitmap = false) {
|
|
28060
28410
|
if (!this.back?.dims) {
|
|
28061
28411
|
throw new Error("back.dims undefined");
|
|
28062
28412
|
}
|
|
@@ -28085,7 +28435,7 @@ var Niivue = class _Niivue {
|
|
|
28085
28435
|
dims[3],
|
|
28086
28436
|
this.gl.RED,
|
|
28087
28437
|
this.gl.UNSIGNED_BYTE,
|
|
28088
|
-
this.drawBitmap
|
|
28438
|
+
useClickToSegmentBitmap ? this.clickToSegmentGrowingBitmap : this.drawBitmap
|
|
28089
28439
|
);
|
|
28090
28440
|
if (isForceRedraw) {
|
|
28091
28441
|
this.drawScene();
|
|
@@ -28497,6 +28847,8 @@ var Niivue = class _Niivue {
|
|
|
28497
28847
|
gl.bindVertexArray(this.unusedVAO);
|
|
28498
28848
|
this.rectShader = new Shader(gl, vertRectShader, fragRectShader);
|
|
28499
28849
|
this.rectShader.use(gl);
|
|
28850
|
+
this.rectOutlineShader = new Shader(gl, vertRectShader, fragRectOutlineShader);
|
|
28851
|
+
this.rectOutlineShader.use(gl);
|
|
28500
28852
|
this.lineShader = new Shader(gl, vertLineShader, fragRectShader);
|
|
28501
28853
|
this.lineShader.use(gl);
|
|
28502
28854
|
this.line3DShader = new Shader(gl, vertLine3DShader, fragRectShader);
|
|
@@ -28649,14 +29001,37 @@ var Niivue = class _Niivue {
|
|
|
28649
29001
|
}
|
|
28650
29002
|
/**
|
|
28651
29003
|
* basic statistics for selected voxel-based image
|
|
28652
|
-
* @param
|
|
28653
|
-
*
|
|
28654
|
-
*
|
|
28655
|
-
*
|
|
29004
|
+
* @param options - an object containing the following properties:
|
|
29005
|
+
* - layer: selects image to describe
|
|
29006
|
+
* - masks: optional binary images to filter voxels
|
|
29007
|
+
* - drawingIsMask: a boolean indicating if the drawing is used as a mask
|
|
29008
|
+
* - roiIsMask: a boolean indicating if the ROI is used as a mask
|
|
29009
|
+
* - startVox: the starting voxel coordinates
|
|
29010
|
+
* - endVox: the ending voxel coordinates
|
|
29011
|
+
* @returns numeric values to describe image or regions of images
|
|
29012
|
+
* @example
|
|
29013
|
+
* niivue.getDescriptives({
|
|
29014
|
+
* layer: 0,
|
|
29015
|
+
* masks: [],
|
|
29016
|
+
* drawingIsMask: true, // drawingIsMask and roiIsMask are mutually exclusive
|
|
29017
|
+
* roiIsMask: false,
|
|
29018
|
+
* startVox: [10, 20, 30], // ignored if roiIsMask is false
|
|
29019
|
+
* endVox: [40, 50, 60] // ignored if roiIsMask is false
|
|
29020
|
+
* });
|
|
28656
29021
|
* @see {@link https://niivue.github.io/niivue/features/draw2.html | live demo usage}
|
|
28657
29022
|
*/
|
|
28658
|
-
getDescriptives(
|
|
29023
|
+
getDescriptives(options) {
|
|
29024
|
+
const {
|
|
29025
|
+
layer = 0,
|
|
29026
|
+
masks = [],
|
|
29027
|
+
drawingIsMask = false,
|
|
29028
|
+
roiIsMask = false,
|
|
29029
|
+
startVox = [0, 0, 0],
|
|
29030
|
+
endVox = [0, 0, 0]
|
|
29031
|
+
} = options;
|
|
29032
|
+
let area = null;
|
|
28659
29033
|
const hdr = this.volumes[layer].hdr;
|
|
29034
|
+
const pixDimsRAS = this.volumes[layer].pixDimsRAS;
|
|
28660
29035
|
let slope = hdr.scl_slope;
|
|
28661
29036
|
if (isNaN(slope)) {
|
|
28662
29037
|
slope = 1;
|
|
@@ -28694,6 +29069,65 @@ var Niivue = class _Niivue {
|
|
|
28694
29069
|
mask[i] = 0;
|
|
28695
29070
|
}
|
|
28696
29071
|
}
|
|
29072
|
+
} else if (masks.length < 1 && roiIsMask) {
|
|
29073
|
+
mask.fill(0);
|
|
29074
|
+
console.log("startVox", startVox);
|
|
29075
|
+
console.log("endVox", endVox);
|
|
29076
|
+
let constantDim = -1;
|
|
29077
|
+
if (startVox[0] === endVox[0]) {
|
|
29078
|
+
constantDim = 0;
|
|
29079
|
+
} else if (startVox[1] === endVox[1]) {
|
|
29080
|
+
constantDim = 1;
|
|
29081
|
+
} else if (startVox[2] === endVox[2]) {
|
|
29082
|
+
constantDim = 2;
|
|
29083
|
+
} else {
|
|
29084
|
+
console.error("Error: No constant dimension found.");
|
|
29085
|
+
return;
|
|
29086
|
+
}
|
|
29087
|
+
const dims = [0, 1, 2];
|
|
29088
|
+
const varDims = dims.filter((dim) => dim !== constantDim);
|
|
29089
|
+
const centerVox = [];
|
|
29090
|
+
centerVox[constantDim] = startVox[constantDim];
|
|
29091
|
+
centerVox[varDims[0]] = (startVox[varDims[0]] + endVox[varDims[0]]) / 2;
|
|
29092
|
+
centerVox[varDims[1]] = (startVox[varDims[1]] + endVox[varDims[1]]) / 2;
|
|
29093
|
+
const radiusX = Math.abs(endVox[varDims[0]] - startVox[varDims[0]]) / 2;
|
|
29094
|
+
const radiusY = Math.abs(endVox[varDims[1]] - startVox[varDims[1]]) / 2;
|
|
29095
|
+
const xdim = hdr.dims[1];
|
|
29096
|
+
const ydim = hdr.dims[2];
|
|
29097
|
+
const minVarDim0 = Math.max(0, Math.floor(centerVox[varDims[0]] - radiusX));
|
|
29098
|
+
const maxVarDim0 = Math.min(hdr.dims[varDims[0] + 1] - 1, Math.ceil(centerVox[varDims[0]] + radiusX));
|
|
29099
|
+
const minVarDim1 = Math.max(0, Math.floor(centerVox[varDims[1]] - radiusY));
|
|
29100
|
+
const maxVarDim1 = Math.min(hdr.dims[varDims[1] + 1] - 1, Math.ceil(centerVox[varDims[1]] + radiusY));
|
|
29101
|
+
const constDimVal = centerVox[constantDim];
|
|
29102
|
+
if (constDimVal < 0 || constDimVal >= hdr.dims[constantDim + 1]) {
|
|
29103
|
+
console.error("Error: Constant dimension value is out of bounds.");
|
|
29104
|
+
return;
|
|
29105
|
+
}
|
|
29106
|
+
for (let i = minVarDim0; i <= maxVarDim0; i++) {
|
|
29107
|
+
for (let j = minVarDim1; j <= maxVarDim1; j++) {
|
|
29108
|
+
const voxel = [];
|
|
29109
|
+
voxel[constantDim] = constDimVal;
|
|
29110
|
+
voxel[varDims[0]] = i;
|
|
29111
|
+
voxel[varDims[1]] = j;
|
|
29112
|
+
const di = (voxel[varDims[0]] - centerVox[varDims[0]]) / radiusX;
|
|
29113
|
+
const dj = (voxel[varDims[1]] - centerVox[varDims[1]]) / radiusY;
|
|
29114
|
+
const distSq = di * di + dj * dj;
|
|
29115
|
+
if (distSq <= 1) {
|
|
29116
|
+
const x = voxel[0];
|
|
29117
|
+
const y = voxel[1];
|
|
29118
|
+
const z = voxel[2];
|
|
29119
|
+
const index = z * xdim * ydim + y * xdim + x;
|
|
29120
|
+
mask[index] = 1;
|
|
29121
|
+
}
|
|
29122
|
+
}
|
|
29123
|
+
}
|
|
29124
|
+
const voxelArea = pixDimsRAS[varDims[0] + 1] * pixDimsRAS[varDims[1] + 1];
|
|
29125
|
+
const numMaskedVoxels = mask.reduce((count, value) => count + (value === 1 ? 1 : 0), 0);
|
|
29126
|
+
area = numMaskedVoxels * voxelArea;
|
|
29127
|
+
const radiusX_mm = radiusX * pixDimsRAS[varDims[0] + 1];
|
|
29128
|
+
const radiusY_mm = radiusY * pixDimsRAS[varDims[1] + 1];
|
|
29129
|
+
const areaEllipse = Math.PI * radiusX_mm * radiusY_mm;
|
|
29130
|
+
area = areaEllipse;
|
|
28697
29131
|
}
|
|
28698
29132
|
let k = 0;
|
|
28699
29133
|
let M = 0;
|
|
@@ -28747,7 +29181,8 @@ var Niivue = class _Niivue {
|
|
|
28747
29181
|
cal_min: this.volumes[layer].cal_min,
|
|
28748
29182
|
cal_max: this.volumes[layer].cal_max,
|
|
28749
29183
|
robust_min: this.volumes[layer].robust_min,
|
|
28750
|
-
robust_max: this.volumes[layer].robust_max
|
|
29184
|
+
robust_max: this.volumes[layer].robust_max,
|
|
29185
|
+
area
|
|
28751
29186
|
};
|
|
28752
29187
|
}
|
|
28753
29188
|
// not included in public docs
|
|
@@ -30225,6 +30660,29 @@ var Niivue = class _Niivue {
|
|
|
30225
30660
|
const pos = [(x - this.graph.LTWH[0]) / this.graph.LTWH[2], (y - this.graph.LTWH[1]) / this.graph.LTWH[3]];
|
|
30226
30661
|
return pos[0] > 0 && pos[1] > 0 && pos[0] <= 1 && pos[1] <= 1;
|
|
30227
30662
|
}
|
|
30663
|
+
// update drawBitmap if it differs from clickTosegmentBitmap
|
|
30664
|
+
updateBitmapFromClickToSegment() {
|
|
30665
|
+
if (this.clickToSegmentGrowingBitmap === null) {
|
|
30666
|
+
return;
|
|
30667
|
+
}
|
|
30668
|
+
if (this.drawBitmap === null) {
|
|
30669
|
+
return;
|
|
30670
|
+
}
|
|
30671
|
+
if (this.clickToSegmentGrowingBitmap.length !== this.drawBitmap.length) {
|
|
30672
|
+
return;
|
|
30673
|
+
}
|
|
30674
|
+
const nvx = this.drawBitmap.length;
|
|
30675
|
+
for (let i = 0; i < nvx; i++) {
|
|
30676
|
+
this.drawBitmap[i] = this.clickToSegmentGrowingBitmap[i];
|
|
30677
|
+
}
|
|
30678
|
+
}
|
|
30679
|
+
sumBitmap(img) {
|
|
30680
|
+
let sum = 0;
|
|
30681
|
+
for (let i = 0; i < img.length; i++) {
|
|
30682
|
+
sum += img[i];
|
|
30683
|
+
}
|
|
30684
|
+
return sum;
|
|
30685
|
+
}
|
|
30228
30686
|
// not included in public docs
|
|
30229
30687
|
// handle mouse click event on canvas
|
|
30230
30688
|
mouseClick(x, y, posChange = 0, isDelta = true) {
|
|
@@ -30274,7 +30732,7 @@ var Niivue = class _Niivue {
|
|
|
30274
30732
|
if (axCorSag > 2 /* SAGITTAL */) {
|
|
30275
30733
|
continue;
|
|
30276
30734
|
}
|
|
30277
|
-
|
|
30735
|
+
let texFrac = this.screenXY2TextureFrac(x, y, i, false);
|
|
30278
30736
|
if (texFrac[0] < 0) {
|
|
30279
30737
|
continue;
|
|
30280
30738
|
}
|
|
@@ -30301,31 +30759,75 @@ var Niivue = class _Niivue {
|
|
|
30301
30759
|
this.scene.crosshairPos = vec311.clone(texFrac);
|
|
30302
30760
|
}
|
|
30303
30761
|
if (this.opts.drawingEnabled) {
|
|
30304
|
-
|
|
30305
|
-
const ptMM = this.frac2mm(this.scene.crosshairPos);
|
|
30762
|
+
let pt = this.frac2vox(this.scene.crosshairPos);
|
|
30306
30763
|
if (this.opts.clickToSegment) {
|
|
30307
|
-
|
|
30308
|
-
|
|
30764
|
+
texFrac = this.screenXY2TextureFrac(this.clickToSegmentXY[0], this.clickToSegmentXY[1], i, false);
|
|
30765
|
+
pt = this.frac2vox(texFrac);
|
|
30766
|
+
let diff = 0;
|
|
30767
|
+
let threshold = this.opts.clickToSegmentPercent + diff;
|
|
30768
|
+
if (this.uiData.mousedown) {
|
|
30769
|
+
const xDiff = (this.clickToSegmentXY[0] - x) / this.gl.canvas.width;
|
|
30770
|
+
const yDiff = (this.clickToSegmentXY[1] - y) / this.gl.canvas.height;
|
|
30771
|
+
diff = Math.max(Math.abs(xDiff), Math.abs(yDiff));
|
|
30772
|
+
threshold = this.opts.clickToSegmentPercent + diff;
|
|
30773
|
+
}
|
|
30774
|
+
let voxelIntensity = this.back.getValue(pt[0], pt[1], pt[2]);
|
|
30775
|
+
if (this.opts.clickToSegmentAutoIntensity) {
|
|
30776
|
+
if (threshold !== 0) {
|
|
30777
|
+
if (voxelIntensity === 0) {
|
|
30778
|
+
voxelIntensity = 0.01;
|
|
30779
|
+
}
|
|
30780
|
+
this.opts.clickToSegmentIntensityMax = voxelIntensity * (1 + threshold);
|
|
30781
|
+
this.opts.clickToSegmentIntensityMin = voxelIntensity * (1 - threshold);
|
|
30782
|
+
}
|
|
30783
|
+
if (voxelIntensity > (this.back.cal_min + this.back.cal_max) * 0.5) {
|
|
30784
|
+
this.opts.clickToSegmentBright = true;
|
|
30785
|
+
} else {
|
|
30786
|
+
this.opts.clickToSegmentBright = false;
|
|
30787
|
+
}
|
|
30788
|
+
}
|
|
30309
30789
|
const brightOrDark = this.opts.clickToSegmentBright ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
|
|
30310
|
-
this.drawPenFillPts = [];
|
|
30311
30790
|
this.drawPenAxCorSag = axCorSag;
|
|
30312
|
-
|
|
30313
|
-
const
|
|
30314
|
-
const
|
|
30315
|
-
|
|
30316
|
-
|
|
30317
|
-
const
|
|
30318
|
-
const
|
|
30319
|
-
|
|
30320
|
-
|
|
30321
|
-
this.
|
|
30322
|
-
|
|
30323
|
-
|
|
30324
|
-
}
|
|
30791
|
+
if (this.drawPenAxCorSag === 0 /* AXIAL */) {
|
|
30792
|
+
const pixDims = [this.back.pixDimsRAS[0], this.back.pixDimsRAS[1]];
|
|
30793
|
+
const minPixDim = Math.min(...pixDims);
|
|
30794
|
+
this.opts.penSize = Math.ceil(this.opts.clickToSegmentRadius / minPixDim);
|
|
30795
|
+
} else if (this.drawPenAxCorSag === 1 /* CORONAL */) {
|
|
30796
|
+
const pixDims = [this.back.pixDimsRAS[0], this.back.pixDimsRAS[2]];
|
|
30797
|
+
const minPixDim = Math.min(...pixDims);
|
|
30798
|
+
this.opts.penSize = Math.ceil(this.opts.clickToSegmentRadius / minPixDim);
|
|
30799
|
+
} else if (this.drawPenAxCorSag === 2 /* SAGITTAL */) {
|
|
30800
|
+
const pixDims = [this.back.pixDimsRAS[1], this.back.pixDimsRAS[2]];
|
|
30801
|
+
const minPixDim = Math.min(...pixDims);
|
|
30802
|
+
this.opts.penSize = Math.ceil(this.opts.clickToSegmentRadius / minPixDim);
|
|
30325
30803
|
}
|
|
30804
|
+
this.drawPt(pt[0], pt[1], pt[2], this.opts.penValue);
|
|
30805
|
+
if (diff !== 0) {
|
|
30806
|
+
this.clickToSegmentIsGrowing = true;
|
|
30807
|
+
this.clickToSegmentGrowingBitmap = this.drawBitmap.slice();
|
|
30808
|
+
} else {
|
|
30809
|
+
this.clickToSegmentIsGrowing = false;
|
|
30810
|
+
}
|
|
30811
|
+
this.drawFloodFill(
|
|
30812
|
+
[pt[0], pt[1], pt[2]],
|
|
30813
|
+
0,
|
|
30814
|
+
brightOrDark,
|
|
30815
|
+
this.opts.clickToSegmentIntensityMin,
|
|
30816
|
+
this.opts.clickToSegmentIntensityMax,
|
|
30817
|
+
this.opts.floodFillNeighbors,
|
|
30818
|
+
this.opts.clickToSegmentMaxDistanceMM,
|
|
30819
|
+
this.opts.clickToSegmentIs2D
|
|
30820
|
+
);
|
|
30326
30821
|
this.drawScene();
|
|
30327
30822
|
this.createOnLocationChange(axCorSag);
|
|
30328
|
-
|
|
30823
|
+
if (this.clickToSegmentIsGrowing) {
|
|
30824
|
+
return;
|
|
30825
|
+
}
|
|
30826
|
+
const info = this.getDescriptives({
|
|
30827
|
+
layer: 0,
|
|
30828
|
+
masks: [],
|
|
30829
|
+
drawingIsMask: true
|
|
30830
|
+
});
|
|
30329
30831
|
this.onClickToSegment({ mL: info.volumeML, mm3: info.volumeMM3 });
|
|
30330
30832
|
return;
|
|
30331
30833
|
}
|
|
@@ -30543,20 +31045,42 @@ var Niivue = class _Niivue {
|
|
|
30543
31045
|
if (!this.rectShader) {
|
|
30544
31046
|
throw new Error("rectShader undefined");
|
|
30545
31047
|
}
|
|
30546
|
-
this.
|
|
30547
|
-
|
|
30548
|
-
|
|
30549
|
-
|
|
30550
|
-
|
|
30551
|
-
this.
|
|
30552
|
-
|
|
30553
|
-
|
|
30554
|
-
|
|
30555
|
-
|
|
30556
|
-
|
|
30557
|
-
|
|
30558
|
-
|
|
30559
|
-
|
|
31048
|
+
if (!this.opts.selectionBoxIsOutline) {
|
|
31049
|
+
this.rectShader.use(this.gl);
|
|
31050
|
+
this.gl.enable(this.gl.BLEND);
|
|
31051
|
+
this.gl.uniform4fv(this.rectShader.uniforms.lineColor, lineColor);
|
|
31052
|
+
this.gl.uniform2fv(this.rectShader.uniforms.canvasWidthHeight, [this.gl.canvas.width, this.gl.canvas.height]);
|
|
31053
|
+
this.gl.uniform4f(
|
|
31054
|
+
this.rectShader.uniforms.leftTopWidthHeight,
|
|
31055
|
+
leftTopWidthHeight[0],
|
|
31056
|
+
leftTopWidthHeight[1],
|
|
31057
|
+
leftTopWidthHeight[2],
|
|
31058
|
+
leftTopWidthHeight[3]
|
|
31059
|
+
);
|
|
31060
|
+
this.gl.bindVertexArray(this.genericVAO);
|
|
31061
|
+
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
31062
|
+
this.gl.bindVertexArray(this.unusedVAO);
|
|
31063
|
+
} else {
|
|
31064
|
+
this.drawCircle(leftTopWidthHeight, lineColor, 0.1);
|
|
31065
|
+
this.rectOutlineShader.use(this.gl);
|
|
31066
|
+
this.gl.enable(this.gl.BLEND);
|
|
31067
|
+
this.gl.uniform1f(this.rectOutlineShader.uniforms.thickness, this.opts.selectionBoxLineThickness);
|
|
31068
|
+
this.gl.uniform4fv(this.rectOutlineShader.uniforms.lineColor, lineColor);
|
|
31069
|
+
this.gl.uniform2fv(this.rectOutlineShader.uniforms.canvasWidthHeight, [
|
|
31070
|
+
this.gl.canvas.width,
|
|
31071
|
+
this.gl.canvas.height
|
|
31072
|
+
]);
|
|
31073
|
+
this.gl.uniform4f(
|
|
31074
|
+
this.rectOutlineShader.uniforms.leftTopWidthHeight,
|
|
31075
|
+
leftTopWidthHeight[0],
|
|
31076
|
+
leftTopWidthHeight[1],
|
|
31077
|
+
leftTopWidthHeight[2],
|
|
31078
|
+
leftTopWidthHeight[3]
|
|
31079
|
+
);
|
|
31080
|
+
this.gl.bindVertexArray(this.genericVAO);
|
|
31081
|
+
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
31082
|
+
this.gl.bindVertexArray(this.unusedVAO);
|
|
31083
|
+
}
|
|
30560
31084
|
}
|
|
30561
31085
|
drawCircle(leftTopWidthHeight, circleColor = this.opts.fontColor, fillPercent = 1) {
|
|
30562
31086
|
if (!this.circleShader) {
|
|
@@ -30582,6 +31106,10 @@ var Niivue = class _Niivue {
|
|
|
30582
31106
|
// not included in public docs
|
|
30583
31107
|
// draw a rectangle at desired location
|
|
30584
31108
|
drawSelectionBox(leftTopWidthHeight) {
|
|
31109
|
+
if (this.opts.dragMode === 6 /* roiSelection */) {
|
|
31110
|
+
this.drawCircle(leftTopWidthHeight, this.opts.selectionBoxColor, 0.1);
|
|
31111
|
+
return;
|
|
31112
|
+
}
|
|
30585
31113
|
this.drawRect(leftTopWidthHeight, this.opts.selectionBoxColor);
|
|
30586
31114
|
}
|
|
30587
31115
|
// not included in public docs
|
|
@@ -31572,10 +32100,10 @@ var Niivue = class _Niivue {
|
|
|
31572
32100
|
if (graph.opacity <= 0 || graph.LTWH[2] <= 5 || graph.LTWH[3] <= 5) {
|
|
31573
32101
|
return;
|
|
31574
32102
|
}
|
|
31575
|
-
if (graph.LTWH[0] + graph.LTWH[2] > this.gl.canvas.width) {
|
|
32103
|
+
if (Math.floor(graph.LTWH[0] + graph.LTWH[2]) > this.gl.canvas.width) {
|
|
31576
32104
|
return;
|
|
31577
32105
|
}
|
|
31578
|
-
if (graph.LTWH[1] + graph.LTWH[3] > this.gl.canvas.height) {
|
|
32106
|
+
if (Math.floor(graph.LTWH[1] + graph.LTWH[3]) > this.gl.canvas.height) {
|
|
31579
32107
|
return;
|
|
31580
32108
|
}
|
|
31581
32109
|
graph.backColor = [0.15, 0.15, 0.15, graph.opacity];
|
|
@@ -31645,6 +32173,12 @@ var Niivue = class _Niivue {
|
|
|
31645
32173
|
mx = Math.max(v, mx);
|
|
31646
32174
|
}
|
|
31647
32175
|
}
|
|
32176
|
+
const volMn = this.volumes[vols[0]].cal_min;
|
|
32177
|
+
const volMx = this.volumes[vols[0]].cal_max;
|
|
32178
|
+
if (graph.isRangeCalMinMax && volMn < volMx && isFinite(volMn) && isFinite(volMx)) {
|
|
32179
|
+
mn = volMn;
|
|
32180
|
+
mx = volMx;
|
|
32181
|
+
}
|
|
31648
32182
|
if (graph.normalizeValues && mx > mn) {
|
|
31649
32183
|
const range = mx - mn;
|
|
31650
32184
|
for (let j = 0; j < graph.lines.length; j++) {
|
|
@@ -33258,10 +33792,14 @@ var Niivue = class _Niivue {
|
|
|
33258
33792
|
width,
|
|
33259
33793
|
height
|
|
33260
33794
|
]);
|
|
33795
|
+
return;
|
|
33261
33796
|
}
|
|
33262
33797
|
if (this.opts.clickToSegment) {
|
|
33263
33798
|
const x = this.mousePos[0];
|
|
33264
33799
|
const y = this.mousePos[1];
|
|
33800
|
+
if (this.inRenderTile(x, y) >= 0) {
|
|
33801
|
+
return;
|
|
33802
|
+
}
|
|
33265
33803
|
const tileIdx = this.tileIndex(x, y);
|
|
33266
33804
|
if (tileIdx > -1) {
|
|
33267
33805
|
const fovMM = this.screenSlices[tileIdx].fovMM;
|