@niivue/niivue 0.44.2-dev.1 → 0.44.2-dev.2
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 +15 -71
- package/build/index.js +198 -460
- package/build/index.js.map +1 -1
- package/build/index.min.js +1 -1
- package/dist/index.d.ts +9 -2
- package/dist/index.js +200 -39
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/package.json +1 -1
- package/src/index_segment.html +79 -11
- package/src/index_sync.html +64 -20
- package/src/niivue/index.ts +261 -49
- package/src/niivue.css +0 -1
- package/src/nvdocument.ts +5 -1
- package/dist/niivue.umd.js +0 -1869
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.45.1";
|
|
14
14
|
|
|
15
15
|
// src/logger.ts
|
|
16
16
|
var _Log = class _Log {
|
|
@@ -806,46 +806,6 @@ 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
|
-
}`;
|
|
849
809
|
var vertColorbarShader = `#version 300 es
|
|
850
810
|
#line 490
|
|
851
811
|
layout(location=0) in vec3 pos;
|
|
@@ -22290,7 +22250,6 @@ var DRAG_MODE = /* @__PURE__ */ ((DRAG_MODE2) => {
|
|
|
22290
22250
|
DRAG_MODE2[DRAG_MODE2["pan"] = 3] = "pan";
|
|
22291
22251
|
DRAG_MODE2[DRAG_MODE2["slicer3D"] = 4] = "slicer3D";
|
|
22292
22252
|
DRAG_MODE2[DRAG_MODE2["callbackOnly"] = 5] = "callbackOnly";
|
|
22293
|
-
DRAG_MODE2[DRAG_MODE2["roiSelection"] = 6] = "roiSelection";
|
|
22294
22253
|
return DRAG_MODE2;
|
|
22295
22254
|
})(DRAG_MODE || {});
|
|
22296
22255
|
var DEFAULT_OPTIONS = {
|
|
@@ -22360,27 +22319,10 @@ var DEFAULT_OPTIONS = {
|
|
|
22360
22319
|
renderOverlayBlend: 1,
|
|
22361
22320
|
sliceMosaicString: "",
|
|
22362
22321
|
centerMosaic: false,
|
|
22363
|
-
penSize: 1,
|
|
22364
|
-
// in voxels, since all drawing is done using bitmap indices
|
|
22365
22322
|
clickToSegment: false,
|
|
22366
|
-
clickToSegmentRadius:
|
|
22367
|
-
|
|
22368
|
-
clickToSegmentBright: true
|
|
22369
|
-
clickToSegmentAutoIntensity: false,
|
|
22370
|
-
// new option, but keep clickToSegmentBright for backwards compatibility
|
|
22371
|
-
clickToSegmentIntensityMax: NaN,
|
|
22372
|
-
// NaN will use auto threshold (default flood fill behavior from before)
|
|
22373
|
-
clickToSegmentIntensityMin: NaN,
|
|
22374
|
-
// NaN will use auto threshold (default flood fill behavior from before)
|
|
22375
|
-
// 0 will use auto threshold (default flood fill behavior from before)
|
|
22376
|
-
// Take the voxel intensity at the click point and use this percentage +/- to threshold the flood fill operation.
|
|
22377
|
-
// If greater than 0, clickedVoxelIntensity +/- clickedVoxelIntensity * clickToSegmentPercent will be used
|
|
22378
|
-
// for the clickToSegmentIntensityMin and clickToSegmentIntensityMax values.
|
|
22379
|
-
clickToSegmentPercent: 0,
|
|
22380
|
-
clickToSegmentMaxDistanceMM: Number.POSITIVE_INFINITY,
|
|
22381
|
-
// default value is infinity for backwards compatibility with flood fill routine.
|
|
22382
|
-
selectionBoxLineThickness: 4,
|
|
22383
|
-
selectionBoxIsOutline: false
|
|
22323
|
+
clickToSegmentRadius: 2,
|
|
22324
|
+
clickToSegmentSteps: 10,
|
|
22325
|
+
clickToSegmentBright: true
|
|
22384
22326
|
};
|
|
22385
22327
|
var INITIAL_SCENE_DATA = {
|
|
22386
22328
|
gamma: 1,
|
|
@@ -24077,11 +24019,12 @@ var defaultSaveImageOptions = {
|
|
|
24077
24019
|
isSaveDrawing: false,
|
|
24078
24020
|
volumeByIndex: 0
|
|
24079
24021
|
};
|
|
24080
|
-
var Niivue = class {
|
|
24022
|
+
var Niivue = class _Niivue {
|
|
24081
24023
|
/**
|
|
24082
24024
|
* @param options - options object to set modifiable Niivue properties
|
|
24083
24025
|
*/
|
|
24084
24026
|
constructor(options = DEFAULT_OPTIONS) {
|
|
24027
|
+
__publicField(this, "loaders", {});
|
|
24085
24028
|
__publicField(this, "canvas", null);
|
|
24086
24029
|
// the reference to the canvas element on the page
|
|
24087
24030
|
__publicField(this, "_gl", null);
|
|
@@ -24124,7 +24067,6 @@ var Niivue = class {
|
|
|
24124
24067
|
__publicField(this, "orientCubeShader");
|
|
24125
24068
|
__publicField(this, "orientCubeShaderVAO", null);
|
|
24126
24069
|
__publicField(this, "rectShader");
|
|
24127
|
-
__publicField(this, "rectOutlineShader");
|
|
24128
24070
|
__publicField(this, "renderShader");
|
|
24129
24071
|
__publicField(this, "lineShader");
|
|
24130
24072
|
__publicField(this, "line3DShader");
|
|
@@ -24177,20 +24119,7 @@ var Niivue = class {
|
|
|
24177
24119
|
__publicField(this, "extentsMax");
|
|
24178
24120
|
// ResizeObserver
|
|
24179
24121
|
__publicField(this, "resizeObserver", null);
|
|
24180
|
-
|
|
24181
|
-
__publicField(this, "syncOpts", {
|
|
24182
|
-
"3d": false,
|
|
24183
|
-
// legacy option
|
|
24184
|
-
"2d": false,
|
|
24185
|
-
// legacy option
|
|
24186
|
-
zoomPan: false,
|
|
24187
|
-
cal_min: false,
|
|
24188
|
-
cal_max: false,
|
|
24189
|
-
clipPlane: false,
|
|
24190
|
-
gamma: false,
|
|
24191
|
-
sliceType: false,
|
|
24192
|
-
crosshair: false
|
|
24193
|
-
});
|
|
24122
|
+
__publicField(this, "syncOpts", {});
|
|
24194
24123
|
__publicField(this, "readyForSync", false);
|
|
24195
24124
|
// UI Data
|
|
24196
24125
|
__publicField(this, "uiData", {
|
|
@@ -24669,9 +24598,6 @@ var Niivue = class {
|
|
|
24669
24598
|
* @see {@link https://niivue.github.io/niivue/features/sync.mesh.html | live demo usage}
|
|
24670
24599
|
*/
|
|
24671
24600
|
syncWith(otherNV, syncOpts = { "2d": true, "3d": true }) {
|
|
24672
|
-
if (!(otherNV instanceof Array)) {
|
|
24673
|
-
otherNV = [otherNV];
|
|
24674
|
-
}
|
|
24675
24601
|
this.otherNV = otherNV;
|
|
24676
24602
|
this.syncOpts = { ...syncOpts };
|
|
24677
24603
|
}
|
|
@@ -24687,55 +24613,9 @@ var Niivue = class {
|
|
|
24687
24613
|
* @see {@link https://niivue.github.io/niivue/features/sync.mesh.html | live demo usage}
|
|
24688
24614
|
*/
|
|
24689
24615
|
broadcastTo(otherNV, syncOpts = { "2d": true, "3d": true }) {
|
|
24690
|
-
if (!(otherNV instanceof Array)) {
|
|
24691
|
-
otherNV = [otherNV];
|
|
24692
|
-
}
|
|
24693
24616
|
this.otherNV = otherNV;
|
|
24694
24617
|
this.syncOpts = syncOpts;
|
|
24695
24618
|
}
|
|
24696
|
-
doSync3d(otherNV) {
|
|
24697
|
-
otherNV.scene.renderAzimuth = this.scene.renderAzimuth;
|
|
24698
|
-
otherNV.scene.renderElevation = this.scene.renderElevation;
|
|
24699
|
-
otherNV.scene.volScaleMultiplier = this.scene.volScaleMultiplier;
|
|
24700
|
-
}
|
|
24701
|
-
// both crosshair and zoomPan
|
|
24702
|
-
doSync2d(otherNV) {
|
|
24703
|
-
const thisMM = this.frac2mm(this.scene.crosshairPos);
|
|
24704
|
-
otherNV.scene.crosshairPos = otherNV.mm2frac(thisMM);
|
|
24705
|
-
otherNV.scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24706
|
-
}
|
|
24707
|
-
doSyncGamma(otherNV) {
|
|
24708
|
-
const thisGamma = this.scene.gamma;
|
|
24709
|
-
const otherGamma = otherNV.scene.gamma;
|
|
24710
|
-
if (thisGamma !== otherGamma) {
|
|
24711
|
-
otherNV.setGamma(thisGamma);
|
|
24712
|
-
}
|
|
24713
|
-
}
|
|
24714
|
-
doSyncZoomPan(otherNV) {
|
|
24715
|
-
otherNV.scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24716
|
-
}
|
|
24717
|
-
doSyncCrosshair(otherNV) {
|
|
24718
|
-
const thisMM = this.frac2mm(this.scene.crosshairPos);
|
|
24719
|
-
otherNV.scene.crosshairPos = otherNV.mm2frac(thisMM);
|
|
24720
|
-
}
|
|
24721
|
-
doSyncCalMin(otherNV) {
|
|
24722
|
-
if (this.volumes[0].cal_min !== otherNV.volumes[0].cal_min) {
|
|
24723
|
-
otherNV.volumes[0].cal_min = this.volumes[0].cal_min;
|
|
24724
|
-
otherNV.updateGLVolume();
|
|
24725
|
-
}
|
|
24726
|
-
}
|
|
24727
|
-
doSyncCalMax(otherNV) {
|
|
24728
|
-
if (this.volumes[0].cal_max !== otherNV.volumes[0].cal_max) {
|
|
24729
|
-
otherNV.volumes[0].cal_max = this.volumes[0].cal_max;
|
|
24730
|
-
otherNV.updateGLVolume();
|
|
24731
|
-
}
|
|
24732
|
-
}
|
|
24733
|
-
doSyncSliceType(otherNV) {
|
|
24734
|
-
otherNV.setSliceType(this.opts.sliceType);
|
|
24735
|
-
}
|
|
24736
|
-
doSyncClipPlane(otherNV) {
|
|
24737
|
-
otherNV.setClipPlane(this.scene.clipPlaneDepthAziElev);
|
|
24738
|
-
}
|
|
24739
24619
|
/**
|
|
24740
24620
|
* Sync the scene controls (orientation, crosshair location, etc.) from one Niivue instance to another. useful for using one canvas to drive another.
|
|
24741
24621
|
* @internal
|
|
@@ -24752,39 +24632,46 @@ var Niivue = class {
|
|
|
24752
24632
|
if (!this.gl.canvas.matches(":focus")) {
|
|
24753
24633
|
return;
|
|
24754
24634
|
}
|
|
24755
|
-
|
|
24756
|
-
|
|
24757
|
-
|
|
24758
|
-
|
|
24759
|
-
if (
|
|
24760
|
-
this.
|
|
24761
|
-
}
|
|
24762
|
-
if (this.syncOpts.crosshair) {
|
|
24763
|
-
this.doSyncCrosshair(this.otherNV[i]);
|
|
24764
|
-
}
|
|
24765
|
-
if (this.syncOpts.zoomPan) {
|
|
24766
|
-
this.doSyncZoomPan(this.otherNV[i]);
|
|
24767
|
-
}
|
|
24768
|
-
if (this.syncOpts.sliceType) {
|
|
24769
|
-
this.doSyncSliceType(this.otherNV[i]);
|
|
24770
|
-
}
|
|
24771
|
-
if (this.syncOpts.cal_min) {
|
|
24772
|
-
this.doSyncCalMin(this.otherNV[i]);
|
|
24773
|
-
}
|
|
24774
|
-
if (this.syncOpts.cal_max) {
|
|
24775
|
-
this.doSyncCalMax(this.otherNV[i]);
|
|
24776
|
-
}
|
|
24777
|
-
if (this.syncOpts.clipPlane) {
|
|
24778
|
-
this.doSyncClipPlane(this.otherNV[i]);
|
|
24635
|
+
const thisMM = this.frac2mm(this.scene.crosshairPos);
|
|
24636
|
+
if (this.otherNV instanceof _Niivue) {
|
|
24637
|
+
const thisGamma = this.scene.gamma;
|
|
24638
|
+
const otherGamma = this.otherNV.scene.gamma;
|
|
24639
|
+
if (thisGamma !== otherGamma) {
|
|
24640
|
+
this.otherNV.setGamma(thisGamma);
|
|
24779
24641
|
}
|
|
24780
24642
|
if (this.syncOpts["2d"]) {
|
|
24781
|
-
this.
|
|
24643
|
+
this.otherNV.scene.crosshairPos = this.otherNV.mm2frac(thisMM);
|
|
24644
|
+
this.otherNV.scene.pan2Dxyzmm = vec48.clone(this.scene.pan2Dxyzmm);
|
|
24782
24645
|
}
|
|
24783
24646
|
if (this.syncOpts["3d"]) {
|
|
24784
|
-
this.
|
|
24647
|
+
this.otherNV.scene.renderAzimuth = this.scene.renderAzimuth;
|
|
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();
|
|
24785
24674
|
}
|
|
24786
|
-
this.otherNV[i].drawScene();
|
|
24787
|
-
this.otherNV[i].createOnLocationChange();
|
|
24788
24675
|
}
|
|
24789
24676
|
}
|
|
24790
24677
|
/** Not documented publicly for now
|
|
@@ -25101,9 +24988,6 @@ var Niivue = class {
|
|
|
25101
24988
|
const fracStart = this.canvasPos2frac([this.uiData.dragStart[0], this.uiData.dragStart[1]]);
|
|
25102
24989
|
const fracEnd = this.canvasPos2frac([this.uiData.dragEnd[0], this.uiData.dragEnd[1]]);
|
|
25103
24990
|
this.generateMouseUpCallback(fracStart, fracEnd);
|
|
25104
|
-
if (this.opts.dragMode === 6 /* roiSelection */) {
|
|
25105
|
-
return;
|
|
25106
|
-
}
|
|
25107
24991
|
if (this.opts.dragMode !== 1 /* contrast */) {
|
|
25108
24992
|
return;
|
|
25109
24993
|
}
|
|
@@ -25455,6 +25339,11 @@ var Niivue = class {
|
|
|
25455
25339
|
if (ext === "GZ") {
|
|
25456
25340
|
ext = re.exec(fullname.slice(0, -3))[1];
|
|
25457
25341
|
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}`;
|
|
25458
25347
|
}
|
|
25459
25348
|
return upperCase ? ext : ext.toLowerCase();
|
|
25460
25349
|
}
|
|
@@ -25475,6 +25364,28 @@ var Niivue = class {
|
|
|
25475
25364
|
}
|
|
25476
25365
|
async addVolumesFromUrl(imageOptionsArray) {
|
|
25477
25366
|
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
|
+
}
|
|
25478
25389
|
const imageOptions = {
|
|
25479
25390
|
url: imageItem.url,
|
|
25480
25391
|
headers: imageItem.headers,
|
|
@@ -25614,6 +25525,40 @@ var Niivue = class {
|
|
|
25614
25525
|
this.addVolume(volume);
|
|
25615
25526
|
});
|
|
25616
25527
|
}
|
|
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
|
+
}
|
|
25617
25562
|
// not included in public docs
|
|
25618
25563
|
dropListener(e) {
|
|
25619
25564
|
e.stopPropagation();
|
|
@@ -27285,6 +27230,37 @@ var Niivue = class {
|
|
|
27285
27230
|
this.document.meshes = this.meshes;
|
|
27286
27231
|
return this.document.download(fileName, compress2);
|
|
27287
27232
|
}
|
|
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
|
+
}
|
|
27288
27264
|
/**
|
|
27289
27265
|
* load an array of volume objects
|
|
27290
27266
|
* @param volumeList - the array of objects to load. each object must have a resolvable "url" property at a minimum
|
|
@@ -27304,27 +27280,7 @@ var Niivue = class {
|
|
|
27304
27280
|
this.volumes = [];
|
|
27305
27281
|
this.gl.clearColor(0, 0, 0, 1);
|
|
27306
27282
|
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
|
27307
|
-
|
|
27308
|
-
await this.addVolumesFromUrl(volumeList);
|
|
27309
|
-
return this;
|
|
27310
|
-
}
|
|
27311
|
-
const imageOptions = {
|
|
27312
|
-
url: volumeList[0].url,
|
|
27313
|
-
headers: volumeList[0].headers,
|
|
27314
|
-
name: volumeList[0].name,
|
|
27315
|
-
colormap: volumeList[0].colormap ? volumeList[0].colormap : volumeList[0].colorMap,
|
|
27316
|
-
colormapNegative: volumeList[0].colormapNegative ? volumeList[0].colormapNegative : volumeList[0].colorMapNegative,
|
|
27317
|
-
opacity: volumeList[0].opacity,
|
|
27318
|
-
urlImgData: volumeList[0].urlImgData,
|
|
27319
|
-
cal_min: volumeList[0].cal_min,
|
|
27320
|
-
cal_max: volumeList[0].cal_max,
|
|
27321
|
-
trustCalMinMax: this.opts.trustCalMinMax,
|
|
27322
|
-
isManifest: volumeList[0].isManifest,
|
|
27323
|
-
frame4D: volumeList[0].frame4D,
|
|
27324
|
-
limitFrames4D: volumeList[0].limitFrames4D || this.opts.limitFrames4D,
|
|
27325
|
-
colorbarVisible: volumeList[0].colorbarVisible
|
|
27326
|
-
};
|
|
27327
|
-
await this.addVolumeFromUrl(imageOptions);
|
|
27283
|
+
await this.addVolumesFromUrl(volumeList);
|
|
27328
27284
|
return this;
|
|
27329
27285
|
}
|
|
27330
27286
|
/**
|
|
@@ -27354,7 +27310,29 @@ var Niivue = class {
|
|
|
27354
27310
|
*/
|
|
27355
27311
|
async addMeshesFromUrl(meshOptions) {
|
|
27356
27312
|
const promises = meshOptions.map(async (meshItem) => {
|
|
27357
|
-
const ext = this.getFileExt(meshItem.url);
|
|
27313
|
+
const ext = this.getFileExt(meshItem.name || meshItem.url);
|
|
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
|
+
}
|
|
27358
27336
|
if (ext === "JCON" || ext === "JSON") {
|
|
27359
27337
|
const response = await fetch(meshItem.url, {});
|
|
27360
27338
|
const json = await response.json();
|
|
@@ -27395,13 +27373,7 @@ var Niivue = class {
|
|
|
27395
27373
|
this.meshes = [];
|
|
27396
27374
|
this.gl.clearColor(0, 0, 0, 1);
|
|
27397
27375
|
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
|
27398
|
-
|
|
27399
|
-
await this.addMeshesFromUrl(meshList);
|
|
27400
|
-
this.updateGLVolume();
|
|
27401
|
-
this.drawScene();
|
|
27402
|
-
return this;
|
|
27403
|
-
}
|
|
27404
|
-
await this.addMeshFromUrl(meshList[0]);
|
|
27376
|
+
await this.addMeshesFromUrl(meshList);
|
|
27405
27377
|
this.updateGLVolume();
|
|
27406
27378
|
this.drawScene();
|
|
27407
27379
|
return this;
|
|
@@ -27675,23 +27647,6 @@ var Niivue = class {
|
|
|
27675
27647
|
y = Math.min(Math.max(y, 0), dy - 1);
|
|
27676
27648
|
z = Math.min(Math.max(z, 0), dz - 1);
|
|
27677
27649
|
this.drawBitmap[x + y * dx + z * dx * dy] = penValue;
|
|
27678
|
-
const isAx = this.drawPenAxCorSag === 0;
|
|
27679
|
-
const isCor = this.drawPenAxCorSag === 1;
|
|
27680
|
-
const isSag = this.drawPenAxCorSag === 2;
|
|
27681
|
-
if (this.opts.penSize > 1) {
|
|
27682
|
-
const halfPenSize = Math.floor(this.opts.penSize / 2);
|
|
27683
|
-
for (let i = -halfPenSize; i <= halfPenSize; i++) {
|
|
27684
|
-
for (let j = -halfPenSize; j <= halfPenSize; j++) {
|
|
27685
|
-
if (isAx) {
|
|
27686
|
-
this.drawBitmap[x + i + (y + j) * dx + z * dx * dy] = penValue;
|
|
27687
|
-
} else if (isCor) {
|
|
27688
|
-
this.drawBitmap[x + i + y * dx + (z + j) * dx * dy] = penValue;
|
|
27689
|
-
} else if (isSag) {
|
|
27690
|
-
this.drawBitmap[x + (y + j) * dx + (z + i) * dx * dy] = penValue;
|
|
27691
|
-
}
|
|
27692
|
-
}
|
|
27693
|
-
}
|
|
27694
|
-
}
|
|
27695
27650
|
}
|
|
27696
27651
|
// not included in public docs
|
|
27697
27652
|
// create line between to voxels in drawing
|
|
@@ -27773,98 +27728,6 @@ var Niivue = class {
|
|
|
27773
27728
|
}
|
|
27774
27729
|
}
|
|
27775
27730
|
}
|
|
27776
|
-
/**
|
|
27777
|
-
* Performs a 1-voxel binary dilation on a connected cluster within the drawing mask using the drawFloodFillCore function.
|
|
27778
|
-
*
|
|
27779
|
-
* @param seedXYZ - voxel index of the seed voxel in the mask array.
|
|
27780
|
-
* @param neighbors - Number of neighbors to consider for connectivity and dilation (6, 18, or 26).
|
|
27781
|
-
*/
|
|
27782
|
-
drawingBinaryDilationWithSeed(seedXYZ, neighbors = 6) {
|
|
27783
|
-
try {
|
|
27784
|
-
let xyz2vx = function(pt) {
|
|
27785
|
-
return pt[0] + pt[1] * nx + pt[2] * nxy;
|
|
27786
|
-
};
|
|
27787
|
-
const mask = this.drawBitmap;
|
|
27788
|
-
const xDim = this.back.dims[1];
|
|
27789
|
-
const yDim = this.back.dims[2];
|
|
27790
|
-
const zDim = this.back.dims[3];
|
|
27791
|
-
const nx = xDim;
|
|
27792
|
-
const nxy = xDim * yDim;
|
|
27793
|
-
const totalVoxels = nxy * zDim;
|
|
27794
|
-
const seedIndex = xyz2vx(seedXYZ);
|
|
27795
|
-
if (seedIndex < 0 || seedIndex >= totalVoxels) {
|
|
27796
|
-
throw new Error("Seed index is out of bounds.");
|
|
27797
|
-
}
|
|
27798
|
-
const seedValue = mask[seedIndex];
|
|
27799
|
-
if (seedValue === 0) {
|
|
27800
|
-
throw new Error("Seed voxel is not part of a filled cluster.");
|
|
27801
|
-
}
|
|
27802
|
-
const img = mask.slice();
|
|
27803
|
-
for (let i = 0; i < totalVoxels; i++) {
|
|
27804
|
-
img[i] = img[i] === seedValue ? 1 : 0;
|
|
27805
|
-
}
|
|
27806
|
-
this.drawFloodFillCore(img, seedIndex, neighbors);
|
|
27807
|
-
const outputMask = mask.slice();
|
|
27808
|
-
const neighborOffsets = [];
|
|
27809
|
-
const offsets6 = [-nxy, nxy, -xDim, xDim, -1, 1];
|
|
27810
|
-
neighborOffsets.push(...offsets6);
|
|
27811
|
-
if (neighbors > 6) {
|
|
27812
|
-
neighborOffsets.push(
|
|
27813
|
-
-xDim - 1,
|
|
27814
|
-
-xDim + 1,
|
|
27815
|
-
xDim - 1,
|
|
27816
|
-
xDim + 1,
|
|
27817
|
-
-nxy - xDim,
|
|
27818
|
-
-nxy + xDim,
|
|
27819
|
-
-nxy - 1,
|
|
27820
|
-
-nxy + 1,
|
|
27821
|
-
nxy - xDim,
|
|
27822
|
-
nxy + xDim,
|
|
27823
|
-
nxy - 1,
|
|
27824
|
-
nxy + 1
|
|
27825
|
-
);
|
|
27826
|
-
}
|
|
27827
|
-
if (neighbors > 18) {
|
|
27828
|
-
neighborOffsets.push(
|
|
27829
|
-
-nxy - xDim - 1,
|
|
27830
|
-
-nxy - xDim + 1,
|
|
27831
|
-
-nxy + xDim - 1,
|
|
27832
|
-
-nxy + xDim + 1,
|
|
27833
|
-
nxy - xDim - 1,
|
|
27834
|
-
nxy - xDim + 1,
|
|
27835
|
-
nxy + xDim - 1,
|
|
27836
|
-
nxy + xDim + 1
|
|
27837
|
-
);
|
|
27838
|
-
}
|
|
27839
|
-
for (let idx = 0; idx < totalVoxels; idx++) {
|
|
27840
|
-
if (img[idx] === 2) {
|
|
27841
|
-
const x = idx % xDim;
|
|
27842
|
-
const y = Math.floor(idx % nxy / xDim);
|
|
27843
|
-
const z = Math.floor(idx / nxy);
|
|
27844
|
-
for (const offset of neighborOffsets) {
|
|
27845
|
-
const neighborIdx = idx + offset;
|
|
27846
|
-
if (neighborIdx < 0 || neighborIdx >= totalVoxels) {
|
|
27847
|
-
continue;
|
|
27848
|
-
}
|
|
27849
|
-
const nx2 = neighborIdx % xDim;
|
|
27850
|
-
const ny = Math.floor(neighborIdx % nxy / xDim);
|
|
27851
|
-
const nz = Math.floor(neighborIdx / nxy);
|
|
27852
|
-
if (Math.abs(nx2 - x) > 1 || Math.abs(ny - y) > 1 || Math.abs(nz - z) > 1) {
|
|
27853
|
-
continue;
|
|
27854
|
-
}
|
|
27855
|
-
if (mask[neighborIdx] === 0) {
|
|
27856
|
-
outputMask[neighborIdx] = seedValue;
|
|
27857
|
-
}
|
|
27858
|
-
}
|
|
27859
|
-
}
|
|
27860
|
-
}
|
|
27861
|
-
this.drawBitmap = outputMask;
|
|
27862
|
-
this.drawAddUndoBitmap();
|
|
27863
|
-
this.refreshDrawing(true);
|
|
27864
|
-
} catch (error) {
|
|
27865
|
-
log.error("Error in drawingBinaryDilationWithSeed:", error);
|
|
27866
|
-
}
|
|
27867
|
-
}
|
|
27868
27731
|
// a voxel can be defined as having 6, 18 or 26 neighbors:
|
|
27869
27732
|
// 6: neighbors share faces (distance=1)
|
|
27870
27733
|
// 18: neighbors share faces (distance=1) or edges (1.4)
|
|
@@ -27946,7 +27809,7 @@ var Niivue = class {
|
|
|
27946
27809
|
}
|
|
27947
27810
|
// not included in public docs
|
|
27948
27811
|
// set all connected voxels in drawing to new color
|
|
27949
|
-
drawFloodFill(seedXYZ, newColor = 0, growSelectedCluster = 0, forceMin = NaN, forceMax = NaN, neighbors = 6
|
|
27812
|
+
drawFloodFill(seedXYZ, newColor = 0, growSelectedCluster = 0, forceMin = NaN, forceMax = NaN, neighbors = 6) {
|
|
27950
27813
|
if (!this.drawBitmap) {
|
|
27951
27814
|
throw new Error("drawBitmap undefined");
|
|
27952
27815
|
}
|
|
@@ -27968,25 +27831,9 @@ var Niivue = class {
|
|
|
27968
27831
|
if (img.length !== nxy * dims[2]) {
|
|
27969
27832
|
return;
|
|
27970
27833
|
}
|
|
27971
|
-
function vx2xyz(vx) {
|
|
27972
|
-
const Z = Math.floor(vx / nxy);
|
|
27973
|
-
const Y = Math.floor((vx - Z * nxy) / nx);
|
|
27974
|
-
const X = Math.floor(vx % nx);
|
|
27975
|
-
return [X, Y, Z];
|
|
27976
|
-
}
|
|
27977
27834
|
function xyz2vx(pt) {
|
|
27978
27835
|
return pt[0] + pt[1] * nx + pt[2] * nxy;
|
|
27979
27836
|
}
|
|
27980
|
-
const vx2mm = (xyz) => {
|
|
27981
|
-
return this.vox2mm(xyz, this.back.matRAS);
|
|
27982
|
-
};
|
|
27983
|
-
const seedMM = vx2mm(seedXYZ);
|
|
27984
|
-
const maxDistanceMM2 = maxDistanceMM ** 2;
|
|
27985
|
-
function isWithinDistance(vx) {
|
|
27986
|
-
const xyzMM = vx2mm(vx2xyz(vx));
|
|
27987
|
-
const dist2 = (xyzMM[0] - seedMM[0]) ** 2 + (xyzMM[1] - seedMM[1]) ** 2 + (xyzMM[2] - seedMM[2]) ** 2;
|
|
27988
|
-
return dist2 <= maxDistanceMM2;
|
|
27989
|
-
}
|
|
27990
27837
|
const seedVx = xyz2vx(seedXYZ);
|
|
27991
27838
|
const seedColor = img[seedVx];
|
|
27992
27839
|
if (seedColor === newColor) {
|
|
@@ -28000,9 +27847,6 @@ var Niivue = class {
|
|
|
28000
27847
|
for (let i = 1; i < nxyz; i++) {
|
|
28001
27848
|
img[i] = 0;
|
|
28002
27849
|
if (this.drawBitmap[i] === seedColor) {
|
|
28003
|
-
if (!isWithinDistance(i)) {
|
|
28004
|
-
continue;
|
|
28005
|
-
}
|
|
28006
27850
|
img[i] = 1;
|
|
28007
27851
|
}
|
|
28008
27852
|
}
|
|
@@ -28032,9 +27876,6 @@ var Niivue = class {
|
|
|
28032
27876
|
for (let i = 1; i < nxyz; i++) {
|
|
28033
27877
|
img[i] = 0;
|
|
28034
27878
|
if (backImg[i] >= mn && backImg[i] <= mx) {
|
|
28035
|
-
if (!isWithinDistance(i)) {
|
|
28036
|
-
continue;
|
|
28037
|
-
}
|
|
28038
27879
|
img[i] = 1;
|
|
28039
27880
|
}
|
|
28040
27881
|
}
|
|
@@ -28656,8 +28497,6 @@ var Niivue = class {
|
|
|
28656
28497
|
gl.bindVertexArray(this.unusedVAO);
|
|
28657
28498
|
this.rectShader = new Shader(gl, vertRectShader, fragRectShader);
|
|
28658
28499
|
this.rectShader.use(gl);
|
|
28659
|
-
this.rectOutlineShader = new Shader(gl, vertRectShader, fragRectOutlineShader);
|
|
28660
|
-
this.rectOutlineShader.use(gl);
|
|
28661
28500
|
this.lineShader = new Shader(gl, vertLineShader, fragRectShader);
|
|
28662
28501
|
this.lineShader.use(gl);
|
|
28663
28502
|
this.line3DShader = new Shader(gl, vertLine3DShader, fragRectShader);
|
|
@@ -28810,37 +28649,14 @@ var Niivue = class {
|
|
|
28810
28649
|
}
|
|
28811
28650
|
/**
|
|
28812
28651
|
* basic statistics for selected voxel-based image
|
|
28813
|
-
* @param
|
|
28814
|
-
*
|
|
28815
|
-
*
|
|
28816
|
-
*
|
|
28817
|
-
* - roiIsMask: a boolean indicating if the ROI is used as a mask
|
|
28818
|
-
* - startVox: the starting voxel coordinates
|
|
28819
|
-
* - endVox: the ending voxel coordinates
|
|
28820
|
-
* @returns numeric values to describe image or regions of images
|
|
28821
|
-
* @example
|
|
28822
|
-
* niivue.getDescriptives({
|
|
28823
|
-
* layer: 0,
|
|
28824
|
-
* masks: [],
|
|
28825
|
-
* drawingIsMask: true, // drawingIsMask and roiIsMask are mutually exclusive
|
|
28826
|
-
* roiIsMask: false,
|
|
28827
|
-
* startVox: [10, 20, 30], // ignored if roiIsMask is false
|
|
28828
|
-
* endVox: [40, 50, 60] // ignored if roiIsMask is false
|
|
28829
|
-
* });
|
|
28652
|
+
* @param layer - selects image to describe
|
|
28653
|
+
* @param masks - are optional binary images to filter voxles
|
|
28654
|
+
* @returns numeric values to describe image
|
|
28655
|
+
* @example niivue.getDescriptives(0);
|
|
28830
28656
|
* @see {@link https://niivue.github.io/niivue/features/draw2.html | live demo usage}
|
|
28831
28657
|
*/
|
|
28832
|
-
getDescriptives(
|
|
28833
|
-
const {
|
|
28834
|
-
layer = 0,
|
|
28835
|
-
masks = [],
|
|
28836
|
-
drawingIsMask = false,
|
|
28837
|
-
roiIsMask = false,
|
|
28838
|
-
startVox = [0, 0, 0],
|
|
28839
|
-
endVox = [0, 0, 0]
|
|
28840
|
-
} = options;
|
|
28841
|
-
let area = null;
|
|
28658
|
+
getDescriptives(layer = 0, masks = [], drawingIsMask = false) {
|
|
28842
28659
|
const hdr = this.volumes[layer].hdr;
|
|
28843
|
-
const pixDimsRAS = this.volumes[layer].pixDimsRAS;
|
|
28844
28660
|
let slope = hdr.scl_slope;
|
|
28845
28661
|
if (isNaN(slope)) {
|
|
28846
28662
|
slope = 1;
|
|
@@ -28878,37 +28694,6 @@ var Niivue = class {
|
|
|
28878
28694
|
mask[i] = 0;
|
|
28879
28695
|
}
|
|
28880
28696
|
}
|
|
28881
|
-
} else if (masks.length < 1 && roiIsMask) {
|
|
28882
|
-
mask.fill(0);
|
|
28883
|
-
console.log("startVox", startVox);
|
|
28884
|
-
console.log("endVox", endVox);
|
|
28885
|
-
const xrange = this.calculateMinMaxVoxIdx([startVox[0], endVox[0]]);
|
|
28886
|
-
const yrange = this.calculateMinMaxVoxIdx([startVox[1], endVox[1]]);
|
|
28887
|
-
const zrange = this.calculateMinMaxVoxIdx([startVox[2], endVox[2]]);
|
|
28888
|
-
if (startVox[0] - endVox[0] === 0) {
|
|
28889
|
-
xrange[1] = startVox[0] + 1;
|
|
28890
|
-
} else if (startVox[1] - endVox[1] === 0) {
|
|
28891
|
-
yrange[1] = startVox[1] + 1;
|
|
28892
|
-
} else if (startVox[2] - endVox[2] === 0) {
|
|
28893
|
-
zrange[1] = startVox[2] + 1;
|
|
28894
|
-
}
|
|
28895
|
-
const xdim = hdr.dims[1];
|
|
28896
|
-
const ydim = hdr.dims[2];
|
|
28897
|
-
for (let z = zrange[0]; z < zrange[1]; z++) {
|
|
28898
|
-
const zi = z * xdim * ydim;
|
|
28899
|
-
for (let y = yrange[0]; y < yrange[1]; y++) {
|
|
28900
|
-
const yi = y * xdim;
|
|
28901
|
-
for (let x = xrange[0]; x < xrange[1]; x++) {
|
|
28902
|
-
const index = zi + yi + x;
|
|
28903
|
-
mask[index] = 1;
|
|
28904
|
-
}
|
|
28905
|
-
}
|
|
28906
|
-
}
|
|
28907
|
-
const xmm = Math.abs(pixDimsRAS[1] * (endVox[0] - startVox[0]));
|
|
28908
|
-
const ymm = Math.abs(pixDimsRAS[2] * (endVox[1] - startVox[1]));
|
|
28909
|
-
const zmm = Math.abs(pixDimsRAS[3] * (endVox[2] - startVox[2]));
|
|
28910
|
-
const mms = [xmm, ymm, zmm];
|
|
28911
|
-
area = mms.filter((m) => m !== 0).reduce((a, b) => a * b, 1);
|
|
28912
28697
|
}
|
|
28913
28698
|
let k = 0;
|
|
28914
28699
|
let M = 0;
|
|
@@ -28962,8 +28747,7 @@ var Niivue = class {
|
|
|
28962
28747
|
cal_min: this.volumes[layer].cal_min,
|
|
28963
28748
|
cal_max: this.volumes[layer].cal_max,
|
|
28964
28749
|
robust_min: this.volumes[layer].robust_min,
|
|
28965
|
-
robust_max: this.volumes[layer].robust_max
|
|
28966
|
-
area
|
|
28750
|
+
robust_max: this.volumes[layer].robust_max
|
|
28967
28751
|
};
|
|
28968
28752
|
}
|
|
28969
28753
|
// not included in public docs
|
|
@@ -30518,51 +30302,30 @@ var Niivue = class {
|
|
|
30518
30302
|
}
|
|
30519
30303
|
if (this.opts.drawingEnabled) {
|
|
30520
30304
|
const pt = this.frac2vox(this.scene.crosshairPos);
|
|
30305
|
+
const ptMM = this.frac2mm(this.scene.crosshairPos);
|
|
30521
30306
|
if (this.opts.clickToSegment) {
|
|
30522
|
-
const
|
|
30523
|
-
|
|
30524
|
-
this.opts.clickToSegmentIntensityMax = voxelIntensity * (1 + this.opts.clickToSegmentPercent);
|
|
30525
|
-
this.opts.clickToSegmentIntensityMin = voxelIntensity * (1 - this.opts.clickToSegmentPercent);
|
|
30526
|
-
}
|
|
30527
|
-
if (this.opts.clickToSegmentAutoIntensity) {
|
|
30528
|
-
if (voxelIntensity > (this.back.cal_min + this.back.cal_max) * 0.5) {
|
|
30529
|
-
this.opts.clickToSegmentBright = true;
|
|
30530
|
-
} else {
|
|
30531
|
-
this.opts.clickToSegmentBright = false;
|
|
30532
|
-
}
|
|
30533
|
-
}
|
|
30307
|
+
const radius = this.opts.clickToSegmentRadius;
|
|
30308
|
+
const steps = this.opts.clickToSegmentSteps;
|
|
30534
30309
|
const brightOrDark = this.opts.clickToSegmentBright ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
|
|
30310
|
+
this.drawPenFillPts = [];
|
|
30535
30311
|
this.drawPenAxCorSag = axCorSag;
|
|
30536
|
-
|
|
30537
|
-
const
|
|
30538
|
-
const
|
|
30539
|
-
|
|
30540
|
-
|
|
30541
|
-
const
|
|
30542
|
-
const
|
|
30543
|
-
|
|
30544
|
-
|
|
30545
|
-
|
|
30546
|
-
|
|
30547
|
-
|
|
30312
|
+
for (let i2 = 1; i2 <= steps; i2++) {
|
|
30313
|
+
const angle = i2 / steps * 2 * Math.PI;
|
|
30314
|
+
const xMM = ptMM[0] + radius * Math.cos(angle);
|
|
30315
|
+
const yMM = ptMM[1] + radius * Math.sin(angle);
|
|
30316
|
+
const zMM = ptMM[2];
|
|
30317
|
+
const xVox = this.back.mm2vox([xMM, yMM, zMM])[0];
|
|
30318
|
+
const yVox = this.back.mm2vox([xMM, yMM, zMM])[1];
|
|
30319
|
+
const zVox = this.back.mm2vox([xMM, yMM, zMM])[2];
|
|
30320
|
+
this.drawPt(xVox, yVox, zVox, this.opts.penValue);
|
|
30321
|
+
this.drawPenFillPts.push([xVox, yVox, pt[2]]);
|
|
30322
|
+
if (i2 === steps) {
|
|
30323
|
+
this.drawFloodFill([xVox, yVox, pt[2]], 0, brightOrDark, NaN, NaN, this.opts.floodFillNeighbors);
|
|
30324
|
+
}
|
|
30548
30325
|
}
|
|
30549
|
-
this.drawPt(pt[0], pt[1], pt[2], this.opts.penValue);
|
|
30550
|
-
this.drawFloodFill(
|
|
30551
|
-
[pt[0], pt[1], pt[2]],
|
|
30552
|
-
0,
|
|
30553
|
-
brightOrDark,
|
|
30554
|
-
this.opts.clickToSegmentIntensityMin,
|
|
30555
|
-
this.opts.clickToSegmentIntensityMax,
|
|
30556
|
-
this.opts.floodFillNeighbors,
|
|
30557
|
-
this.opts.clickToSegmentMaxDistanceMM
|
|
30558
|
-
);
|
|
30559
30326
|
this.drawScene();
|
|
30560
30327
|
this.createOnLocationChange(axCorSag);
|
|
30561
|
-
const info = this.getDescriptives(
|
|
30562
|
-
layer: 0,
|
|
30563
|
-
masks: [],
|
|
30564
|
-
drawingIsMask: true
|
|
30565
|
-
});
|
|
30328
|
+
const info = this.getDescriptives(0, [], true);
|
|
30566
30329
|
this.onClickToSegment({ mL: info.volumeML, mm3: info.volumeMM3 });
|
|
30567
30330
|
return;
|
|
30568
30331
|
}
|
|
@@ -30780,41 +30543,20 @@ var Niivue = class {
|
|
|
30780
30543
|
if (!this.rectShader) {
|
|
30781
30544
|
throw new Error("rectShader undefined");
|
|
30782
30545
|
}
|
|
30783
|
-
|
|
30784
|
-
|
|
30785
|
-
|
|
30786
|
-
|
|
30787
|
-
|
|
30788
|
-
this.
|
|
30789
|
-
|
|
30790
|
-
|
|
30791
|
-
|
|
30792
|
-
|
|
30793
|
-
|
|
30794
|
-
|
|
30795
|
-
|
|
30796
|
-
|
|
30797
|
-
this.gl.bindVertexArray(this.unusedVAO);
|
|
30798
|
-
} else {
|
|
30799
|
-
this.rectOutlineShader.use(this.gl);
|
|
30800
|
-
this.gl.enable(this.gl.BLEND);
|
|
30801
|
-
this.gl.uniform1f(this.rectOutlineShader.uniforms.thickness, this.opts.selectionBoxLineThickness);
|
|
30802
|
-
this.gl.uniform4fv(this.rectOutlineShader.uniforms.lineColor, lineColor);
|
|
30803
|
-
this.gl.uniform2fv(this.rectOutlineShader.uniforms.canvasWidthHeight, [
|
|
30804
|
-
this.gl.canvas.width,
|
|
30805
|
-
this.gl.canvas.height
|
|
30806
|
-
]);
|
|
30807
|
-
this.gl.uniform4f(
|
|
30808
|
-
this.rectOutlineShader.uniforms.leftTopWidthHeight,
|
|
30809
|
-
leftTopWidthHeight[0],
|
|
30810
|
-
leftTopWidthHeight[1],
|
|
30811
|
-
leftTopWidthHeight[2],
|
|
30812
|
-
leftTopWidthHeight[3]
|
|
30813
|
-
);
|
|
30814
|
-
this.gl.bindVertexArray(this.genericVAO);
|
|
30815
|
-
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
30816
|
-
this.gl.bindVertexArray(this.unusedVAO);
|
|
30817
|
-
}
|
|
30546
|
+
this.rectShader.use(this.gl);
|
|
30547
|
+
this.gl.enable(this.gl.BLEND);
|
|
30548
|
+
this.gl.uniform4fv(this.rectShader.uniforms.lineColor, lineColor);
|
|
30549
|
+
this.gl.uniform2fv(this.rectShader.uniforms.canvasWidthHeight, [this.gl.canvas.width, this.gl.canvas.height]);
|
|
30550
|
+
this.gl.uniform4f(
|
|
30551
|
+
this.rectShader.uniforms.leftTopWidthHeight,
|
|
30552
|
+
leftTopWidthHeight[0],
|
|
30553
|
+
leftTopWidthHeight[1],
|
|
30554
|
+
leftTopWidthHeight[2],
|
|
30555
|
+
leftTopWidthHeight[3]
|
|
30556
|
+
);
|
|
30557
|
+
this.gl.bindVertexArray(this.genericVAO);
|
|
30558
|
+
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
30559
|
+
this.gl.bindVertexArray(this.unusedVAO);
|
|
30818
30560
|
}
|
|
30819
30561
|
drawCircle(leftTopWidthHeight, circleColor = this.opts.fontColor, fillPercent = 1) {
|
|
30820
30562
|
if (!this.circleShader) {
|
|
@@ -33516,14 +33258,10 @@ var Niivue = class {
|
|
|
33516
33258
|
width,
|
|
33517
33259
|
height
|
|
33518
33260
|
]);
|
|
33519
|
-
return;
|
|
33520
33261
|
}
|
|
33521
33262
|
if (this.opts.clickToSegment) {
|
|
33522
33263
|
const x = this.mousePos[0];
|
|
33523
33264
|
const y = this.mousePos[1];
|
|
33524
|
-
if (this.inRenderTile(x, y) >= 0) {
|
|
33525
|
-
return;
|
|
33526
|
-
}
|
|
33527
33265
|
const tileIdx = this.tileIndex(x, y);
|
|
33528
33266
|
if (tileIdx > -1) {
|
|
33529
33267
|
const fovMM = this.screenSlices[tileIdx].fovMM;
|