@niivue/niivue 0.44.2-dev.3 → 0.44.2-dev.4
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 +79 -16
- package/build/index.js +630 -208
- package/build/index.js.map +1 -1
- package/build/index.min.js +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/niivue.umd.js +1869 -0
- package/package.json +1 -1
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.44.2-dev.4";
|
|
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;
|
|
@@ -22250,6 +22290,7 @@ var DRAG_MODE = /* @__PURE__ */ ((DRAG_MODE2) => {
|
|
|
22250
22290
|
DRAG_MODE2[DRAG_MODE2["pan"] = 3] = "pan";
|
|
22251
22291
|
DRAG_MODE2[DRAG_MODE2["slicer3D"] = 4] = "slicer3D";
|
|
22252
22292
|
DRAG_MODE2[DRAG_MODE2["callbackOnly"] = 5] = "callbackOnly";
|
|
22293
|
+
DRAG_MODE2[DRAG_MODE2["roiSelection"] = 6] = "roiSelection";
|
|
22253
22294
|
return DRAG_MODE2;
|
|
22254
22295
|
})(DRAG_MODE || {});
|
|
22255
22296
|
var DEFAULT_OPTIONS = {
|
|
@@ -22319,10 +22360,30 @@ var DEFAULT_OPTIONS = {
|
|
|
22319
22360
|
renderOverlayBlend: 1,
|
|
22320
22361
|
sliceMosaicString: "",
|
|
22321
22362
|
centerMosaic: false,
|
|
22363
|
+
penSize: 1,
|
|
22364
|
+
// in voxels, since all drawing is done using bitmap indices
|
|
22322
22365
|
clickToSegment: false,
|
|
22323
|
-
clickToSegmentRadius:
|
|
22324
|
-
|
|
22325
|
-
clickToSegmentBright: true
|
|
22366
|
+
clickToSegmentRadius: 3,
|
|
22367
|
+
// in mm
|
|
22368
|
+
clickToSegmentBright: true,
|
|
22369
|
+
clickToSegmentAutoIntensity: false,
|
|
22370
|
+
// new option, but keep clickToSegmentBright for backwards compatibility
|
|
22371
|
+
clickToSegmentIntensityMax: NaN,
|
|
22372
|
+
// NaN will use auto threshold (default flood fill behavior from before)
|
|
22373
|
+
clickToSegmentIntensityMin: NaN,
|
|
22374
|
+
// NaN will use auto threshold (default flood fill behavior from before)
|
|
22375
|
+
// 0 will use auto threshold (default flood fill behavior from before)
|
|
22376
|
+
// Take the voxel intensity at the click point and use this percentage +/- to threshold the flood fill operation.
|
|
22377
|
+
// If greater than 0, clickedVoxelIntensity +/- clickedVoxelIntensity * clickToSegmentPercent will be used
|
|
22378
|
+
// for the clickToSegmentIntensityMin and clickToSegmentIntensityMax values.
|
|
22379
|
+
clickToSegmentPercent: 0,
|
|
22380
|
+
clickToSegmentMaxDistanceMM: Number.POSITIVE_INFINITY,
|
|
22381
|
+
// default value is infinity for backwards compatibility with flood fill routine.
|
|
22382
|
+
clickToSegmentIs2D: false,
|
|
22383
|
+
selectionBoxLineThickness: 4,
|
|
22384
|
+
selectionBoxIsOutline: false,
|
|
22385
|
+
scrollRequiresFocus: false
|
|
22386
|
+
// determines if the cavas need to be focused to scroll
|
|
22326
22387
|
};
|
|
22327
22388
|
var INITIAL_SCENE_DATA = {
|
|
22328
22389
|
gamma: 1,
|
|
@@ -24019,12 +24080,11 @@ var defaultSaveImageOptions = {
|
|
|
24019
24080
|
isSaveDrawing: false,
|
|
24020
24081
|
volumeByIndex: 0
|
|
24021
24082
|
};
|
|
24022
|
-
var Niivue = class
|
|
24083
|
+
var Niivue = class {
|
|
24023
24084
|
/**
|
|
24024
24085
|
* @param options - options object to set modifiable Niivue properties
|
|
24025
24086
|
*/
|
|
24026
24087
|
constructor(options = DEFAULT_OPTIONS) {
|
|
24027
|
-
__publicField(this, "loaders", {});
|
|
24028
24088
|
__publicField(this, "canvas", null);
|
|
24029
24089
|
// the reference to the canvas element on the page
|
|
24030
24090
|
__publicField(this, "_gl", null);
|
|
@@ -24050,6 +24110,12 @@ var Niivue = class _Niivue {
|
|
|
24050
24110
|
// the color lookup table for drawing
|
|
24051
24111
|
__publicField(this, "drawOpacity", 0.8);
|
|
24052
24112
|
// opacity of drawing (default)
|
|
24113
|
+
__publicField(this, "clickToSegmentIsGrowing", false);
|
|
24114
|
+
// flag to indicate if the clickToSegment flood fill growing is in progress with left mouse down + drag
|
|
24115
|
+
__publicField(this, "clickToSegmentGrowingBitmap", null);
|
|
24116
|
+
// the bitmap of the growing flood fill
|
|
24117
|
+
__publicField(this, "clickToSegmentXY", [0, 0]);
|
|
24118
|
+
// the x,y location of the clickToSegment flood fill
|
|
24053
24119
|
__publicField(this, "renderDrawAmbientOcclusion", 0.4);
|
|
24054
24120
|
__publicField(this, "colorbarHeight", 0);
|
|
24055
24121
|
// height in pixels, set when colorbar is drawn
|
|
@@ -24067,6 +24133,7 @@ var Niivue = class _Niivue {
|
|
|
24067
24133
|
__publicField(this, "orientCubeShader");
|
|
24068
24134
|
__publicField(this, "orientCubeShaderVAO", null);
|
|
24069
24135
|
__publicField(this, "rectShader");
|
|
24136
|
+
__publicField(this, "rectOutlineShader");
|
|
24070
24137
|
__publicField(this, "renderShader");
|
|
24071
24138
|
__publicField(this, "lineShader");
|
|
24072
24139
|
__publicField(this, "line3DShader");
|
|
@@ -24119,7 +24186,20 @@ var Niivue = class _Niivue {
|
|
|
24119
24186
|
__publicField(this, "extentsMax");
|
|
24120
24187
|
// ResizeObserver
|
|
24121
24188
|
__publicField(this, "resizeObserver", null);
|
|
24122
|
-
|
|
24189
|
+
// syncOpts: Record<string, unknown> = {}
|
|
24190
|
+
__publicField(this, "syncOpts", {
|
|
24191
|
+
"3d": false,
|
|
24192
|
+
// legacy option
|
|
24193
|
+
"2d": false,
|
|
24194
|
+
// legacy option
|
|
24195
|
+
zoomPan: false,
|
|
24196
|
+
cal_min: false,
|
|
24197
|
+
cal_max: false,
|
|
24198
|
+
clipPlane: false,
|
|
24199
|
+
gamma: false,
|
|
24200
|
+
sliceType: false,
|
|
24201
|
+
crosshair: false
|
|
24202
|
+
});
|
|
24123
24203
|
__publicField(this, "readyForSync", false);
|
|
24124
24204
|
// UI Data
|
|
24125
24205
|
__publicField(this, "uiData", {
|
|
@@ -24598,6 +24678,9 @@ var Niivue = class _Niivue {
|
|
|
24598
24678
|
* @see {@link https://niivue.github.io/niivue/features/sync.mesh.html | live demo usage}
|
|
24599
24679
|
*/
|
|
24600
24680
|
syncWith(otherNV, syncOpts = { "2d": true, "3d": true }) {
|
|
24681
|
+
if (!(otherNV instanceof Array)) {
|
|
24682
|
+
otherNV = [otherNV];
|
|
24683
|
+
}
|
|
24601
24684
|
this.otherNV = otherNV;
|
|
24602
24685
|
this.syncOpts = { ...syncOpts };
|
|
24603
24686
|
}
|
|
@@ -24613,9 +24696,55 @@ var Niivue = class _Niivue {
|
|
|
24613
24696
|
* @see {@link https://niivue.github.io/niivue/features/sync.mesh.html | live demo usage}
|
|
24614
24697
|
*/
|
|
24615
24698
|
broadcastTo(otherNV, syncOpts = { "2d": true, "3d": true }) {
|
|
24699
|
+
if (!(otherNV instanceof Array)) {
|
|
24700
|
+
otherNV = [otherNV];
|
|
24701
|
+
}
|
|
24616
24702
|
this.otherNV = otherNV;
|
|
24617
24703
|
this.syncOpts = syncOpts;
|
|
24618
24704
|
}
|
|
24705
|
+
doSync3d(otherNV) {
|
|
24706
|
+
otherNV.scene.renderAzimuth = this.scene.renderAzimuth;
|
|
24707
|
+
otherNV.scene.renderElevation = this.scene.renderElevation;
|
|
24708
|
+
otherNV.scene.volScaleMultiplier = this.scene.volScaleMultiplier;
|
|
24709
|
+
}
|
|
24710
|
+
// both crosshair and zoomPan
|
|
24711
|
+
doSync2d(otherNV) {
|
|
24712
|
+
const thisMM = this.frac2mm(this.scene.crosshairPos);
|
|
24713
|
+
otherNV.scene.crosshairPos = otherNV.mm2frac(thisMM);
|
|
24714
|
+
otherNV.scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24715
|
+
}
|
|
24716
|
+
doSyncGamma(otherNV) {
|
|
24717
|
+
const thisGamma = this.scene.gamma;
|
|
24718
|
+
const otherGamma = otherNV.scene.gamma;
|
|
24719
|
+
if (thisGamma !== otherGamma) {
|
|
24720
|
+
otherNV.setGamma(thisGamma);
|
|
24721
|
+
}
|
|
24722
|
+
}
|
|
24723
|
+
doSyncZoomPan(otherNV) {
|
|
24724
|
+
otherNV.scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24725
|
+
}
|
|
24726
|
+
doSyncCrosshair(otherNV) {
|
|
24727
|
+
const thisMM = this.frac2mm(this.scene.crosshairPos);
|
|
24728
|
+
otherNV.scene.crosshairPos = otherNV.mm2frac(thisMM);
|
|
24729
|
+
}
|
|
24730
|
+
doSyncCalMin(otherNV) {
|
|
24731
|
+
if (this.volumes[0].cal_min !== otherNV.volumes[0].cal_min) {
|
|
24732
|
+
otherNV.volumes[0].cal_min = this.volumes[0].cal_min;
|
|
24733
|
+
otherNV.updateGLVolume();
|
|
24734
|
+
}
|
|
24735
|
+
}
|
|
24736
|
+
doSyncCalMax(otherNV) {
|
|
24737
|
+
if (this.volumes[0].cal_max !== otherNV.volumes[0].cal_max) {
|
|
24738
|
+
otherNV.volumes[0].cal_max = this.volumes[0].cal_max;
|
|
24739
|
+
otherNV.updateGLVolume();
|
|
24740
|
+
}
|
|
24741
|
+
}
|
|
24742
|
+
doSyncSliceType(otherNV) {
|
|
24743
|
+
otherNV.setSliceType(this.opts.sliceType);
|
|
24744
|
+
}
|
|
24745
|
+
doSyncClipPlane(otherNV) {
|
|
24746
|
+
otherNV.setClipPlane(this.scene.clipPlaneDepthAziElev);
|
|
24747
|
+
}
|
|
24619
24748
|
/**
|
|
24620
24749
|
* Sync the scene controls (orientation, crosshair location, etc.) from one Niivue instance to another. useful for using one canvas to drive another.
|
|
24621
24750
|
* @internal
|
|
@@ -24632,46 +24761,39 @@ var Niivue = class _Niivue {
|
|
|
24632
24761
|
if (!this.gl.canvas.matches(":focus")) {
|
|
24633
24762
|
return;
|
|
24634
24763
|
}
|
|
24635
|
-
|
|
24636
|
-
|
|
24637
|
-
|
|
24638
|
-
|
|
24639
|
-
if (
|
|
24640
|
-
this.otherNV
|
|
24764
|
+
for (let i = 0; i < this.otherNV.length; i++) {
|
|
24765
|
+
if (this.otherNV[i] === this) {
|
|
24766
|
+
continue;
|
|
24767
|
+
}
|
|
24768
|
+
if (this.syncOpts.gamma) {
|
|
24769
|
+
this.doSyncGamma(this.otherNV[i]);
|
|
24770
|
+
}
|
|
24771
|
+
if (this.syncOpts.crosshair) {
|
|
24772
|
+
this.doSyncCrosshair(this.otherNV[i]);
|
|
24773
|
+
}
|
|
24774
|
+
if (this.syncOpts.zoomPan) {
|
|
24775
|
+
this.doSyncZoomPan(this.otherNV[i]);
|
|
24776
|
+
}
|
|
24777
|
+
if (this.syncOpts.sliceType) {
|
|
24778
|
+
this.doSyncSliceType(this.otherNV[i]);
|
|
24779
|
+
}
|
|
24780
|
+
if (this.syncOpts.cal_min) {
|
|
24781
|
+
this.doSyncCalMin(this.otherNV[i]);
|
|
24782
|
+
}
|
|
24783
|
+
if (this.syncOpts.cal_max) {
|
|
24784
|
+
this.doSyncCalMax(this.otherNV[i]);
|
|
24785
|
+
}
|
|
24786
|
+
if (this.syncOpts.clipPlane) {
|
|
24787
|
+
this.doSyncClipPlane(this.otherNV[i]);
|
|
24641
24788
|
}
|
|
24642
24789
|
if (this.syncOpts["2d"]) {
|
|
24643
|
-
this.
|
|
24644
|
-
this.otherNV.scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24790
|
+
this.doSync2d(this.otherNV[i]);
|
|
24645
24791
|
}
|
|
24646
24792
|
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();
|
|
24793
|
+
this.doSync3d(this.otherNV[i]);
|
|
24674
24794
|
}
|
|
24795
|
+
this.otherNV[i].drawScene();
|
|
24796
|
+
this.otherNV[i].createOnLocationChange();
|
|
24675
24797
|
}
|
|
24676
24798
|
}
|
|
24677
24799
|
/** Not documented publicly for now
|
|
@@ -24766,6 +24888,8 @@ var Niivue = class _Niivue {
|
|
|
24766
24888
|
this.drawPenLocation = [NaN, NaN, NaN];
|
|
24767
24889
|
this.drawPenAxCorSag = -1;
|
|
24768
24890
|
this.uiData.mousedown = true;
|
|
24891
|
+
this.setDragStart(0, 0);
|
|
24892
|
+
this.setDragEnd(0, 0);
|
|
24769
24893
|
log.debug("mouse down");
|
|
24770
24894
|
log.debug(e);
|
|
24771
24895
|
const pos = this.getNoPaddingNoBorderCanvasRelativeMousePosition(e, this.gl.canvas);
|
|
@@ -24773,6 +24897,9 @@ var Niivue = class _Niivue {
|
|
|
24773
24897
|
return;
|
|
24774
24898
|
}
|
|
24775
24899
|
const [x, y] = [pos.x * this.uiData.dpr, pos.y * this.uiData.dpr];
|
|
24900
|
+
if (this.opts.clickToSegment) {
|
|
24901
|
+
this.clickToSegmentXY = [x, y];
|
|
24902
|
+
}
|
|
24776
24903
|
const label = this.getLabelAtPoint([x, y]);
|
|
24777
24904
|
if (label) {
|
|
24778
24905
|
if (label.onClick) {
|
|
@@ -24973,8 +25100,15 @@ var Niivue = class _Niivue {
|
|
|
24973
25100
|
if (this.drawPenFillPts.length > 0) {
|
|
24974
25101
|
this.drawPenFilled();
|
|
24975
25102
|
} else if (this.drawPenAxCorSag >= 0) {
|
|
24976
|
-
this.
|
|
25103
|
+
if (this.opts.clickToSegment) {
|
|
25104
|
+
const sumBitmap = this.sumBitmap(this.drawBitmap);
|
|
25105
|
+
const sumSeg = this.sumBitmap(this.clickToSegmentGrowingBitmap);
|
|
25106
|
+
if (sumSeg > sumBitmap) {
|
|
25107
|
+
this.updateBitmapFromClickToSegment();
|
|
25108
|
+
}
|
|
25109
|
+
}
|
|
24977
25110
|
}
|
|
25111
|
+
this.drawAddUndoBitmap();
|
|
24978
25112
|
this.drawPenLocation = [NaN, NaN, NaN];
|
|
24979
25113
|
this.drawPenAxCorSag = -1;
|
|
24980
25114
|
if (isFunction(this.onMouseUp)) {
|
|
@@ -24988,6 +25122,9 @@ var Niivue = class _Niivue {
|
|
|
24988
25122
|
const fracStart = this.canvasPos2frac([this.uiData.dragStart[0], this.uiData.dragStart[1]]);
|
|
24989
25123
|
const fracEnd = this.canvasPos2frac([this.uiData.dragEnd[0], this.uiData.dragEnd[1]]);
|
|
24990
25124
|
this.generateMouseUpCallback(fracStart, fracEnd);
|
|
25125
|
+
if (this.opts.dragMode === 6 /* roiSelection */) {
|
|
25126
|
+
return;
|
|
25127
|
+
}
|
|
24991
25128
|
if (this.opts.dragMode !== 1 /* contrast */) {
|
|
24992
25129
|
return;
|
|
24993
25130
|
}
|
|
@@ -25290,6 +25427,35 @@ var Niivue = class _Niivue {
|
|
|
25290
25427
|
if (this.thumbnailVisible) {
|
|
25291
25428
|
return;
|
|
25292
25429
|
}
|
|
25430
|
+
const dragStartSum = this.uiData.dragStart.reduce((a, b) => a + b, 0);
|
|
25431
|
+
const dragEndSum = this.uiData.dragEnd.reduce((a, b) => a + b, 0);
|
|
25432
|
+
const validDrag = dragStartSum > 0 && dragEndSum > 0;
|
|
25433
|
+
if (this.opts.dragMode === 6 /* roiSelection */ && validDrag) {
|
|
25434
|
+
const delta = e.deltaY > 0 ? 1 : -1;
|
|
25435
|
+
if (this.uiData.dragStart[0] < this.uiData.dragEnd[0]) {
|
|
25436
|
+
this.uiData.dragStart[0] -= delta;
|
|
25437
|
+
this.uiData.dragEnd[0] += delta;
|
|
25438
|
+
} else {
|
|
25439
|
+
this.uiData.dragStart[0] += delta;
|
|
25440
|
+
this.uiData.dragEnd[0] -= delta;
|
|
25441
|
+
}
|
|
25442
|
+
if (this.uiData.dragStart[1] < this.uiData.dragEnd[1]) {
|
|
25443
|
+
this.uiData.dragStart[1] -= delta;
|
|
25444
|
+
this.uiData.dragEnd[1] += delta;
|
|
25445
|
+
} else {
|
|
25446
|
+
this.uiData.dragStart[1] += delta;
|
|
25447
|
+
this.uiData.dragEnd[1] -= delta;
|
|
25448
|
+
}
|
|
25449
|
+
this.uiData.isDragging = true;
|
|
25450
|
+
this.drawScene();
|
|
25451
|
+
this.uiData.isDragging = false;
|
|
25452
|
+
const tileIdx = this.tileIndex(this.uiData.dragStart[0], this.uiData.dragStart[1]);
|
|
25453
|
+
this.generateMouseUpCallback(
|
|
25454
|
+
this.screenXY2TextureFrac(this.uiData.dragStart[0], this.uiData.dragStart[1], tileIdx),
|
|
25455
|
+
this.screenXY2TextureFrac(this.uiData.dragEnd[0], this.uiData.dragEnd[1], tileIdx)
|
|
25456
|
+
);
|
|
25457
|
+
return;
|
|
25458
|
+
}
|
|
25293
25459
|
const rect = this.canvas.getBoundingClientRect();
|
|
25294
25460
|
if (e.deltaY < 0) {
|
|
25295
25461
|
this.sliceScroll2D(-0.01, e.clientX - rect.left, e.clientY - rect.top);
|
|
@@ -25339,11 +25505,6 @@ var Niivue = class _Niivue {
|
|
|
25339
25505
|
if (ext === "GZ") {
|
|
25340
25506
|
ext = re.exec(fullname.slice(0, -3))[1];
|
|
25341
25507
|
ext = ext.toUpperCase();
|
|
25342
|
-
} else if (ext === "CBOR") {
|
|
25343
|
-
const endExt = ext;
|
|
25344
|
-
ext = re.exec(fullname.slice(0, -5))[1];
|
|
25345
|
-
ext = ext.toUpperCase();
|
|
25346
|
-
ext = `${ext}.${endExt}`;
|
|
25347
25508
|
}
|
|
25348
25509
|
return upperCase ? ext : ext.toLowerCase();
|
|
25349
25510
|
}
|
|
@@ -25364,28 +25525,6 @@ var Niivue = class _Niivue {
|
|
|
25364
25525
|
}
|
|
25365
25526
|
async addVolumesFromUrl(imageOptionsArray) {
|
|
25366
25527
|
const promises = imageOptionsArray.map(async (imageItem) => {
|
|
25367
|
-
const ext = this.getFileExt(imageItem.name || imageItem.url);
|
|
25368
|
-
if (this.loaders[ext]) {
|
|
25369
|
-
let itemToLoad = imageItem.url;
|
|
25370
|
-
const toExt = this.loaders[ext].toExt;
|
|
25371
|
-
let name = imageItem.name || imageItem.url;
|
|
25372
|
-
name = name.split("/").pop();
|
|
25373
|
-
if (typeof imageItem.url === "string") {
|
|
25374
|
-
const url = imageItem.url;
|
|
25375
|
-
try {
|
|
25376
|
-
const response = await fetch(url);
|
|
25377
|
-
if (!response.ok) {
|
|
25378
|
-
throw new Error(`Failed to load file: ${response.statusText}`);
|
|
25379
|
-
}
|
|
25380
|
-
itemToLoad = await response.arrayBuffer();
|
|
25381
|
-
} catch (error) {
|
|
25382
|
-
throw new Error(`Failed to load url ${url}: ${error}`);
|
|
25383
|
-
}
|
|
25384
|
-
}
|
|
25385
|
-
const buffer = await this.loaders[ext].loader(itemToLoad);
|
|
25386
|
-
imageItem.url = buffer;
|
|
25387
|
-
imageItem.name = `${name}.${toExt}`;
|
|
25388
|
-
}
|
|
25389
25528
|
const imageOptions = {
|
|
25390
25529
|
url: imageItem.url,
|
|
25391
25530
|
headers: imageItem.headers,
|
|
@@ -25525,40 +25664,6 @@ var Niivue = class _Niivue {
|
|
|
25525
25664
|
this.addVolume(volume);
|
|
25526
25665
|
});
|
|
25527
25666
|
}
|
|
25528
|
-
/* useLoader
|
|
25529
|
-
|
|
25530
|
-
registers an external file loader for niivue to use when reading files.
|
|
25531
|
-
|
|
25532
|
-
the loader must return an array buffer of the file contents for niivue
|
|
25533
|
-
to parse and the extension of the file so niivue can infer the file type to load.
|
|
25534
|
-
|
|
25535
|
-
example:
|
|
25536
|
-
|
|
25537
|
-
const myCustomLoader = (File) => {
|
|
25538
|
-
// ... do parsing here ...
|
|
25539
|
-
return {
|
|
25540
|
-
arrayBuffer: ArrayBuffer,
|
|
25541
|
-
fileExt: 'iwi.cbor',
|
|
25542
|
-
positions: Float32Array | [], // allows for mesh loading to mz3
|
|
25543
|
-
indices: Uint32Array | [], // allows for mesh loading to mz3
|
|
25544
|
-
}
|
|
25545
|
-
|
|
25546
|
-
nv.useLoader({
|
|
25547
|
-
loader: myCustomLoader,
|
|
25548
|
-
fileExt: 'iwi.cbor',
|
|
25549
|
-
toExt: 'nii'
|
|
25550
|
-
})
|
|
25551
|
-
|
|
25552
|
-
*/
|
|
25553
|
-
useLoader(loader, fileExt, toExt) {
|
|
25554
|
-
this.loaders = {
|
|
25555
|
-
...this.loaders,
|
|
25556
|
-
[fileExt.toUpperCase()]: {
|
|
25557
|
-
loader,
|
|
25558
|
-
toExt
|
|
25559
|
-
}
|
|
25560
|
-
};
|
|
25561
|
-
}
|
|
25562
25667
|
// not included in public docs
|
|
25563
25668
|
dropListener(e) {
|
|
25564
25669
|
e.stopPropagation();
|
|
@@ -26783,6 +26888,10 @@ var Niivue = class _Niivue {
|
|
|
26783
26888
|
}
|
|
26784
26889
|
// not included in public docs
|
|
26785
26890
|
sliceScroll2D(posChange, x, y, isDelta = true) {
|
|
26891
|
+
if (this.opts.scrollRequiresFocus && this.canvas !== document.activeElement) {
|
|
26892
|
+
log.warn("Canvas element does not have focus. Scroll events will not be processed.");
|
|
26893
|
+
return;
|
|
26894
|
+
}
|
|
26786
26895
|
if (this.inGraphTile(x, y)) {
|
|
26787
26896
|
let vol = this.volumes[0].frame4D;
|
|
26788
26897
|
if (posChange > 0) {
|
|
@@ -27230,37 +27339,6 @@ var Niivue = class _Niivue {
|
|
|
27230
27339
|
this.document.meshes = this.meshes;
|
|
27231
27340
|
return this.document.download(fileName, compress2);
|
|
27232
27341
|
}
|
|
27233
|
-
// generic loadImages that wraps loadVolumes and loadMeshes
|
|
27234
|
-
async loadImages(images) {
|
|
27235
|
-
const volumes = [];
|
|
27236
|
-
const meshes = [];
|
|
27237
|
-
for (const image of images) {
|
|
27238
|
-
if ("url" in image) {
|
|
27239
|
-
const ext = this.getFileExt(image.url);
|
|
27240
|
-
if (this.loaders[ext]) {
|
|
27241
|
-
const toExt = this.loaders[ext].toExt.toUpperCase();
|
|
27242
|
-
if (MESH_EXTENSIONS.includes(toExt)) {
|
|
27243
|
-
meshes.push(image);
|
|
27244
|
-
} else {
|
|
27245
|
-
volumes.push(image);
|
|
27246
|
-
}
|
|
27247
|
-
continue;
|
|
27248
|
-
}
|
|
27249
|
-
if (MESH_EXTENSIONS.includes(ext.toUpperCase())) {
|
|
27250
|
-
meshes.push(image);
|
|
27251
|
-
} else {
|
|
27252
|
-
volumes.push(image);
|
|
27253
|
-
}
|
|
27254
|
-
}
|
|
27255
|
-
}
|
|
27256
|
-
if (volumes.length > 0) {
|
|
27257
|
-
await this.loadVolumes(volumes);
|
|
27258
|
-
}
|
|
27259
|
-
if (meshes.length > 0) {
|
|
27260
|
-
await this.loadMeshes(meshes);
|
|
27261
|
-
}
|
|
27262
|
-
return this;
|
|
27263
|
-
}
|
|
27264
27342
|
/**
|
|
27265
27343
|
* load an array of volume objects
|
|
27266
27344
|
* @param volumeList - the array of objects to load. each object must have a resolvable "url" property at a minimum
|
|
@@ -27280,7 +27358,27 @@ var Niivue = class _Niivue {
|
|
|
27280
27358
|
this.volumes = [];
|
|
27281
27359
|
this.gl.clearColor(0, 0, 0, 1);
|
|
27282
27360
|
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
|
27283
|
-
|
|
27361
|
+
if (volumeList.length > 1) {
|
|
27362
|
+
await this.addVolumesFromUrl(volumeList);
|
|
27363
|
+
return this;
|
|
27364
|
+
}
|
|
27365
|
+
const imageOptions = {
|
|
27366
|
+
url: volumeList[0].url,
|
|
27367
|
+
headers: volumeList[0].headers,
|
|
27368
|
+
name: volumeList[0].name,
|
|
27369
|
+
colormap: volumeList[0].colormap ? volumeList[0].colormap : volumeList[0].colorMap,
|
|
27370
|
+
colormapNegative: volumeList[0].colormapNegative ? volumeList[0].colormapNegative : volumeList[0].colorMapNegative,
|
|
27371
|
+
opacity: volumeList[0].opacity,
|
|
27372
|
+
urlImgData: volumeList[0].urlImgData,
|
|
27373
|
+
cal_min: volumeList[0].cal_min,
|
|
27374
|
+
cal_max: volumeList[0].cal_max,
|
|
27375
|
+
trustCalMinMax: this.opts.trustCalMinMax,
|
|
27376
|
+
isManifest: volumeList[0].isManifest,
|
|
27377
|
+
frame4D: volumeList[0].frame4D,
|
|
27378
|
+
limitFrames4D: volumeList[0].limitFrames4D || this.opts.limitFrames4D,
|
|
27379
|
+
colorbarVisible: volumeList[0].colorbarVisible
|
|
27380
|
+
};
|
|
27381
|
+
await this.addVolumeFromUrl(imageOptions);
|
|
27284
27382
|
return this;
|
|
27285
27383
|
}
|
|
27286
27384
|
/**
|
|
@@ -27310,29 +27408,7 @@ var Niivue = class _Niivue {
|
|
|
27310
27408
|
*/
|
|
27311
27409
|
async addMeshesFromUrl(meshOptions) {
|
|
27312
27410
|
const promises = meshOptions.map(async (meshItem) => {
|
|
27313
|
-
const ext = this.getFileExt(meshItem.
|
|
27314
|
-
if (this.loaders[ext]) {
|
|
27315
|
-
let itemToLoad = meshItem.url;
|
|
27316
|
-
const toExt = this.loaders[ext].toExt;
|
|
27317
|
-
let name = meshItem.name || meshItem.url;
|
|
27318
|
-
name = name.split("/").pop();
|
|
27319
|
-
if (typeof meshItem.url === "string") {
|
|
27320
|
-
const url = meshItem.url;
|
|
27321
|
-
try {
|
|
27322
|
-
const response = await fetch(url);
|
|
27323
|
-
if (!response.ok) {
|
|
27324
|
-
throw new Error(`Failed to load file: ${response.statusText}`);
|
|
27325
|
-
}
|
|
27326
|
-
itemToLoad = await response.arrayBuffer();
|
|
27327
|
-
} catch (error) {
|
|
27328
|
-
throw new Error(`Failed to load url ${url}: ${error}`);
|
|
27329
|
-
}
|
|
27330
|
-
}
|
|
27331
|
-
const { positions, indices } = await this.loaders[ext].loader(itemToLoad);
|
|
27332
|
-
meshItem.name = `${name}.${toExt}`;
|
|
27333
|
-
const mz3 = NVMeshUtilities.createMZ3(positions, indices, false);
|
|
27334
|
-
meshItem.buffer = mz3;
|
|
27335
|
-
}
|
|
27411
|
+
const ext = this.getFileExt(meshItem.url);
|
|
27336
27412
|
if (ext === "JCON" || ext === "JSON") {
|
|
27337
27413
|
const response = await fetch(meshItem.url, {});
|
|
27338
27414
|
const json = await response.json();
|
|
@@ -27373,7 +27449,13 @@ var Niivue = class _Niivue {
|
|
|
27373
27449
|
this.meshes = [];
|
|
27374
27450
|
this.gl.clearColor(0, 0, 0, 1);
|
|
27375
27451
|
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
|
27376
|
-
|
|
27452
|
+
if (meshList.length > 1) {
|
|
27453
|
+
await this.addMeshesFromUrl(meshList);
|
|
27454
|
+
this.updateGLVolume();
|
|
27455
|
+
this.drawScene();
|
|
27456
|
+
return this;
|
|
27457
|
+
}
|
|
27458
|
+
await this.addMeshFromUrl(meshList[0]);
|
|
27377
27459
|
this.updateGLVolume();
|
|
27378
27460
|
this.drawScene();
|
|
27379
27461
|
return this;
|
|
@@ -27474,6 +27556,7 @@ var Niivue = class _Niivue {
|
|
|
27474
27556
|
}
|
|
27475
27557
|
const vx = this.back.dims[1] * this.back.dims[2] * this.back.dims[3];
|
|
27476
27558
|
this.drawBitmap = new Uint8Array(vx);
|
|
27559
|
+
this.clickToSegmentGrowingBitmap = new Uint8Array(vx);
|
|
27477
27560
|
this.drawClearAllUndoBitmaps();
|
|
27478
27561
|
this.drawAddUndoBitmap();
|
|
27479
27562
|
this.drawTexture = this.r8Tex(this.drawTexture, TEXTURE7_DRAW, this.back.dims, true);
|
|
@@ -27647,6 +27730,23 @@ var Niivue = class _Niivue {
|
|
|
27647
27730
|
y = Math.min(Math.max(y, 0), dy - 1);
|
|
27648
27731
|
z = Math.min(Math.max(z, 0), dz - 1);
|
|
27649
27732
|
this.drawBitmap[x + y * dx + z * dx * dy] = penValue;
|
|
27733
|
+
const isAx = this.drawPenAxCorSag === 0;
|
|
27734
|
+
const isCor = this.drawPenAxCorSag === 1;
|
|
27735
|
+
const isSag = this.drawPenAxCorSag === 2;
|
|
27736
|
+
if (this.opts.penSize > 1) {
|
|
27737
|
+
const halfPenSize = Math.floor(this.opts.penSize / 2);
|
|
27738
|
+
for (let i = -halfPenSize; i <= halfPenSize; i++) {
|
|
27739
|
+
for (let j = -halfPenSize; j <= halfPenSize; j++) {
|
|
27740
|
+
if (isAx) {
|
|
27741
|
+
this.drawBitmap[x + i + (y + j) * dx + z * dx * dy] = penValue;
|
|
27742
|
+
} else if (isCor) {
|
|
27743
|
+
this.drawBitmap[x + i + y * dx + (z + j) * dx * dy] = penValue;
|
|
27744
|
+
} else if (isSag) {
|
|
27745
|
+
this.drawBitmap[x + (y + j) * dx + (z + i) * dx * dy] = penValue;
|
|
27746
|
+
}
|
|
27747
|
+
}
|
|
27748
|
+
}
|
|
27749
|
+
}
|
|
27650
27750
|
}
|
|
27651
27751
|
// not included in public docs
|
|
27652
27752
|
// create line between to voxels in drawing
|
|
@@ -27728,6 +27828,98 @@ var Niivue = class _Niivue {
|
|
|
27728
27828
|
}
|
|
27729
27829
|
}
|
|
27730
27830
|
}
|
|
27831
|
+
/**
|
|
27832
|
+
* Performs a 1-voxel binary dilation on a connected cluster within the drawing mask using the drawFloodFillCore function.
|
|
27833
|
+
*
|
|
27834
|
+
* @param seedXYZ - voxel index of the seed voxel in the mask array.
|
|
27835
|
+
* @param neighbors - Number of neighbors to consider for connectivity and dilation (6, 18, or 26).
|
|
27836
|
+
*/
|
|
27837
|
+
drawingBinaryDilationWithSeed(seedXYZ, neighbors = 6) {
|
|
27838
|
+
try {
|
|
27839
|
+
let xyz2vx = function(pt) {
|
|
27840
|
+
return pt[0] + pt[1] * nx + pt[2] * nxy;
|
|
27841
|
+
};
|
|
27842
|
+
const mask = this.drawBitmap;
|
|
27843
|
+
const xDim = this.back.dims[1];
|
|
27844
|
+
const yDim = this.back.dims[2];
|
|
27845
|
+
const zDim = this.back.dims[3];
|
|
27846
|
+
const nx = xDim;
|
|
27847
|
+
const nxy = xDim * yDim;
|
|
27848
|
+
const totalVoxels = nxy * zDim;
|
|
27849
|
+
const seedIndex = xyz2vx(seedXYZ);
|
|
27850
|
+
if (seedIndex < 0 || seedIndex >= totalVoxels) {
|
|
27851
|
+
throw new Error("Seed index is out of bounds.");
|
|
27852
|
+
}
|
|
27853
|
+
const seedValue = mask[seedIndex];
|
|
27854
|
+
if (seedValue === 0) {
|
|
27855
|
+
throw new Error("Seed voxel is not part of a filled cluster.");
|
|
27856
|
+
}
|
|
27857
|
+
const img = mask.slice();
|
|
27858
|
+
for (let i = 0; i < totalVoxels; i++) {
|
|
27859
|
+
img[i] = img[i] === seedValue ? 1 : 0;
|
|
27860
|
+
}
|
|
27861
|
+
this.drawFloodFillCore(img, seedIndex, neighbors);
|
|
27862
|
+
const outputMask = mask.slice();
|
|
27863
|
+
const neighborOffsets = [];
|
|
27864
|
+
const offsets6 = [-nxy, nxy, -xDim, xDim, -1, 1];
|
|
27865
|
+
neighborOffsets.push(...offsets6);
|
|
27866
|
+
if (neighbors > 6) {
|
|
27867
|
+
neighborOffsets.push(
|
|
27868
|
+
-xDim - 1,
|
|
27869
|
+
-xDim + 1,
|
|
27870
|
+
xDim - 1,
|
|
27871
|
+
xDim + 1,
|
|
27872
|
+
-nxy - xDim,
|
|
27873
|
+
-nxy + xDim,
|
|
27874
|
+
-nxy - 1,
|
|
27875
|
+
-nxy + 1,
|
|
27876
|
+
nxy - xDim,
|
|
27877
|
+
nxy + xDim,
|
|
27878
|
+
nxy - 1,
|
|
27879
|
+
nxy + 1
|
|
27880
|
+
);
|
|
27881
|
+
}
|
|
27882
|
+
if (neighbors > 18) {
|
|
27883
|
+
neighborOffsets.push(
|
|
27884
|
+
-nxy - xDim - 1,
|
|
27885
|
+
-nxy - xDim + 1,
|
|
27886
|
+
-nxy + xDim - 1,
|
|
27887
|
+
-nxy + xDim + 1,
|
|
27888
|
+
nxy - xDim - 1,
|
|
27889
|
+
nxy - xDim + 1,
|
|
27890
|
+
nxy + xDim - 1,
|
|
27891
|
+
nxy + xDim + 1
|
|
27892
|
+
);
|
|
27893
|
+
}
|
|
27894
|
+
for (let idx = 0; idx < totalVoxels; idx++) {
|
|
27895
|
+
if (img[idx] === 2) {
|
|
27896
|
+
const x = idx % xDim;
|
|
27897
|
+
const y = Math.floor(idx % nxy / xDim);
|
|
27898
|
+
const z = Math.floor(idx / nxy);
|
|
27899
|
+
for (const offset of neighborOffsets) {
|
|
27900
|
+
const neighborIdx = idx + offset;
|
|
27901
|
+
if (neighborIdx < 0 || neighborIdx >= totalVoxels) {
|
|
27902
|
+
continue;
|
|
27903
|
+
}
|
|
27904
|
+
const nx2 = neighborIdx % xDim;
|
|
27905
|
+
const ny = Math.floor(neighborIdx % nxy / xDim);
|
|
27906
|
+
const nz = Math.floor(neighborIdx / nxy);
|
|
27907
|
+
if (Math.abs(nx2 - x) > 1 || Math.abs(ny - y) > 1 || Math.abs(nz - z) > 1) {
|
|
27908
|
+
continue;
|
|
27909
|
+
}
|
|
27910
|
+
if (mask[neighborIdx] === 0) {
|
|
27911
|
+
outputMask[neighborIdx] = seedValue;
|
|
27912
|
+
}
|
|
27913
|
+
}
|
|
27914
|
+
}
|
|
27915
|
+
}
|
|
27916
|
+
this.drawBitmap = outputMask;
|
|
27917
|
+
this.drawAddUndoBitmap();
|
|
27918
|
+
this.refreshDrawing(true);
|
|
27919
|
+
} catch (error) {
|
|
27920
|
+
log.error("Error in drawingBinaryDilationWithSeed:", error);
|
|
27921
|
+
}
|
|
27922
|
+
}
|
|
27731
27923
|
// a voxel can be defined as having 6, 18 or 26 neighbors:
|
|
27732
27924
|
// 6: neighbors share faces (distance=1)
|
|
27733
27925
|
// 18: neighbors share faces (distance=1) or edges (1.4)
|
|
@@ -27809,7 +28001,7 @@ var Niivue = class _Niivue {
|
|
|
27809
28001
|
}
|
|
27810
28002
|
// not included in public docs
|
|
27811
28003
|
// set all connected voxels in drawing to new color
|
|
27812
|
-
drawFloodFill(seedXYZ, newColor = 0, growSelectedCluster = 0, forceMin = NaN, forceMax = NaN, neighbors = 6) {
|
|
28004
|
+
drawFloodFill(seedXYZ, newColor = 0, growSelectedCluster = 0, forceMin = NaN, forceMax = NaN, neighbors = 6, maxDistanceMM = Number.POSITIVE_INFINITY, is2D = false) {
|
|
27813
28005
|
if (!this.drawBitmap) {
|
|
27814
28006
|
throw new Error("drawBitmap undefined");
|
|
27815
28007
|
}
|
|
@@ -27827,13 +28019,46 @@ var Niivue = class _Niivue {
|
|
|
27827
28019
|
const nx = dims[0];
|
|
27828
28020
|
const nxy = nx * dims[1];
|
|
27829
28021
|
const nxyz = nxy * dims[2];
|
|
27830
|
-
|
|
28022
|
+
let img = this.drawBitmap.slice();
|
|
28023
|
+
let drawBitmap = this.drawBitmap;
|
|
28024
|
+
if (this.clickToSegmentIsGrowing) {
|
|
28025
|
+
img = this.clickToSegmentGrowingBitmap.slice();
|
|
28026
|
+
drawBitmap = this.clickToSegmentGrowingBitmap;
|
|
28027
|
+
}
|
|
27831
28028
|
if (img.length !== nxy * dims[2]) {
|
|
27832
28029
|
return;
|
|
27833
28030
|
}
|
|
28031
|
+
let constrainXYZ = -1;
|
|
28032
|
+
if (is2D && this.drawPenAxCorSag === 0 /* AXIAL */) {
|
|
28033
|
+
constrainXYZ = 2;
|
|
28034
|
+
} else if (is2D && this.drawPenAxCorSag === 1 /* CORONAL */) {
|
|
28035
|
+
constrainXYZ = 1;
|
|
28036
|
+
} else if (is2D && this.drawPenAxCorSag === 2 /* SAGITTAL */) {
|
|
28037
|
+
constrainXYZ = 0;
|
|
28038
|
+
}
|
|
28039
|
+
function vx2xyz(vx) {
|
|
28040
|
+
const Z = Math.floor(vx / nxy);
|
|
28041
|
+
const Y = Math.floor((vx - Z * nxy) / nx);
|
|
28042
|
+
const X = Math.floor(vx % nx);
|
|
28043
|
+
return [X, Y, Z];
|
|
28044
|
+
}
|
|
27834
28045
|
function xyz2vx(pt) {
|
|
27835
28046
|
return pt[0] + pt[1] * nx + pt[2] * nxy;
|
|
27836
28047
|
}
|
|
28048
|
+
const vx2mm = (xyz) => {
|
|
28049
|
+
return this.vox2mm(xyz, this.back.matRAS);
|
|
28050
|
+
};
|
|
28051
|
+
const seedMM = vx2mm(seedXYZ);
|
|
28052
|
+
const maxDistanceMM2 = maxDistanceMM ** 2;
|
|
28053
|
+
function isWithinDistance(vx) {
|
|
28054
|
+
const xzyVox = vx2xyz(vx);
|
|
28055
|
+
if (constrainXYZ >= 0 && xzyVox[constrainXYZ] !== seedXYZ[constrainXYZ]) {
|
|
28056
|
+
return false;
|
|
28057
|
+
}
|
|
28058
|
+
const xyzMM = vx2mm(xzyVox);
|
|
28059
|
+
const dist2 = (xyzMM[0] - seedMM[0]) ** 2 + (xyzMM[1] - seedMM[1]) ** 2 + (xyzMM[2] - seedMM[2]) ** 2;
|
|
28060
|
+
return dist2 <= maxDistanceMM2;
|
|
28061
|
+
}
|
|
27837
28062
|
const seedVx = xyz2vx(seedXYZ);
|
|
27838
28063
|
const seedColor = img[seedVx];
|
|
27839
28064
|
if (seedColor === newColor) {
|
|
@@ -27846,7 +28071,10 @@ var Niivue = class _Niivue {
|
|
|
27846
28071
|
}
|
|
27847
28072
|
for (let i = 1; i < nxyz; i++) {
|
|
27848
28073
|
img[i] = 0;
|
|
27849
|
-
if (
|
|
28074
|
+
if (drawBitmap[i] === seedColor) {
|
|
28075
|
+
if (!isWithinDistance(i)) {
|
|
28076
|
+
continue;
|
|
28077
|
+
}
|
|
27850
28078
|
img[i] = 1;
|
|
27851
28079
|
}
|
|
27852
28080
|
}
|
|
@@ -27876,6 +28104,9 @@ var Niivue = class _Niivue {
|
|
|
27876
28104
|
for (let i = 1; i < nxyz; i++) {
|
|
27877
28105
|
img[i] = 0;
|
|
27878
28106
|
if (backImg[i] >= mn && backImg[i] <= mx) {
|
|
28107
|
+
if (!isWithinDistance(i)) {
|
|
28108
|
+
continue;
|
|
28109
|
+
}
|
|
27879
28110
|
img[i] = 1;
|
|
27880
28111
|
}
|
|
27881
28112
|
}
|
|
@@ -27884,11 +28115,19 @@ var Niivue = class _Niivue {
|
|
|
27884
28115
|
}
|
|
27885
28116
|
for (let i = 1; i < nxyz; i++) {
|
|
27886
28117
|
if (img[i] === 2) {
|
|
27887
|
-
|
|
28118
|
+
drawBitmap[i] = newColor;
|
|
27888
28119
|
}
|
|
27889
28120
|
}
|
|
27890
|
-
this.
|
|
27891
|
-
|
|
28121
|
+
if (!this.clickToSegmentIsGrowing) {
|
|
28122
|
+
this.drawBitmap = drawBitmap.slice();
|
|
28123
|
+
if (!this.opts.clickToSegment) {
|
|
28124
|
+
this.drawAddUndoBitmap();
|
|
28125
|
+
}
|
|
28126
|
+
this.refreshDrawing(true, this.clickToSegmentIsGrowing);
|
|
28127
|
+
} else {
|
|
28128
|
+
this.clickToSegmentGrowingBitmap = drawBitmap;
|
|
28129
|
+
this.refreshDrawing(true, this.clickToSegmentIsGrowing);
|
|
28130
|
+
}
|
|
27892
28131
|
}
|
|
27893
28132
|
// not included in public docs
|
|
27894
28133
|
// given series of line segments, connect first and last
|
|
@@ -28048,6 +28287,7 @@ var Niivue = class _Niivue {
|
|
|
28048
28287
|
this.drawClearAllUndoBitmaps();
|
|
28049
28288
|
this.rgbaTex(this.drawTexture, TEXTURE7_DRAW, [2, 2, 2, 2], true);
|
|
28050
28289
|
this.drawBitmap = null;
|
|
28290
|
+
this.clickToSegmentGrowingBitmap = null;
|
|
28051
28291
|
this.drawScene();
|
|
28052
28292
|
}
|
|
28053
28293
|
/**
|
|
@@ -28056,7 +28296,7 @@ var Niivue = class _Niivue {
|
|
|
28056
28296
|
* @example niivue.refreshDrawing();
|
|
28057
28297
|
* @see {@link https://niivue.github.io/niivue/features/cactus.html | live demo usage}
|
|
28058
28298
|
*/
|
|
28059
|
-
refreshDrawing(isForceRedraw = true) {
|
|
28299
|
+
refreshDrawing(isForceRedraw = true, useClickToSegmentBitmap = false) {
|
|
28060
28300
|
if (!this.back?.dims) {
|
|
28061
28301
|
throw new Error("back.dims undefined");
|
|
28062
28302
|
}
|
|
@@ -28085,7 +28325,7 @@ var Niivue = class _Niivue {
|
|
|
28085
28325
|
dims[3],
|
|
28086
28326
|
this.gl.RED,
|
|
28087
28327
|
this.gl.UNSIGNED_BYTE,
|
|
28088
|
-
this.drawBitmap
|
|
28328
|
+
useClickToSegmentBitmap ? this.clickToSegmentGrowingBitmap : this.drawBitmap
|
|
28089
28329
|
);
|
|
28090
28330
|
if (isForceRedraw) {
|
|
28091
28331
|
this.drawScene();
|
|
@@ -28497,6 +28737,8 @@ var Niivue = class _Niivue {
|
|
|
28497
28737
|
gl.bindVertexArray(this.unusedVAO);
|
|
28498
28738
|
this.rectShader = new Shader(gl, vertRectShader, fragRectShader);
|
|
28499
28739
|
this.rectShader.use(gl);
|
|
28740
|
+
this.rectOutlineShader = new Shader(gl, vertRectShader, fragRectOutlineShader);
|
|
28741
|
+
this.rectOutlineShader.use(gl);
|
|
28500
28742
|
this.lineShader = new Shader(gl, vertLineShader, fragRectShader);
|
|
28501
28743
|
this.lineShader.use(gl);
|
|
28502
28744
|
this.line3DShader = new Shader(gl, vertLine3DShader, fragRectShader);
|
|
@@ -28649,14 +28891,37 @@ var Niivue = class _Niivue {
|
|
|
28649
28891
|
}
|
|
28650
28892
|
/**
|
|
28651
28893
|
* basic statistics for selected voxel-based image
|
|
28652
|
-
* @param
|
|
28653
|
-
*
|
|
28654
|
-
*
|
|
28655
|
-
*
|
|
28894
|
+
* @param options - an object containing the following properties:
|
|
28895
|
+
* - layer: selects image to describe
|
|
28896
|
+
* - masks: optional binary images to filter voxels
|
|
28897
|
+
* - drawingIsMask: a boolean indicating if the drawing is used as a mask
|
|
28898
|
+
* - roiIsMask: a boolean indicating if the ROI is used as a mask
|
|
28899
|
+
* - startVox: the starting voxel coordinates
|
|
28900
|
+
* - endVox: the ending voxel coordinates
|
|
28901
|
+
* @returns numeric values to describe image or regions of images
|
|
28902
|
+
* @example
|
|
28903
|
+
* niivue.getDescriptives({
|
|
28904
|
+
* layer: 0,
|
|
28905
|
+
* masks: [],
|
|
28906
|
+
* drawingIsMask: true, // drawingIsMask and roiIsMask are mutually exclusive
|
|
28907
|
+
* roiIsMask: false,
|
|
28908
|
+
* startVox: [10, 20, 30], // ignored if roiIsMask is false
|
|
28909
|
+
* endVox: [40, 50, 60] // ignored if roiIsMask is false
|
|
28910
|
+
* });
|
|
28656
28911
|
* @see {@link https://niivue.github.io/niivue/features/draw2.html | live demo usage}
|
|
28657
28912
|
*/
|
|
28658
|
-
getDescriptives(
|
|
28913
|
+
getDescriptives(options) {
|
|
28914
|
+
const {
|
|
28915
|
+
layer = 0,
|
|
28916
|
+
masks = [],
|
|
28917
|
+
drawingIsMask = false,
|
|
28918
|
+
roiIsMask = false,
|
|
28919
|
+
startVox = [0, 0, 0],
|
|
28920
|
+
endVox = [0, 0, 0]
|
|
28921
|
+
} = options;
|
|
28922
|
+
let area = null;
|
|
28659
28923
|
const hdr = this.volumes[layer].hdr;
|
|
28924
|
+
const pixDimsRAS = this.volumes[layer].pixDimsRAS;
|
|
28660
28925
|
let slope = hdr.scl_slope;
|
|
28661
28926
|
if (isNaN(slope)) {
|
|
28662
28927
|
slope = 1;
|
|
@@ -28694,6 +28959,65 @@ var Niivue = class _Niivue {
|
|
|
28694
28959
|
mask[i] = 0;
|
|
28695
28960
|
}
|
|
28696
28961
|
}
|
|
28962
|
+
} else if (masks.length < 1 && roiIsMask) {
|
|
28963
|
+
mask.fill(0);
|
|
28964
|
+
console.log("startVox", startVox);
|
|
28965
|
+
console.log("endVox", endVox);
|
|
28966
|
+
let constantDim = -1;
|
|
28967
|
+
if (startVox[0] === endVox[0]) {
|
|
28968
|
+
constantDim = 0;
|
|
28969
|
+
} else if (startVox[1] === endVox[1]) {
|
|
28970
|
+
constantDim = 1;
|
|
28971
|
+
} else if (startVox[2] === endVox[2]) {
|
|
28972
|
+
constantDim = 2;
|
|
28973
|
+
} else {
|
|
28974
|
+
console.error("Error: No constant dimension found.");
|
|
28975
|
+
return;
|
|
28976
|
+
}
|
|
28977
|
+
const dims = [0, 1, 2];
|
|
28978
|
+
const varDims = dims.filter((dim) => dim !== constantDim);
|
|
28979
|
+
const centerVox = [];
|
|
28980
|
+
centerVox[constantDim] = startVox[constantDim];
|
|
28981
|
+
centerVox[varDims[0]] = (startVox[varDims[0]] + endVox[varDims[0]]) / 2;
|
|
28982
|
+
centerVox[varDims[1]] = (startVox[varDims[1]] + endVox[varDims[1]]) / 2;
|
|
28983
|
+
const radiusX = Math.abs(endVox[varDims[0]] - startVox[varDims[0]]) / 2;
|
|
28984
|
+
const radiusY = Math.abs(endVox[varDims[1]] - startVox[varDims[1]]) / 2;
|
|
28985
|
+
const xdim = hdr.dims[1];
|
|
28986
|
+
const ydim = hdr.dims[2];
|
|
28987
|
+
const minVarDim0 = Math.max(0, Math.floor(centerVox[varDims[0]] - radiusX));
|
|
28988
|
+
const maxVarDim0 = Math.min(hdr.dims[varDims[0] + 1] - 1, Math.ceil(centerVox[varDims[0]] + radiusX));
|
|
28989
|
+
const minVarDim1 = Math.max(0, Math.floor(centerVox[varDims[1]] - radiusY));
|
|
28990
|
+
const maxVarDim1 = Math.min(hdr.dims[varDims[1] + 1] - 1, Math.ceil(centerVox[varDims[1]] + radiusY));
|
|
28991
|
+
const constDimVal = centerVox[constantDim];
|
|
28992
|
+
if (constDimVal < 0 || constDimVal >= hdr.dims[constantDim + 1]) {
|
|
28993
|
+
console.error("Error: Constant dimension value is out of bounds.");
|
|
28994
|
+
return;
|
|
28995
|
+
}
|
|
28996
|
+
for (let i = minVarDim0; i <= maxVarDim0; i++) {
|
|
28997
|
+
for (let j = minVarDim1; j <= maxVarDim1; j++) {
|
|
28998
|
+
const voxel = [];
|
|
28999
|
+
voxel[constantDim] = constDimVal;
|
|
29000
|
+
voxel[varDims[0]] = i;
|
|
29001
|
+
voxel[varDims[1]] = j;
|
|
29002
|
+
const di = (voxel[varDims[0]] - centerVox[varDims[0]]) / radiusX;
|
|
29003
|
+
const dj = (voxel[varDims[1]] - centerVox[varDims[1]]) / radiusY;
|
|
29004
|
+
const distSq = di * di + dj * dj;
|
|
29005
|
+
if (distSq <= 1) {
|
|
29006
|
+
const x = voxel[0];
|
|
29007
|
+
const y = voxel[1];
|
|
29008
|
+
const z = voxel[2];
|
|
29009
|
+
const index = z * xdim * ydim + y * xdim + x;
|
|
29010
|
+
mask[index] = 1;
|
|
29011
|
+
}
|
|
29012
|
+
}
|
|
29013
|
+
}
|
|
29014
|
+
const voxelArea = pixDimsRAS[varDims[0] + 1] * pixDimsRAS[varDims[1] + 1];
|
|
29015
|
+
const numMaskedVoxels = mask.reduce((count, value) => count + (value === 1 ? 1 : 0), 0);
|
|
29016
|
+
area = numMaskedVoxels * voxelArea;
|
|
29017
|
+
const radiusX_mm = radiusX * pixDimsRAS[varDims[0] + 1];
|
|
29018
|
+
const radiusY_mm = radiusY * pixDimsRAS[varDims[1] + 1];
|
|
29019
|
+
const areaEllipse = Math.PI * radiusX_mm * radiusY_mm;
|
|
29020
|
+
area = areaEllipse;
|
|
28697
29021
|
}
|
|
28698
29022
|
let k = 0;
|
|
28699
29023
|
let M = 0;
|
|
@@ -28747,7 +29071,8 @@ var Niivue = class _Niivue {
|
|
|
28747
29071
|
cal_min: this.volumes[layer].cal_min,
|
|
28748
29072
|
cal_max: this.volumes[layer].cal_max,
|
|
28749
29073
|
robust_min: this.volumes[layer].robust_min,
|
|
28750
|
-
robust_max: this.volumes[layer].robust_max
|
|
29074
|
+
robust_max: this.volumes[layer].robust_max,
|
|
29075
|
+
area
|
|
28751
29076
|
};
|
|
28752
29077
|
}
|
|
28753
29078
|
// not included in public docs
|
|
@@ -30225,6 +30550,29 @@ var Niivue = class _Niivue {
|
|
|
30225
30550
|
const pos = [(x - this.graph.LTWH[0]) / this.graph.LTWH[2], (y - this.graph.LTWH[1]) / this.graph.LTWH[3]];
|
|
30226
30551
|
return pos[0] > 0 && pos[1] > 0 && pos[0] <= 1 && pos[1] <= 1;
|
|
30227
30552
|
}
|
|
30553
|
+
// update drawBitmap if it differs from clickTosegmentBitmap
|
|
30554
|
+
updateBitmapFromClickToSegment() {
|
|
30555
|
+
if (this.clickToSegmentGrowingBitmap === null) {
|
|
30556
|
+
return;
|
|
30557
|
+
}
|
|
30558
|
+
if (this.drawBitmap === null) {
|
|
30559
|
+
return;
|
|
30560
|
+
}
|
|
30561
|
+
if (this.clickToSegmentGrowingBitmap.length !== this.drawBitmap.length) {
|
|
30562
|
+
return;
|
|
30563
|
+
}
|
|
30564
|
+
const nvx = this.drawBitmap.length;
|
|
30565
|
+
for (let i = 0; i < nvx; i++) {
|
|
30566
|
+
this.drawBitmap[i] = this.clickToSegmentGrowingBitmap[i];
|
|
30567
|
+
}
|
|
30568
|
+
}
|
|
30569
|
+
sumBitmap(img) {
|
|
30570
|
+
let sum = 0;
|
|
30571
|
+
for (let i = 0; i < img.length; i++) {
|
|
30572
|
+
sum += img[i];
|
|
30573
|
+
}
|
|
30574
|
+
return sum;
|
|
30575
|
+
}
|
|
30228
30576
|
// not included in public docs
|
|
30229
30577
|
// handle mouse click event on canvas
|
|
30230
30578
|
mouseClick(x, y, posChange = 0, isDelta = true) {
|
|
@@ -30274,7 +30622,7 @@ var Niivue = class _Niivue {
|
|
|
30274
30622
|
if (axCorSag > 2 /* SAGITTAL */) {
|
|
30275
30623
|
continue;
|
|
30276
30624
|
}
|
|
30277
|
-
|
|
30625
|
+
let texFrac = this.screenXY2TextureFrac(x, y, i, false);
|
|
30278
30626
|
if (texFrac[0] < 0) {
|
|
30279
30627
|
continue;
|
|
30280
30628
|
}
|
|
@@ -30301,31 +30649,75 @@ var Niivue = class _Niivue {
|
|
|
30301
30649
|
this.scene.crosshairPos = vec311.clone(texFrac);
|
|
30302
30650
|
}
|
|
30303
30651
|
if (this.opts.drawingEnabled) {
|
|
30304
|
-
|
|
30305
|
-
const ptMM = this.frac2mm(this.scene.crosshairPos);
|
|
30652
|
+
let pt = this.frac2vox(this.scene.crosshairPos);
|
|
30306
30653
|
if (this.opts.clickToSegment) {
|
|
30307
|
-
|
|
30308
|
-
|
|
30654
|
+
texFrac = this.screenXY2TextureFrac(this.clickToSegmentXY[0], this.clickToSegmentXY[1], i, false);
|
|
30655
|
+
pt = this.frac2vox(texFrac);
|
|
30656
|
+
let diff = 0;
|
|
30657
|
+
let threshold = this.opts.clickToSegmentPercent + diff;
|
|
30658
|
+
if (this.uiData.mousedown) {
|
|
30659
|
+
const xDiff = (this.clickToSegmentXY[0] - x) / this.gl.canvas.width;
|
|
30660
|
+
const yDiff = (this.clickToSegmentXY[1] - y) / this.gl.canvas.height;
|
|
30661
|
+
diff = Math.max(Math.abs(xDiff), Math.abs(yDiff));
|
|
30662
|
+
threshold = this.opts.clickToSegmentPercent + diff;
|
|
30663
|
+
}
|
|
30664
|
+
let voxelIntensity = this.back.getValue(pt[0], pt[1], pt[2]);
|
|
30665
|
+
if (this.opts.clickToSegmentAutoIntensity) {
|
|
30666
|
+
if (threshold !== 0) {
|
|
30667
|
+
if (voxelIntensity === 0) {
|
|
30668
|
+
voxelIntensity = 0.01;
|
|
30669
|
+
}
|
|
30670
|
+
this.opts.clickToSegmentIntensityMax = voxelIntensity * (1 + threshold);
|
|
30671
|
+
this.opts.clickToSegmentIntensityMin = voxelIntensity * (1 - threshold);
|
|
30672
|
+
}
|
|
30673
|
+
if (voxelIntensity > (this.back.cal_min + this.back.cal_max) * 0.5) {
|
|
30674
|
+
this.opts.clickToSegmentBright = true;
|
|
30675
|
+
} else {
|
|
30676
|
+
this.opts.clickToSegmentBright = false;
|
|
30677
|
+
}
|
|
30678
|
+
}
|
|
30309
30679
|
const brightOrDark = this.opts.clickToSegmentBright ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
|
|
30310
|
-
this.drawPenFillPts = [];
|
|
30311
30680
|
this.drawPenAxCorSag = axCorSag;
|
|
30312
|
-
|
|
30313
|
-
const
|
|
30314
|
-
const
|
|
30315
|
-
|
|
30316
|
-
|
|
30317
|
-
const
|
|
30318
|
-
const
|
|
30319
|
-
|
|
30320
|
-
|
|
30321
|
-
this.
|
|
30322
|
-
|
|
30323
|
-
|
|
30324
|
-
|
|
30681
|
+
if (this.drawPenAxCorSag === 0 /* AXIAL */) {
|
|
30682
|
+
const pixDims = [this.back.pixDimsRAS[0], this.back.pixDimsRAS[1]];
|
|
30683
|
+
const minPixDim = Math.min(...pixDims);
|
|
30684
|
+
this.opts.penSize = Math.ceil(this.opts.clickToSegmentRadius / minPixDim);
|
|
30685
|
+
} else if (this.drawPenAxCorSag === 1 /* CORONAL */) {
|
|
30686
|
+
const pixDims = [this.back.pixDimsRAS[0], this.back.pixDimsRAS[2]];
|
|
30687
|
+
const minPixDim = Math.min(...pixDims);
|
|
30688
|
+
this.opts.penSize = Math.ceil(this.opts.clickToSegmentRadius / minPixDim);
|
|
30689
|
+
} else if (this.drawPenAxCorSag === 2 /* SAGITTAL */) {
|
|
30690
|
+
const pixDims = [this.back.pixDimsRAS[1], this.back.pixDimsRAS[2]];
|
|
30691
|
+
const minPixDim = Math.min(...pixDims);
|
|
30692
|
+
this.opts.penSize = Math.ceil(this.opts.clickToSegmentRadius / minPixDim);
|
|
30693
|
+
}
|
|
30694
|
+
this.drawPt(pt[0], pt[1], pt[2], this.opts.penValue);
|
|
30695
|
+
if (diff !== 0) {
|
|
30696
|
+
this.clickToSegmentIsGrowing = true;
|
|
30697
|
+
this.clickToSegmentGrowingBitmap = this.drawBitmap.slice();
|
|
30698
|
+
} else {
|
|
30699
|
+
this.clickToSegmentIsGrowing = false;
|
|
30325
30700
|
}
|
|
30701
|
+
this.drawFloodFill(
|
|
30702
|
+
[pt[0], pt[1], pt[2]],
|
|
30703
|
+
0,
|
|
30704
|
+
brightOrDark,
|
|
30705
|
+
this.opts.clickToSegmentIntensityMin,
|
|
30706
|
+
this.opts.clickToSegmentIntensityMax,
|
|
30707
|
+
this.opts.floodFillNeighbors,
|
|
30708
|
+
this.opts.clickToSegmentMaxDistanceMM,
|
|
30709
|
+
this.opts.clickToSegmentIs2D
|
|
30710
|
+
);
|
|
30326
30711
|
this.drawScene();
|
|
30327
30712
|
this.createOnLocationChange(axCorSag);
|
|
30328
|
-
|
|
30713
|
+
if (this.clickToSegmentIsGrowing) {
|
|
30714
|
+
return;
|
|
30715
|
+
}
|
|
30716
|
+
const info = this.getDescriptives({
|
|
30717
|
+
layer: 0,
|
|
30718
|
+
masks: [],
|
|
30719
|
+
drawingIsMask: true
|
|
30720
|
+
});
|
|
30329
30721
|
this.onClickToSegment({ mL: info.volumeML, mm3: info.volumeMM3 });
|
|
30330
30722
|
return;
|
|
30331
30723
|
}
|
|
@@ -30543,20 +30935,42 @@ var Niivue = class _Niivue {
|
|
|
30543
30935
|
if (!this.rectShader) {
|
|
30544
30936
|
throw new Error("rectShader undefined");
|
|
30545
30937
|
}
|
|
30546
|
-
this.
|
|
30547
|
-
|
|
30548
|
-
|
|
30549
|
-
|
|
30550
|
-
|
|
30551
|
-
this.
|
|
30552
|
-
|
|
30553
|
-
|
|
30554
|
-
|
|
30555
|
-
|
|
30556
|
-
|
|
30557
|
-
|
|
30558
|
-
|
|
30559
|
-
|
|
30938
|
+
if (!this.opts.selectionBoxIsOutline) {
|
|
30939
|
+
this.rectShader.use(this.gl);
|
|
30940
|
+
this.gl.enable(this.gl.BLEND);
|
|
30941
|
+
this.gl.uniform4fv(this.rectShader.uniforms.lineColor, lineColor);
|
|
30942
|
+
this.gl.uniform2fv(this.rectShader.uniforms.canvasWidthHeight, [this.gl.canvas.width, this.gl.canvas.height]);
|
|
30943
|
+
this.gl.uniform4f(
|
|
30944
|
+
this.rectShader.uniforms.leftTopWidthHeight,
|
|
30945
|
+
leftTopWidthHeight[0],
|
|
30946
|
+
leftTopWidthHeight[1],
|
|
30947
|
+
leftTopWidthHeight[2],
|
|
30948
|
+
leftTopWidthHeight[3]
|
|
30949
|
+
);
|
|
30950
|
+
this.gl.bindVertexArray(this.genericVAO);
|
|
30951
|
+
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
30952
|
+
this.gl.bindVertexArray(this.unusedVAO);
|
|
30953
|
+
} else {
|
|
30954
|
+
this.drawCircle(leftTopWidthHeight, lineColor, 0.1);
|
|
30955
|
+
this.rectOutlineShader.use(this.gl);
|
|
30956
|
+
this.gl.enable(this.gl.BLEND);
|
|
30957
|
+
this.gl.uniform1f(this.rectOutlineShader.uniforms.thickness, this.opts.selectionBoxLineThickness);
|
|
30958
|
+
this.gl.uniform4fv(this.rectOutlineShader.uniforms.lineColor, lineColor);
|
|
30959
|
+
this.gl.uniform2fv(this.rectOutlineShader.uniforms.canvasWidthHeight, [
|
|
30960
|
+
this.gl.canvas.width,
|
|
30961
|
+
this.gl.canvas.height
|
|
30962
|
+
]);
|
|
30963
|
+
this.gl.uniform4f(
|
|
30964
|
+
this.rectOutlineShader.uniforms.leftTopWidthHeight,
|
|
30965
|
+
leftTopWidthHeight[0],
|
|
30966
|
+
leftTopWidthHeight[1],
|
|
30967
|
+
leftTopWidthHeight[2],
|
|
30968
|
+
leftTopWidthHeight[3]
|
|
30969
|
+
);
|
|
30970
|
+
this.gl.bindVertexArray(this.genericVAO);
|
|
30971
|
+
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
30972
|
+
this.gl.bindVertexArray(this.unusedVAO);
|
|
30973
|
+
}
|
|
30560
30974
|
}
|
|
30561
30975
|
drawCircle(leftTopWidthHeight, circleColor = this.opts.fontColor, fillPercent = 1) {
|
|
30562
30976
|
if (!this.circleShader) {
|
|
@@ -30582,6 +30996,10 @@ var Niivue = class _Niivue {
|
|
|
30582
30996
|
// not included in public docs
|
|
30583
30997
|
// draw a rectangle at desired location
|
|
30584
30998
|
drawSelectionBox(leftTopWidthHeight) {
|
|
30999
|
+
if (this.opts.dragMode === 6 /* roiSelection */) {
|
|
31000
|
+
this.drawCircle(leftTopWidthHeight, this.opts.selectionBoxColor, 0.1);
|
|
31001
|
+
return;
|
|
31002
|
+
}
|
|
30585
31003
|
this.drawRect(leftTopWidthHeight, this.opts.selectionBoxColor);
|
|
30586
31004
|
}
|
|
30587
31005
|
// not included in public docs
|
|
@@ -33258,10 +33676,14 @@ var Niivue = class _Niivue {
|
|
|
33258
33676
|
width,
|
|
33259
33677
|
height
|
|
33260
33678
|
]);
|
|
33679
|
+
return;
|
|
33261
33680
|
}
|
|
33262
33681
|
if (this.opts.clickToSegment) {
|
|
33263
33682
|
const x = this.mousePos[0];
|
|
33264
33683
|
const y = this.mousePos[1];
|
|
33684
|
+
if (this.inRenderTile(x, y) >= 0) {
|
|
33685
|
+
return;
|
|
33686
|
+
}
|
|
33265
33687
|
const tileIdx = this.tileIndex(x, y);
|
|
33266
33688
|
if (tileIdx > -1) {
|
|
33267
33689
|
const fovMM = this.screenSlices[tileIdx].fovMM;
|