cloudmr-ux 2.0.7 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -24
- package/dist/CmrComponents/CmrButton/CmrButton.css +0 -0
- package/dist/CmrComponents/CmrButton/CmrButton.d.ts +4 -0
- package/dist/CmrComponents/CmrButton/CmrButton.js +30 -0
- package/dist/CmrComponents/CmrButton/index.d.ts +1 -0
- package/dist/CmrComponents/CmrButton/index.js +1 -0
- package/dist/CmrComponents/CmrCheckbox/CmrCheckbox.css +29 -0
- package/dist/CmrComponents/CmrCheckbox/CmrCheckbox.d.ts +14 -0
- package/dist/CmrComponents/CmrCheckbox/CmrCheckbox.js +30 -0
- package/dist/CmrComponents/CmrCheckbox/index.d.ts +1 -0
- package/dist/CmrComponents/CmrCheckbox/index.js +1 -0
- package/dist/CmrComponents/CmrColorPicker/CmrColorPicker.d.ts +8 -0
- package/dist/CmrComponents/CmrColorPicker/CmrColorPicker.js +29 -0
- package/dist/CmrComponents/CmrColorPicker/CmrColorPicker.scss +27 -0
- package/dist/CmrComponents/CmrInput/CmrInput.css +27 -0
- package/dist/CmrComponents/CmrInput/CmrInput.d.ts +17 -0
- package/dist/CmrComponents/CmrInput/CmrInput.js +29 -0
- package/dist/CmrComponents/CmrInput/index.d.ts +1 -0
- package/dist/CmrComponents/CmrInput/index.js +1 -0
- package/dist/CmrComponents/CmrRadioGroup/CmrRadioGroup.css +25 -0
- package/dist/CmrComponents/CmrRadioGroup/CmrRadioGroup.d.ts +15 -0
- package/dist/CmrComponents/CmrRadioGroup/CmrRadioGroup.js +37 -0
- package/dist/CmrComponents/CmrRadioGroup/index.d.ts +1 -0
- package/dist/CmrComponents/CmrRadioGroup/index.js +1 -0
- package/dist/CmrComponents/CmrSelect/CmrSelect.css +1 -0
- package/dist/CmrComponents/CmrSelect/CmrSelect.d.ts +24 -0
- package/dist/CmrComponents/CmrSelect/CmrSelect.js +46 -0
- package/dist/CmrComponents/CmrSelect/index.d.ts +1 -0
- package/dist/CmrComponents/CmrSelect/index.js +1 -0
- package/dist/CmrComponents/checkbox/Checkbox.css +8 -0
- package/dist/CmrComponents/checkbox/Checkbox.d.ts +15 -0
- package/dist/CmrComponents/checkbox/Checkbox.js +25 -0
- package/dist/CmrComponents/collapse/Collapse.css +3 -0
- package/dist/CmrComponents/collapse/Collapse.d.ts +18 -0
- package/dist/CmrComponents/collapse/Collapse.js +87 -0
- package/dist/CmrComponents/dialogue/Confirmation.d.ts +20 -0
- package/dist/CmrComponents/dialogue/Confirmation.js +36 -0
- package/dist/CmrComponents/dialogue/DeletionDialog.d.ts +4 -0
- package/dist/CmrComponents/dialogue/DeletionDialog.js +39 -0
- package/dist/CmrComponents/dialogue/EditConfirmation.d.ts +13 -0
- package/dist/CmrComponents/dialogue/EditConfirmation.js +45 -0
- package/dist/CmrComponents/double-slider/DualSlider.d.ts +21 -0
- package/dist/CmrComponents/double-slider/DualSlider.js +152 -0
- package/dist/CmrComponents/double-slider/InvertibleDualSlider.d.ts +24 -0
- package/dist/CmrComponents/double-slider/InvertibleDualSlider.js +174 -0
- package/dist/CmrComponents/gui-slider/ControlledSlider.d.ts +9 -0
- package/dist/CmrComponents/gui-slider/ControlledSlider.js +96 -0
- package/dist/CmrComponents/gui-slider/Slider.d.ts +20 -0
- package/dist/CmrComponents/gui-slider/Slider.js +127 -0
- package/dist/CmrComponents/header/Header.d.ts +17 -0
- package/dist/CmrComponents/header/Header.js +90 -0
- package/dist/CmrComponents/header/Header.scss +32 -0
- package/dist/CmrComponents/input-number/InputNumber.css +0 -0
- package/dist/CmrComponents/input-number/InputNumber.d.ts +17 -0
- package/dist/CmrComponents/input-number/InputNumber.js +30 -0
- package/dist/CmrComponents/label/Label.css +13 -0
- package/dist/CmrComponents/label/Label.d.ts +9 -0
- package/dist/CmrComponents/label/Label.js +18 -0
- package/dist/CmrComponents/panel/Panel.css +5 -0
- package/dist/CmrComponents/panel/Panel.d.ts +12 -0
- package/dist/CmrComponents/panel/Panel.js +42 -0
- package/dist/CmrComponents/rename/edit.d.ts +7 -0
- package/dist/CmrComponents/rename/edit.js +117 -0
- package/dist/CmrComponents/select-upload/SelectUpload.css +26 -0
- package/dist/CmrComponents/select-upload/SelectUpload.d.ts +33 -0
- package/dist/CmrComponents/select-upload/SelectUpload.js +90 -0
- package/dist/CmrComponents/tk-dualrange/TKDualRange.d.ts +17 -0
- package/dist/CmrComponents/tk-dualrange/TKDualRange.js +65 -0
- package/dist/CmrComponents/tk-dualrange/tk-dual-range.css +140 -0
- package/dist/CmrComponents/tooltip/Tooltip.css +0 -0
- package/dist/CmrComponents/tooltip/Tooltip.d.ts +18 -0
- package/dist/CmrComponents/tooltip/Tooltip.js +30 -0
- package/dist/CmrComponents/upload/Upload.css +5 -0
- package/dist/CmrComponents/upload/Upload.d.ts +80 -0
- package/dist/CmrComponents/upload/Upload.js +185 -0
- package/dist/CmrComponents/upload/UploadWindow.d.ts +15 -0
- package/dist/CmrComponents/upload/UploadWindow.js +286 -0
- package/dist/CmrTable/CmrTable.css +26 -0
- package/dist/CmrTable/CmrTable.d.ts +13 -0
- package/dist/CmrTable/CmrTable.js +47 -0
- package/dist/CmrTabs/CmrTabs.d.ts +7 -0
- package/dist/CmrTabs/CmrTabs.js +64 -0
- package/dist/CmrTabs/tab.model.d.ts +12 -0
- package/dist/CmrTabs/tab.model.js +1 -0
- package/dist/core/app/main/Main.d.ts +6 -0
- package/dist/core/app/main/Main.js +18 -0
- package/dist/core/app/results/Logs.d.ts +1 -0
- package/dist/core/app/results/Logs.js +33 -0
- package/dist/core/app/results/PreprocessJob.d.ts +1 -0
- package/dist/core/app/results/PreprocessJob.js +100 -0
- package/dist/core/app/results/Results.d.ts +15 -0
- package/dist/core/app/results/Results.js +372 -0
- package/dist/core/app/results/Results.scss +92 -0
- package/dist/core/app/results/Rois.d.ts +11 -0
- package/dist/core/app/results/Rois.js +269 -0
- package/dist/core/app/settings/Settings.d.ts +1 -0
- package/dist/core/app/settings/Settings.js +109 -0
- package/dist/core/app/signin/ForgotPassword.d.ts +3 -0
- package/dist/core/app/signin/ForgotPassword.js +142 -0
- package/dist/core/app/signin/Register.d.ts +3 -0
- package/dist/core/app/signin/Register.js +126 -0
- package/dist/core/app/signin/Signin.d.ts +5 -0
- package/dist/core/app/signin/Signin.js +84 -0
- package/dist/core/app/signin/Signin.scss +86 -0
- package/dist/core/app/upload/Upload.d.ts +3 -0
- package/dist/core/app/upload/Upload.js +261 -0
- package/dist/core/app/upload/Upload.scss +0 -0
- package/dist/core/common/components/CmrColorPicker/CmrColorPicker.d.ts +8 -0
- package/dist/core/common/components/CmrColorPicker/CmrColorPicker.js +29 -0
- package/dist/core/common/components/CmrColorPicker/CmrColorPicker.scss +27 -0
- package/dist/core/common/components/NiivueTools/Niivue.css +8 -0
- package/dist/core/common/components/NiivueTools/Niivue.d.ts +14 -0
- package/dist/core/common/components/NiivueTools/Niivue.js +1270 -0
- package/dist/core/common/components/NiivueTools/NiivuePatcher.js +1875 -0
- package/dist/core/common/components/NiivueTools/components/ColorPicker.d.ts +5 -0
- package/dist/core/common/components/NiivueTools/components/ColorPicker.js +68 -0
- package/dist/core/common/components/NiivueTools/components/DrawPlatte.d.ts +10 -0
- package/dist/core/common/components/NiivueTools/components/DrawPlatte.js +88 -0
- package/dist/core/common/components/NiivueTools/components/DrawToolKit.d.ts +32 -0
- package/dist/core/common/components/NiivueTools/components/DrawToolKit.js +164 -0
- package/dist/core/common/components/NiivueTools/components/EraserPlatte.d.ts +10 -0
- package/dist/core/common/components/NiivueTools/components/EraserPlatte.js +43 -0
- package/dist/core/common/components/NiivueTools/components/Layer.d.ts +10 -0
- package/dist/core/common/components/NiivueTools/components/Layer.js +117 -0
- package/dist/core/common/components/NiivueTools/components/LayersPanel.d.ts +8 -0
- package/dist/core/common/components/NiivueTools/components/LayersPanel.js +108 -0
- package/dist/core/common/components/NiivueTools/components/LocationTable.d.ts +9 -0
- package/dist/core/common/components/NiivueTools/components/LocationTable.js +42 -0
- package/dist/core/common/components/NiivueTools/components/MaskPlatte.d.ts +10 -0
- package/dist/core/common/components/NiivueTools/components/MaskPlatte.js +123 -0
- package/dist/core/common/components/NiivueTools/components/NiivuePanel.d.ts +34 -0
- package/dist/core/common/components/NiivueTools/components/NiivuePanel.js +305 -0
- package/dist/core/common/components/NiivueTools/components/NumberPicker.d.ts +8 -0
- package/dist/core/common/components/NiivueTools/components/NumberPicker.js +40 -0
- package/dist/core/common/components/NiivueTools/components/SettingsPanel.d.ts +7 -0
- package/dist/core/common/components/NiivueTools/components/SettingsPanel.js +30 -0
- package/dist/core/common/components/NiivueTools/components/Switch.d.ts +5 -0
- package/dist/core/common/components/NiivueTools/components/Switch.js +26 -0
- package/dist/core/common/components/NiivueTools/components/Toolbar.d.ts +40 -0
- package/dist/core/common/components/NiivueTools/components/Toolbar.js +184 -0
- package/dist/core/common/components/NiivueTools/components/Toolbar.scss +39 -0
- package/dist/core/common/components/NiivueTools/components/stats.d.ts +2 -0
- package/dist/core/common/components/NiivueTools/components/stats.js +13 -0
- package/dist/core/common/components/NiivueTools/index.css +14 -0
- package/dist/core/common/components/NiivueTools/util.js +309 -0
- package/dist/core/common/components/footer/Footer.d.ts +3 -0
- package/dist/core/common/components/footer/Footer.js +20 -0
- package/dist/core/common/components/footer/Footer.scss +5 -0
- package/dist/core/common/utilities/AuthenticatedRequests.d.ts +16 -0
- package/dist/core/common/utilities/AuthenticatedRequests.js +158 -0
- package/dist/core/common/utilities/CalendarHelper.d.ts +5 -0
- package/dist/core/common/utilities/CalendarHelper.js +27 -0
- package/dist/core/common/utilities/DownloadFromText.d.ts +3 -0
- package/dist/core/common/utilities/DownloadFromText.js +20 -0
- package/dist/core/common/utilities/StoreToRequest.d.ts +1 -0
- package/dist/core/common/utilities/StoreToRequest.js +4 -0
- package/dist/core/common/utilities/SystemUtilities.d.ts +4 -0
- package/dist/core/common/utilities/SystemUtilities.js +79 -0
- package/dist/core/common/utilities/file-transformation/anonymize.d.ts +1 -0
- package/dist/core/common/utilities/file-transformation/anonymize.js +114 -0
- package/dist/core/common/utilities/file-transformation/utilities.d.ts +2 -0
- package/dist/core/common/utilities/file-transformation/utilities.js +23 -0
- package/dist/core/common/utilities/index.d.ts +25 -0
- package/dist/core/common/utilities/index.js +118 -0
- package/dist/core/common/utilities/parse-jwt.d.ts +1 -0
- package/dist/core/common/utilities/parse-jwt.js +14 -0
- package/dist/core/components/PasswordRequirements.d.ts +7 -0
- package/dist/core/components/PasswordRequirements.js +30 -0
- package/dist/core/config/AppConfig.d.ts +5 -0
- package/dist/core/config/AppConfig.js +42 -0
- package/dist/core/config/types.d.ts +40 -0
- package/dist/core/config/types.js +1 -0
- package/dist/core/features/authenticate/authenticateActionCreation.d.ts +46 -0
- package/dist/core/features/authenticate/authenticateActionCreation.js +326 -0
- package/dist/core/features/authenticate/authenticateSlice.d.ts +45 -0
- package/dist/core/features/authenticate/authenticateSlice.js +203 -0
- package/dist/core/features/data/dataActionCreation.d.ts +40 -0
- package/dist/core/features/data/dataActionCreation.js +340 -0
- package/dist/core/features/data/dataSlice.d.ts +37 -0
- package/dist/core/features/data/dataSlice.js +87 -0
- package/dist/core/features/jobs/jobActionCreation.d.ts +35 -0
- package/dist/core/features/jobs/jobActionCreation.js +242 -0
- package/dist/core/features/jobs/jobsSlice.d.ts +57 -0
- package/dist/core/features/jobs/jobsSlice.js +54 -0
- package/dist/core/features/rois/resultActionCreation.d.ts +21 -0
- package/dist/core/features/rois/resultActionCreation.js +114 -0
- package/dist/core/features/rois/resultSlice.d.ts +24 -0
- package/dist/core/features/rois/resultSlice.js +68 -0
- package/dist/core/features/rois/roiTypes.d.ts +44 -0
- package/dist/core/features/rois/roiTypes.js +1 -0
- package/dist/core/features/setup/setupActionCreation.d.ts +7 -0
- package/dist/core/features/setup/setupActionCreation.js +100 -0
- package/dist/core/index.d.ts +22 -0
- package/dist/core/index.js +27 -0
- package/dist/core/store/configureStore.d.ts +13 -0
- package/dist/core/store/configureStore.js +38 -0
- package/dist/core/store/hooks.d.ts +11 -0
- package/dist/core/store/hooks.js +5 -0
- package/dist/core/utils/passwordValidation.d.ts +25 -0
- package/dist/core/utils/passwordValidation.js +19 -0
- package/dist/index.d.ts +29 -329
- package/dist/index.js +26 -1402
- package/dist/style.css +47 -0
- package/package.json +309 -41
- package/dist/index.css +0 -170
- package/dist/index.mjs +0 -1354
|
@@ -0,0 +1,1875 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file patches the original NiiVue library to produce customized behaviors and effects.
|
|
3
|
+
*/
|
|
4
|
+
import { Niivue, NVImage, NVImageFromUrlOptions } from "@niivue/niivue";
|
|
5
|
+
import { mat4, vec3 } from "gl-matrix";
|
|
6
|
+
import { tickSpacing } from "./util";
|
|
7
|
+
|
|
8
|
+
var NiivueObject3D = function (
|
|
9
|
+
id,
|
|
10
|
+
vertexBuffer,
|
|
11
|
+
mode,
|
|
12
|
+
indexCount,
|
|
13
|
+
indexBuffer = null,
|
|
14
|
+
vao = null,
|
|
15
|
+
) {
|
|
16
|
+
this.BLEND = 1;
|
|
17
|
+
this.CULL_FACE = 2;
|
|
18
|
+
this.CULL_FRONT = 4;
|
|
19
|
+
this.CULL_BACK = 8;
|
|
20
|
+
this.ENABLE_DEPTH_TEST = 16;
|
|
21
|
+
this.sphereIdx = [];
|
|
22
|
+
this.sphereVtx = [];
|
|
23
|
+
this.renderShaders = [];
|
|
24
|
+
this.isVisible = true;
|
|
25
|
+
this.isPickable = true;
|
|
26
|
+
this.vertexBuffer = vertexBuffer;
|
|
27
|
+
this.indexCount = indexCount;
|
|
28
|
+
this.indexBuffer = indexBuffer;
|
|
29
|
+
this.vao = vao;
|
|
30
|
+
this.mode = mode;
|
|
31
|
+
this.glFlags = 0;
|
|
32
|
+
this.id = id;
|
|
33
|
+
this.colorId = [
|
|
34
|
+
((id >> 0) & 255) / 255,
|
|
35
|
+
((id >> 8) & 255) / 255,
|
|
36
|
+
((id >> 16) & 255) / 255,
|
|
37
|
+
((id >> 24) & 255) / 255,
|
|
38
|
+
];
|
|
39
|
+
this.modelMatrix = create2();
|
|
40
|
+
this.scale = [1, 1, 1];
|
|
41
|
+
this.position = [0, 0, 0];
|
|
42
|
+
this.rotation = [0, 0, 0];
|
|
43
|
+
this.rotationRadians = 0;
|
|
44
|
+
this.extentsMin = [];
|
|
45
|
+
this.extentsMax = [];
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function create2() {
|
|
49
|
+
var out = new Float32Array(16);
|
|
50
|
+
out[0] = 1;
|
|
51
|
+
out[5] = 1;
|
|
52
|
+
out[10] = 1;
|
|
53
|
+
out[15] = 1;
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function decodeRLE(rle, decodedlen) {
|
|
58
|
+
const r = new Uint8Array(rle.buffer);
|
|
59
|
+
const rI = new Int8Array(r.buffer); // typecast as header can be negative
|
|
60
|
+
let rp = 0; // input position in rle array
|
|
61
|
+
// d: output uncompressed data array
|
|
62
|
+
const d = new Uint8Array(decodedlen);
|
|
63
|
+
let dp = 0; // output position in decoded array
|
|
64
|
+
while (rp < r.length) {
|
|
65
|
+
// read header
|
|
66
|
+
const hdr = rI[rp];
|
|
67
|
+
rp++;
|
|
68
|
+
if (hdr < 0) {
|
|
69
|
+
// write run
|
|
70
|
+
const v = rI[rp];
|
|
71
|
+
rp++;
|
|
72
|
+
for (let i = 0; i < 1 - hdr; i++) {
|
|
73
|
+
d[dp] = v;
|
|
74
|
+
dp++;
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
// write literal
|
|
78
|
+
for (let i = 0; i < hdr + 1; i++) {
|
|
79
|
+
d[dp] = rI[rp];
|
|
80
|
+
rp++;
|
|
81
|
+
dp++;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return d;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function intensityRaw2Scaled(hdr, raw) {
|
|
89
|
+
if (hdr.scl_slope === 0) {
|
|
90
|
+
hdr.scl_slope = 1.0;
|
|
91
|
+
}
|
|
92
|
+
return raw * hdr.scl_slope + hdr.scl_inter;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const SLICE_TYPE = Object.freeze({
|
|
96
|
+
AXIAL: 0,
|
|
97
|
+
CORONAL: 1,
|
|
98
|
+
SAGITTAL: 2,
|
|
99
|
+
MULTIPLANAR: 3,
|
|
100
|
+
RENDER: 4,
|
|
101
|
+
});
|
|
102
|
+
const DRAG_MODE = Object.freeze({
|
|
103
|
+
none: 0,
|
|
104
|
+
contrast: 1,
|
|
105
|
+
measurement: 2,
|
|
106
|
+
pan: 3,
|
|
107
|
+
slicer3D: 4,
|
|
108
|
+
callbackOnly: 5,
|
|
109
|
+
});
|
|
110
|
+
const MULTIPLANAR_TYPE = Object.freeze({
|
|
111
|
+
AUTO: 0,
|
|
112
|
+
COLUMN: 1,
|
|
113
|
+
GRID: 2,
|
|
114
|
+
ROW: 3,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const labelVisibility = {};
|
|
118
|
+
Niivue.prototype.getLabelVisibility = function (label) {
|
|
119
|
+
if (labelVisibility[label] === undefined) {
|
|
120
|
+
labelVisibility[label] = true;
|
|
121
|
+
return true;
|
|
122
|
+
} else {
|
|
123
|
+
return labelVisibility[label];
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
Niivue.prototype.setLabelVisibility = function (label, visible) {
|
|
128
|
+
console.log(label);
|
|
129
|
+
labelVisibility[label] = visible;
|
|
130
|
+
if (
|
|
131
|
+
this.hiddenBitmap === undefined ||
|
|
132
|
+
this.hiddenBitmap === null ||
|
|
133
|
+
this.hiddenBitmap.length === 0
|
|
134
|
+
)
|
|
135
|
+
this.hiddenBitmap = new Uint8Array(this.drawBitmap.length);
|
|
136
|
+
if (!visible) {
|
|
137
|
+
for (let i = 0; i < this.drawBitmap.length; i++) {
|
|
138
|
+
if (this.drawBitmap[i] === label) {
|
|
139
|
+
console.log(this.drawBitmap[i]);
|
|
140
|
+
this.hiddenBitmap[i] = label;
|
|
141
|
+
this.drawBitmap[i] = 0;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
this.refreshDrawing(false);
|
|
145
|
+
} else {
|
|
146
|
+
for (let i = 0; i < this.hiddenBitmap.length; i++) {
|
|
147
|
+
if (this.hiddenBitmap[i] === label) {
|
|
148
|
+
this.hiddenBitmap[i] = 0;
|
|
149
|
+
if (this.drawBitmap[i] === 0) {
|
|
150
|
+
this.drawBitmap[i] = label;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
this.refreshDrawing(false);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// const drawAddUndoBitmap = Niivue.prototype.drawAddUndoBitmap;
|
|
159
|
+
// // This patching adds visibility filtering to drawings
|
|
160
|
+
// Niivue.prototype.drawAddUndoBitmap = async function(){
|
|
161
|
+
// let resp = await drawAddUndoBitmap.call(this);
|
|
162
|
+
// return resp;
|
|
163
|
+
// }
|
|
164
|
+
|
|
165
|
+
// This patch to closeDrawing clears invisible bitmapCache when applied
|
|
166
|
+
const closeDrawing = Niivue.prototype.closeDrawing;
|
|
167
|
+
/**
|
|
168
|
+
* This patch to closeDrawing clears invisible bitmapCache when applied
|
|
169
|
+
*/
|
|
170
|
+
Niivue.prototype.closeDrawing = function () {
|
|
171
|
+
if (this.drawBitmap !== undefined && this.drawBitmap !== null)
|
|
172
|
+
this.hiddenBitmap = new Uint8Array(this.drawBitmap.length);
|
|
173
|
+
else if (this.hiddenBitmap !== undefined) this.hiddenBitmap = [];
|
|
174
|
+
closeDrawing.call(this);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The difference between clear drawing and close drawing is that
|
|
179
|
+
* clear drawing retains itself in the undo stack.
|
|
180
|
+
*/
|
|
181
|
+
Niivue.prototype.clearDrawing = function () {
|
|
182
|
+
if (this.drawBitmap != undefined && this.drawBitmap != null) {
|
|
183
|
+
this.drawBitmap = new Uint8Array(this.drawBitmap.length);
|
|
184
|
+
this.hiddenBitmap = new Uint8Array(this.drawBitmap.length);
|
|
185
|
+
}
|
|
186
|
+
this.drawAddUndoBitmap();
|
|
187
|
+
this.refreshDrawing(true);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
Niivue.prototype.drawSceneCore = function () {
|
|
191
|
+
if (!this.initialized) {
|
|
192
|
+
return; // do not do anything until we are initialized (init will call drawScene).
|
|
193
|
+
}
|
|
194
|
+
this.colorbarHeight = 0;
|
|
195
|
+
this.gl.clearColor(
|
|
196
|
+
this.opts.backColor[0],
|
|
197
|
+
this.opts.backColor[1],
|
|
198
|
+
this.opts.backColor[2],
|
|
199
|
+
this.opts.backColor[3],
|
|
200
|
+
);
|
|
201
|
+
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
|
|
202
|
+
//this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
|
203
|
+
if (this.bmpTexture && this.thumbnailVisible) {
|
|
204
|
+
//draw the thumbnail image and exit
|
|
205
|
+
this.drawThumbnail();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
let posString = "";
|
|
209
|
+
if (
|
|
210
|
+
this.volumes.length === 0 ||
|
|
211
|
+
typeof this.volumes[0].dims === "undefined"
|
|
212
|
+
) {
|
|
213
|
+
if (this.meshes.length > 0) {
|
|
214
|
+
this.screenSlices = []; // empty array
|
|
215
|
+
this.opts.sliceType = SLICE_TYPE.RENDER; //only meshes loaded: we must use 3D render mode
|
|
216
|
+
this.draw3D(); //meshes loaded but no volume
|
|
217
|
+
if (this.opts.isColorbar) this.drawColorbar();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
this.drawLoadingText(this.loadingText);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (!("dims" in this.back)) return;
|
|
224
|
+
if (
|
|
225
|
+
this.uiData.isDragging &&
|
|
226
|
+
this.scene.clipPlaneDepthAziElev[0] < 1.8 &&
|
|
227
|
+
this.inRenderTile(this.uiData.dragStart[0], this.uiData.dragStart[1]) >= 0
|
|
228
|
+
) {
|
|
229
|
+
//user dragging over a 3D rendering
|
|
230
|
+
let x = this.uiData.dragStart[0] - this.uiData.dragEnd[0];
|
|
231
|
+
let y = this.uiData.dragStart[1] - this.uiData.dragEnd[1];
|
|
232
|
+
let depthAziElev = this.uiData.dragClipPlaneStartDepthAziElev.slice();
|
|
233
|
+
depthAziElev[1] -= x;
|
|
234
|
+
depthAziElev[1] = depthAziElev[1] % 360;
|
|
235
|
+
depthAziElev[2] += y;
|
|
236
|
+
if (
|
|
237
|
+
depthAziElev[1] !== this.scene.clipPlaneDepthAziElev[1] ||
|
|
238
|
+
depthAziElev[2] !== this.scene.clipPlaneDepthAziElev[2]
|
|
239
|
+
) {
|
|
240
|
+
this.scene.clipPlaneDepthAziElev = depthAziElev;
|
|
241
|
+
return this.setClipPlane(this.scene.clipPlaneDepthAziElev);
|
|
242
|
+
}
|
|
243
|
+
} //dragging over rendering
|
|
244
|
+
if (
|
|
245
|
+
this.sliceMosaicString.length < 1 &&
|
|
246
|
+
this.opts.sliceType === SLICE_TYPE.RENDER
|
|
247
|
+
) {
|
|
248
|
+
if (this.opts.isColorbar) this.reserveColorbarPanel();
|
|
249
|
+
this.screenSlices = []; // empty array
|
|
250
|
+
this.draw3D();
|
|
251
|
+
if (this.opts.isColorbar) this.drawColorbar();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (this.opts.isColorbar) this.reserveColorbarPanel();
|
|
255
|
+
let maxVols = this.getMaxVols();
|
|
256
|
+
let isDrawGraph =
|
|
257
|
+
this.opts.sliceType === SLICE_TYPE.MULTIPLANAR &&
|
|
258
|
+
maxVols > 1 &&
|
|
259
|
+
this.graph.autoSizeMultiplanar &&
|
|
260
|
+
this.graph.opacity > 0;
|
|
261
|
+
|
|
262
|
+
if (this.sliceMosaicString.length > 0) {
|
|
263
|
+
this.drawMosaic(this.sliceMosaicString);
|
|
264
|
+
} else {
|
|
265
|
+
// issue56 is use mm else use voxel
|
|
266
|
+
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
|
|
267
|
+
this.screenSlices = []; // empty array
|
|
268
|
+
if (this.opts.sliceType === SLICE_TYPE.AXIAL) {
|
|
269
|
+
this.draw2D([0, 0, 0, 0], 0);
|
|
270
|
+
} else if (this.opts.sliceType === SLICE_TYPE.CORONAL) {
|
|
271
|
+
this.draw2D([0, 0, 0, 0], 1);
|
|
272
|
+
} else if (this.opts.sliceType === SLICE_TYPE.SAGITTAL) {
|
|
273
|
+
this.draw2D([0, 0, 0, 0], 2);
|
|
274
|
+
} else {
|
|
275
|
+
//sliceTypeMultiplanar
|
|
276
|
+
let isDrawPenDown =
|
|
277
|
+
isFinite(this.drawPenLocation[0]) && this.opts.drawingEnabled;
|
|
278
|
+
let { volScale } = this.sliceScale();
|
|
279
|
+
// if (typeof this.opts.multiplanarPadPixels !== "number")
|
|
280
|
+
// log.debug("multiplanarPadPixels must be numeric");
|
|
281
|
+
let pad = parseFloat(this.opts.multiplanarPadPixels);
|
|
282
|
+
let vScaleMax = Math.max(volScale[0], volScale[1], volScale[2]);
|
|
283
|
+
// size for 2 rows, 2 columns
|
|
284
|
+
let ltwh2x2 = this.scaleSlice(
|
|
285
|
+
// volScale[0] + volScale[1],
|
|
286
|
+
// volScale[1] + volScale[2],
|
|
287
|
+
vScaleMax * 2,
|
|
288
|
+
vScaleMax * 2,
|
|
289
|
+
pad * 1,
|
|
290
|
+
pad * 1,
|
|
291
|
+
);
|
|
292
|
+
let mx = Math.max(Math.max(volScale[1], volScale[2]), volScale[0]);
|
|
293
|
+
// size for 3 columns and 1 row
|
|
294
|
+
let ltwh3x1 = this.scaleSlice(
|
|
295
|
+
volScale[0] + volScale[0] + volScale[1],
|
|
296
|
+
Math.max(volScale[1], volScale[2]),
|
|
297
|
+
pad * 2,
|
|
298
|
+
);
|
|
299
|
+
// size for 4 columns and 1 row
|
|
300
|
+
let ltwh4x1 = this.scaleSlice(
|
|
301
|
+
volScale[0] + volScale[0] + volScale[1] + mx,
|
|
302
|
+
Math.max(volScale[1], volScale[2]),
|
|
303
|
+
pad * 3,
|
|
304
|
+
);
|
|
305
|
+
// size for 1 column * 3 rows
|
|
306
|
+
let ltwh1x3 = this.scaleSlice(
|
|
307
|
+
mx,
|
|
308
|
+
volScale[1] + volScale[2] + volScale[2],
|
|
309
|
+
0,
|
|
310
|
+
pad * 2,
|
|
311
|
+
);
|
|
312
|
+
// size for 1 column * 4 rows
|
|
313
|
+
let ltwh1x4 = this.scaleSlice(
|
|
314
|
+
mx,
|
|
315
|
+
volScale[1] + volScale[2] + volScale[2] + mx,
|
|
316
|
+
0,
|
|
317
|
+
pad * 3,
|
|
318
|
+
);
|
|
319
|
+
let isDraw3D = !isDrawPenDown && (maxVols < 2 || !isDrawGraph);
|
|
320
|
+
let isDrawColumn = false;
|
|
321
|
+
let isDrawGrid = false;
|
|
322
|
+
let isDrawRow = false;
|
|
323
|
+
if (this.opts.multiplanarLayout == MULTIPLANAR_TYPE.COLUMN)
|
|
324
|
+
isDrawColumn = true;
|
|
325
|
+
else if (this.opts.multiplanarLayout == MULTIPLANAR_TYPE.GRID)
|
|
326
|
+
isDrawGrid = true;
|
|
327
|
+
else if (this.opts.multiplanarLayout == MULTIPLANAR_TYPE.ROW)
|
|
328
|
+
isDrawRow = true;
|
|
329
|
+
else {
|
|
330
|
+
//auto select layout based on canvas size
|
|
331
|
+
if (ltwh1x3[4] > ltwh3x1[4] && ltwh1x3[4] > ltwh2x2[4])
|
|
332
|
+
isDrawColumn = true;
|
|
333
|
+
else if (ltwh3x1[4] > ltwh2x2[4]) isDrawRow = true;
|
|
334
|
+
else isDrawGrid = true;
|
|
335
|
+
}
|
|
336
|
+
if (isDrawColumn) {
|
|
337
|
+
let ltwh = ltwh1x3;
|
|
338
|
+
if (this.opts.multiplanarForceRender || ltwh1x4[4] >= ltwh1x3[4])
|
|
339
|
+
ltwh = ltwh1x4;
|
|
340
|
+
else isDraw3D = false;
|
|
341
|
+
let sX = volScale[0] * ltwh[4];
|
|
342
|
+
let sY = volScale[1] * ltwh[4];
|
|
343
|
+
let sZ = volScale[2] * ltwh[4];
|
|
344
|
+
let sMx = mx * ltwh[4];
|
|
345
|
+
//draw axial
|
|
346
|
+
this.draw2D([ltwh[0], ltwh[1], sX, sY], 0);
|
|
347
|
+
//draw coronal
|
|
348
|
+
this.draw2D([ltwh[0], ltwh[1] + sY + pad, sX, sZ], 1);
|
|
349
|
+
//draw sagittal
|
|
350
|
+
this.draw2D([ltwh[0], ltwh[1] + sY + pad + sZ + pad, sY, sZ], 2);
|
|
351
|
+
if (isDraw3D)
|
|
352
|
+
this.draw3D([ltwh[0], ltwh[1] + sY + sZ + sZ + pad * 3, sMx, sMx]);
|
|
353
|
+
} else if (isDrawRow) {
|
|
354
|
+
let ltwh = ltwh3x1;
|
|
355
|
+
if (this.opts.multiplanarForceRender || ltwh4x1[4] >= ltwh3x1[4])
|
|
356
|
+
ltwh = ltwh4x1;
|
|
357
|
+
else isDraw3D = false;
|
|
358
|
+
let sX = volScale[0] * ltwh[4];
|
|
359
|
+
let sY = volScale[1] * ltwh[4];
|
|
360
|
+
let sZ = volScale[2] * ltwh[4];
|
|
361
|
+
//draw axial
|
|
362
|
+
this.draw2D([ltwh[0], ltwh[1], sX, sY], 0);
|
|
363
|
+
//draw coronal
|
|
364
|
+
this.draw2D([ltwh[0] + sX + pad, ltwh[1], sX, sZ], 1);
|
|
365
|
+
//draw sagittal
|
|
366
|
+
this.draw2D([ltwh[0] + sX + sX + pad * 2, ltwh[1], sY, sZ], 2);
|
|
367
|
+
if (isDraw3D)
|
|
368
|
+
this.draw3D([
|
|
369
|
+
ltwh[0] + sX + sX + sY + pad * 3,
|
|
370
|
+
ltwh[1],
|
|
371
|
+
ltwh[3],
|
|
372
|
+
ltwh[3],
|
|
373
|
+
]);
|
|
374
|
+
} else if (isDrawGrid) {
|
|
375
|
+
let ltwh = ltwh2x2;
|
|
376
|
+
let sX = volScale[0] * ltwh[4];
|
|
377
|
+
let sY = volScale[1] * ltwh[4];
|
|
378
|
+
let sZ = volScale[2] * ltwh[4];
|
|
379
|
+
let sS = vScaleMax * ltwh[4];
|
|
380
|
+
let px = (sS - sX) / 2;
|
|
381
|
+
let py = (sS - sY) / 2;
|
|
382
|
+
let pz = (sS - sZ) / 2;
|
|
383
|
+
//draw axial
|
|
384
|
+
this.draw2D([ltwh[0], ltwh[1] + sZ + pad + pz * 2, sX, sY, sS], 0);
|
|
385
|
+
//draw coronal
|
|
386
|
+
this.draw2D([ltwh[0], ltwh[1], sX, sZ, sS], 1);
|
|
387
|
+
//draw sagittal
|
|
388
|
+
this.draw2D([ltwh[0] + sX + pad + px * 2 + py, ltwh[1], sY, sZ, sS], 2);
|
|
389
|
+
if (isDraw3D)
|
|
390
|
+
this.draw3D([
|
|
391
|
+
ltwh[0] + sX + pad + 2 * px + py,
|
|
392
|
+
ltwh[1] + sZ + pad + 2 * pz + py,
|
|
393
|
+
sY,
|
|
394
|
+
sY,
|
|
395
|
+
]);
|
|
396
|
+
} //if isDrawGrid
|
|
397
|
+
} //if multiplanar
|
|
398
|
+
} //if mosaic not 2D
|
|
399
|
+
if (this.opts.isRuler) this.drawRuler();
|
|
400
|
+
if (this.opts.isColorbar) this.drawColorbar();
|
|
401
|
+
if (isDrawGraph) this.drawGraph();
|
|
402
|
+
if (this.uiData.isDragging) {
|
|
403
|
+
if (this.uiData.mouseButtonCenterDown) {
|
|
404
|
+
this.dragForCenterButton([
|
|
405
|
+
this.uiData.dragStart[0],
|
|
406
|
+
this.uiData.dragStart[1],
|
|
407
|
+
this.uiData.dragEnd[0],
|
|
408
|
+
this.uiData.dragEnd[1],
|
|
409
|
+
]);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
if (this.opts.dragMode === DRAG_MODE.slicer3D) {
|
|
413
|
+
this.dragForSlicer3D([
|
|
414
|
+
this.uiData.dragStart[0],
|
|
415
|
+
this.uiData.dragStart[1],
|
|
416
|
+
this.uiData.dragEnd[0],
|
|
417
|
+
this.uiData.dragEnd[1],
|
|
418
|
+
]);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (this.opts.dragMode === DRAG_MODE.pan) {
|
|
422
|
+
this.dragForPanZoom([
|
|
423
|
+
this.uiData.dragStart[0],
|
|
424
|
+
this.uiData.dragStart[1],
|
|
425
|
+
this.uiData.dragEnd[0],
|
|
426
|
+
this.uiData.dragEnd[1],
|
|
427
|
+
]);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (
|
|
431
|
+
this.inRenderTile(this.uiData.dragStart[0], this.uiData.dragStart[1]) >= 0
|
|
432
|
+
)
|
|
433
|
+
return;
|
|
434
|
+
if (this.opts.dragMode === DRAG_MODE.measurement) {
|
|
435
|
+
//if (this.opts.isDragShowsMeasurementTool) {
|
|
436
|
+
this.drawMeasurementTool([
|
|
437
|
+
this.uiData.dragStart[0],
|
|
438
|
+
this.uiData.dragStart[1],
|
|
439
|
+
this.uiData.dragEnd[0],
|
|
440
|
+
this.uiData.dragEnd[1],
|
|
441
|
+
]);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
let width = Math.abs(this.uiData.dragStart[0] - this.uiData.dragEnd[0]);
|
|
445
|
+
let height = Math.abs(this.uiData.dragStart[1] - this.uiData.dragEnd[1]);
|
|
446
|
+
this.drawSelectionBox([
|
|
447
|
+
Math.min(this.uiData.dragStart[0], this.uiData.dragEnd[0]),
|
|
448
|
+
Math.min(this.uiData.dragStart[1], this.uiData.dragEnd[1]),
|
|
449
|
+
width,
|
|
450
|
+
height,
|
|
451
|
+
]);
|
|
452
|
+
}
|
|
453
|
+
const pos = this.frac2mm([
|
|
454
|
+
this.scene.crosshairPos[0],
|
|
455
|
+
this.scene.crosshairPos[1],
|
|
456
|
+
this.scene.crosshairPos[2],
|
|
457
|
+
]);
|
|
458
|
+
|
|
459
|
+
posString =
|
|
460
|
+
pos[0].toFixed(2) + "×" + pos[1].toFixed(2) + "×" + pos[2].toFixed(2);
|
|
461
|
+
this.readyForSync = true; // by the time we get here, all volumes should be loaded and ready to be drawn. We let other niivue instances know that we can now reliably sync draw calls (images are loaded)
|
|
462
|
+
this.sync();
|
|
463
|
+
return posString;
|
|
464
|
+
}; // drawSceneCore()
|
|
465
|
+
|
|
466
|
+
Niivue.prototype.drawColorbarCore = function (
|
|
467
|
+
layer = 0,
|
|
468
|
+
leftTopWidthHeight = [0, 0, 0, 0],
|
|
469
|
+
isNegativeColor = false,
|
|
470
|
+
min = 0,
|
|
471
|
+
max = 1,
|
|
472
|
+
isAlphaThreshold,
|
|
473
|
+
) {
|
|
474
|
+
if (leftTopWidthHeight[2] <= 0 || leftTopWidthHeight[3] <= 0) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
let txtHt = Math.max(this.opts.textHeight, 0.01);
|
|
478
|
+
txtHt = txtHt * Math.min(this.gl.canvas.height, this.gl.canvas.width);
|
|
479
|
+
let margin = txtHt;
|
|
480
|
+
const fullHt = 3 * txtHt;
|
|
481
|
+
let barHt = txtHt;
|
|
482
|
+
if (leftTopWidthHeight[3] < fullHt) {
|
|
483
|
+
// no space for text
|
|
484
|
+
if (leftTopWidthHeight[3] < 3) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
margin = 1;
|
|
488
|
+
barHt = leftTopWidthHeight[3] - 2;
|
|
489
|
+
}
|
|
490
|
+
this.gl.disable(this.gl.DEPTH_TEST);
|
|
491
|
+
this.colorbarHeight = leftTopWidthHeight[3] + 1;
|
|
492
|
+
const barLTWH = [
|
|
493
|
+
leftTopWidthHeight[0] + margin,
|
|
494
|
+
leftTopWidthHeight[1],
|
|
495
|
+
leftTopWidthHeight[2] - 2 * margin,
|
|
496
|
+
barHt,
|
|
497
|
+
];
|
|
498
|
+
const rimLTWH = [
|
|
499
|
+
barLTWH[0] - 1,
|
|
500
|
+
barLTWH[1] - 1,
|
|
501
|
+
barLTWH[2] + 2,
|
|
502
|
+
barLTWH[3] + 2,
|
|
503
|
+
];
|
|
504
|
+
this.drawRect(rimLTWH, this.opts.crosshairColor);
|
|
505
|
+
|
|
506
|
+
if (!this.colorbarShader) {
|
|
507
|
+
throw new Error("colorbarShader undefined");
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
this.colorbarShader.use(this.gl);
|
|
511
|
+
this.gl.activeTexture(this.gl.TEXTURE1);
|
|
512
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, this.colormapTexture);
|
|
513
|
+
this.gl.texParameteri(
|
|
514
|
+
this.gl.TEXTURE_2D,
|
|
515
|
+
this.gl.TEXTURE_MIN_FILTER,
|
|
516
|
+
this.gl.NEAREST,
|
|
517
|
+
);
|
|
518
|
+
this.gl.texParameteri(
|
|
519
|
+
this.gl.TEXTURE_2D,
|
|
520
|
+
this.gl.TEXTURE_MAG_FILTER,
|
|
521
|
+
this.gl.NEAREST,
|
|
522
|
+
);
|
|
523
|
+
const lx = layer;
|
|
524
|
+
this.gl.uniform1f(this.colorbarShader.uniforms.layer, lx);
|
|
525
|
+
this.gl.uniform2fv(this.colorbarShader.uniforms.canvasWidthHeight, [
|
|
526
|
+
this.gl.canvas.width,
|
|
527
|
+
this.gl.canvas.height,
|
|
528
|
+
]);
|
|
529
|
+
this.gl.disable(this.gl.CULL_FACE);
|
|
530
|
+
if (isNegativeColor) {
|
|
531
|
+
const flip = [barLTWH[0] + barLTWH[2], barLTWH[1], -barLTWH[2], barLTWH[3]];
|
|
532
|
+
this.gl.uniform4fv(this.colorbarShader.uniforms.leftTopWidthHeight, flip);
|
|
533
|
+
} else {
|
|
534
|
+
this.gl.uniform4fv(
|
|
535
|
+
this.colorbarShader.uniforms.leftTopWidthHeight,
|
|
536
|
+
barLTWH,
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
this.gl.bindVertexArray(this.genericVAO);
|
|
540
|
+
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
541
|
+
this.gl.bindVertexArray(this.unusedVAO); // switch off to avoid tampering with settings
|
|
542
|
+
this.gl.texParameteri(
|
|
543
|
+
this.gl.TEXTURE_2D,
|
|
544
|
+
this.gl.TEXTURE_MIN_FILTER,
|
|
545
|
+
this.gl.LINEAR,
|
|
546
|
+
);
|
|
547
|
+
this.gl.texParameteri(
|
|
548
|
+
this.gl.TEXTURE_2D,
|
|
549
|
+
this.gl.TEXTURE_MAG_FILTER,
|
|
550
|
+
this.gl.LINEAR,
|
|
551
|
+
);
|
|
552
|
+
let thresholdTic = 0.0; // only show threshold tickmark in alphaThreshold mode
|
|
553
|
+
if (isAlphaThreshold && max < 0.0 && isNegativeColor) {
|
|
554
|
+
thresholdTic = max;
|
|
555
|
+
max = 0.0;
|
|
556
|
+
} else if (isAlphaThreshold && min > 0.0) {
|
|
557
|
+
thresholdTic = min;
|
|
558
|
+
min = 0.0;
|
|
559
|
+
}
|
|
560
|
+
if (min === max || txtHt < 1) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const range = Math.abs(max - min);
|
|
564
|
+
let [spacing, ticMin] = tickSpacing(min, max);
|
|
565
|
+
if (ticMin < min) {
|
|
566
|
+
ticMin += spacing;
|
|
567
|
+
}
|
|
568
|
+
// determine font size
|
|
569
|
+
function humanize(x) {
|
|
570
|
+
// drop trailing zeros from numerical string
|
|
571
|
+
return x.toFixed(6).replace(/\.?0*$/, "");
|
|
572
|
+
}
|
|
573
|
+
let tic = ticMin;
|
|
574
|
+
const ticLTWH = [0, barLTWH[1] + barLTWH[3] - txtHt * 0.5, 2, txtHt * 0.75];
|
|
575
|
+
const txtTop = ticLTWH[1] + ticLTWH[3];
|
|
576
|
+
const isNeg = 1;
|
|
577
|
+
while (tic <= max) {
|
|
578
|
+
ticLTWH[0] = barLTWH[0] + ((tic - min) / range) * barLTWH[2];
|
|
579
|
+
this.drawRect(ticLTWH);
|
|
580
|
+
const str =
|
|
581
|
+
humanize(isNeg * tic) +
|
|
582
|
+
(this.power
|
|
583
|
+
? `E-${this.power}` +
|
|
584
|
+
(this.transformB && this.transformB !== 0
|
|
585
|
+
? `+${this.transformB}`
|
|
586
|
+
: "")
|
|
587
|
+
: "");
|
|
588
|
+
// if (fntSize > 0)
|
|
589
|
+
this.drawTextBelow([ticLTWH[0], txtTop], str);
|
|
590
|
+
// this.drawTextRight([plotLTWH[0], y], str, fntScale)
|
|
591
|
+
tic += spacing;
|
|
592
|
+
}
|
|
593
|
+
if (thresholdTic !== 0) {
|
|
594
|
+
const tticLTWH = [
|
|
595
|
+
barLTWH[0] + ((thresholdTic - min) / range) * barLTWH[2],
|
|
596
|
+
barLTWH[1] - barLTWH[3] * 0.25,
|
|
597
|
+
2,
|
|
598
|
+
barLTWH[3] * 1.5,
|
|
599
|
+
];
|
|
600
|
+
this.drawRect(tticLTWH);
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
Niivue.prototype.draw2D = function (
|
|
605
|
+
leftTopWidthHeight,
|
|
606
|
+
axCorSag,
|
|
607
|
+
customMM = NaN,
|
|
608
|
+
) {
|
|
609
|
+
let frac2mmTexture = this.volumes[0].frac2mm.slice();
|
|
610
|
+
let screen = this.screenFieldOfViewExtendedMM(axCorSag);
|
|
611
|
+
let mesh2ortho = mat4.create();
|
|
612
|
+
if (!this.opts.isSliceMM) {
|
|
613
|
+
frac2mmTexture = this.volumes[0].frac2mmOrtho.slice();
|
|
614
|
+
mesh2ortho = mat4.clone(this.volumes[0].mm2ortho);
|
|
615
|
+
screen = this.screenFieldOfViewExtendedVox(axCorSag);
|
|
616
|
+
}
|
|
617
|
+
let isRadiolgical =
|
|
618
|
+
this.opts.isRadiologicalConvention && axCorSag < SLICE_TYPE.SAGITTAL;
|
|
619
|
+
if (customMM === Infinity || customMM === -Infinity) {
|
|
620
|
+
isRadiolgical = customMM !== Infinity;
|
|
621
|
+
if (axCorSag === SLICE_TYPE.CORONAL) {
|
|
622
|
+
isRadiolgical = !isRadiolgical;
|
|
623
|
+
}
|
|
624
|
+
} else if (this.opts.sagittalNoseLeft && axCorSag === SLICE_TYPE.SAGITTAL) {
|
|
625
|
+
isRadiolgical = !isRadiolgical;
|
|
626
|
+
}
|
|
627
|
+
let elevation = 0;
|
|
628
|
+
let azimuth = 0;
|
|
629
|
+
if (axCorSag === SLICE_TYPE.SAGITTAL) {
|
|
630
|
+
azimuth = isRadiolgical ? 90 : -90;
|
|
631
|
+
} else if (axCorSag === SLICE_TYPE.CORONAL) {
|
|
632
|
+
azimuth = isRadiolgical ? 180 : 0;
|
|
633
|
+
} else {
|
|
634
|
+
azimuth = isRadiolgical ? 180 : 0;
|
|
635
|
+
elevation = isRadiolgical ? -90 : 90;
|
|
636
|
+
}
|
|
637
|
+
const gl = this.gl;
|
|
638
|
+
if (leftTopWidthHeight[2] === 0 || leftTopWidthHeight[3] === 0) {
|
|
639
|
+
// only one tile: stretch tile to fill whole screen.
|
|
640
|
+
const pixPerMMw = gl.canvas.width / screen.fovMM[0];
|
|
641
|
+
const pixPerMMh = gl.canvas.height / screen.fovMM[1];
|
|
642
|
+
const pixPerMMmin = Math.min(pixPerMMw, pixPerMMh);
|
|
643
|
+
const zoomW = pixPerMMw / pixPerMMmin;
|
|
644
|
+
const zoomH = pixPerMMh / pixPerMMmin;
|
|
645
|
+
screen.fovMM[0] *= zoomW;
|
|
646
|
+
screen.fovMM[1] *= zoomH;
|
|
647
|
+
let center = (screen.mnMM[0] + screen.mxMM[0]) * 0.5;
|
|
648
|
+
screen.mnMM[0] = center - screen.fovMM[0] * 0.5;
|
|
649
|
+
screen.mxMM[0] = center + screen.fovMM[0] * 0.5;
|
|
650
|
+
center = (screen.mnMM[1] + screen.mxMM[1]) * 0.5;
|
|
651
|
+
screen.mnMM[1] = center - screen.fovMM[1] * 0.5;
|
|
652
|
+
screen.mxMM[1] = center + screen.fovMM[1] * 0.5;
|
|
653
|
+
// screen.mnMM[0] *= zoomW;
|
|
654
|
+
// screen.mxMM[0] *= zoomW;
|
|
655
|
+
// screen.mnMM[1] *= zoomH;
|
|
656
|
+
// screen.mxMM[1] *= zoomH;
|
|
657
|
+
leftTopWidthHeight = [0, 0, gl.canvas.width, gl.canvas.height];
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if (leftTopWidthHeight[4] !== undefined) {
|
|
661
|
+
const mx = leftTopWidthHeight[4];
|
|
662
|
+
// only one tile: stretch tile to fill whole screen.
|
|
663
|
+
const pixPerMMw = mx / screen.fovMM[0];
|
|
664
|
+
const pixPerMMh = mx / screen.fovMM[1];
|
|
665
|
+
const pixPerMMmin = Math.min(pixPerMMw, pixPerMMh);
|
|
666
|
+
const zoomW = pixPerMMw / pixPerMMmin;
|
|
667
|
+
const zoomH = pixPerMMh / pixPerMMmin;
|
|
668
|
+
screen.fovMM[0] *= zoomW;
|
|
669
|
+
screen.fovMM[1] *= zoomH;
|
|
670
|
+
let center = (screen.mnMM[0] + screen.mxMM[0]) * 0.5;
|
|
671
|
+
screen.mnMM[0] = center - screen.fovMM[0] * 0.5;
|
|
672
|
+
screen.mxMM[0] = center + screen.fovMM[0] * 0.5;
|
|
673
|
+
center = (screen.mnMM[1] + screen.mxMM[1]) * 0.5;
|
|
674
|
+
screen.mnMM[1] = center - screen.fovMM[1] * 0.5;
|
|
675
|
+
screen.mxMM[1] = center + screen.fovMM[1] * 0.5;
|
|
676
|
+
// screen.mnMM[0] *= zoomW;
|
|
677
|
+
// screen.mxMM[0] *= zoomW;
|
|
678
|
+
// screen.mnMM[1] *= zoomH;
|
|
679
|
+
// screen.mxMM[1] *= zoomH;
|
|
680
|
+
leftTopWidthHeight[2] = mx;
|
|
681
|
+
leftTopWidthHeight[3] = mx;
|
|
682
|
+
}
|
|
683
|
+
// if (leftTopWidthHeight[2] !== leftTopWidthHeight[3]) {
|
|
684
|
+
// const mx = Math.max(leftTopWidthHeight[2],leftTopWidthHeight[3]);
|
|
685
|
+
// // only one tile: stretch tile to fill whole screen.
|
|
686
|
+
// const pixPerMMw = mx / screen.fovMM[0]
|
|
687
|
+
// const pixPerMMh = mx / screen.fovMM[1]
|
|
688
|
+
// const pixPerMMmin = Math.min(pixPerMMw, pixPerMMh)
|
|
689
|
+
// const zoomW = pixPerMMw / pixPerMMmin
|
|
690
|
+
// const zoomH = pixPerMMh / pixPerMMmin
|
|
691
|
+
// screen.fovMM[0] *= zoomW
|
|
692
|
+
// screen.fovMM[1] *= zoomH
|
|
693
|
+
// let center = (screen.mnMM[0] + screen.mxMM[0]) * 0.5
|
|
694
|
+
// screen.mnMM[0] = center - screen.fovMM[0] * 0.5
|
|
695
|
+
// screen.mxMM[0] = center + screen.fovMM[0] * 0.5
|
|
696
|
+
// center = (screen.mnMM[1] + screen.mxMM[1]) * 0.5
|
|
697
|
+
// screen.mnMM[1] = center - screen.fovMM[1] * 0.5
|
|
698
|
+
// screen.mxMM[1] = center + screen.fovMM[1] * 0.5
|
|
699
|
+
// // screen.mnMM[0] *= zoomW;
|
|
700
|
+
// // screen.mxMM[0] *= zoomW;
|
|
701
|
+
// // screen.mnMM[1] *= zoomH;
|
|
702
|
+
// // screen.mxMM[1] *= zoomH;
|
|
703
|
+
// leftTopWidthHeight[2] = mx;
|
|
704
|
+
// leftTopWidthHeight[3] = mx;
|
|
705
|
+
// }
|
|
706
|
+
if (isNaN(customMM)) {
|
|
707
|
+
const pan = this.scene.pan2Dxyzmm;
|
|
708
|
+
const panXY = this.swizzleVec3MM(
|
|
709
|
+
vec3.fromValues(pan[0], pan[1], pan[2]),
|
|
710
|
+
axCorSag,
|
|
711
|
+
);
|
|
712
|
+
const zoom = this.scene.pan2Dxyzmm[3];
|
|
713
|
+
screen.mnMM[0] -= panXY[0];
|
|
714
|
+
screen.mxMM[0] -= panXY[0];
|
|
715
|
+
screen.mnMM[1] -= panXY[1];
|
|
716
|
+
screen.mxMM[1] -= panXY[1];
|
|
717
|
+
screen.mnMM[0] /= zoom;
|
|
718
|
+
screen.mxMM[0] /= zoom;
|
|
719
|
+
screen.mnMM[1] /= zoom;
|
|
720
|
+
screen.mxMM[1] /= zoom;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
let sliceDim = 2; // axial depth is NIfTI k dimension
|
|
724
|
+
if (axCorSag === SLICE_TYPE.CORONAL) {
|
|
725
|
+
sliceDim = 1;
|
|
726
|
+
} // sagittal depth is NIfTI j dimension
|
|
727
|
+
if (axCorSag === SLICE_TYPE.SAGITTAL) {
|
|
728
|
+
sliceDim = 0;
|
|
729
|
+
} // sagittal depth is NIfTI i dimension
|
|
730
|
+
let sliceFrac = this.scene.crosshairPos[sliceDim];
|
|
731
|
+
let mm = this.frac2mm(this.scene.crosshairPos);
|
|
732
|
+
if (!isNaN(customMM) && customMM !== Infinity && customMM !== -Infinity) {
|
|
733
|
+
mm = this.frac2mm([0.5, 0.5, 0.5]);
|
|
734
|
+
mm[sliceDim] = customMM;
|
|
735
|
+
const frac = this.mm2frac(mm);
|
|
736
|
+
sliceFrac = frac[sliceDim];
|
|
737
|
+
}
|
|
738
|
+
const sliceMM = mm[sliceDim];
|
|
739
|
+
gl.clear(gl.DEPTH_BUFFER_BIT);
|
|
740
|
+
let obj = this.calculateMvpMatrix2D(
|
|
741
|
+
leftTopWidthHeight,
|
|
742
|
+
screen.mnMM,
|
|
743
|
+
screen.mxMM,
|
|
744
|
+
Infinity,
|
|
745
|
+
0,
|
|
746
|
+
azimuth,
|
|
747
|
+
elevation,
|
|
748
|
+
isRadiolgical,
|
|
749
|
+
);
|
|
750
|
+
if (customMM === Infinity || customMM === -Infinity) {
|
|
751
|
+
// draw rendering
|
|
752
|
+
const ltwh = leftTopWidthHeight.slice();
|
|
753
|
+
this.draw3D(
|
|
754
|
+
leftTopWidthHeight,
|
|
755
|
+
obj.modelViewProjectionMatrix,
|
|
756
|
+
obj.modelMatrix,
|
|
757
|
+
obj.normalMatrix,
|
|
758
|
+
azimuth,
|
|
759
|
+
elevation,
|
|
760
|
+
);
|
|
761
|
+
const tile = this.screenSlices[this.screenSlices.length - 1];
|
|
762
|
+
// tile.AxyzMxy = this.xyMM2xyzMM(axCorSag, 0.5);
|
|
763
|
+
tile.leftTopWidthHeight = ltwh;
|
|
764
|
+
tile.axCorSag = axCorSag;
|
|
765
|
+
tile.sliceFrac = Infinity; // use infinity to denote this is a rendering, not slice: not one depth
|
|
766
|
+
tile.AxyzMxy = this.xyMM2xyzMM(axCorSag, sliceFrac);
|
|
767
|
+
tile.leftTopMM = obj.leftTopMM;
|
|
768
|
+
tile.fovMM = obj.fovMM;
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
gl.enable(gl.DEPTH_TEST);
|
|
772
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
773
|
+
// draw the slice
|
|
774
|
+
gl.disable(gl.BLEND);
|
|
775
|
+
gl.depthFunc(gl.GREATER);
|
|
776
|
+
gl.disable(gl.CULL_FACE); // show front and back faces
|
|
777
|
+
this.sliceMMShader.use(this.gl);
|
|
778
|
+
gl.uniform1f(
|
|
779
|
+
this.sliceMMShader.uniforms.overlayOutlineWidth,
|
|
780
|
+
this.overlayOutlineWidth,
|
|
781
|
+
);
|
|
782
|
+
gl.uniform1f(
|
|
783
|
+
this.sliceMMShader.uniforms.overlayAlphaShader,
|
|
784
|
+
this.overlayAlphaShader,
|
|
785
|
+
);
|
|
786
|
+
gl.uniform1i(
|
|
787
|
+
this.sliceMMShader.uniforms.isAlphaClipDark,
|
|
788
|
+
this.isAlphaClipDark ? 1 : 0,
|
|
789
|
+
);
|
|
790
|
+
gl.uniform1i(
|
|
791
|
+
this.sliceMMShader.uniforms.backgroundMasksOverlays,
|
|
792
|
+
this.backgroundMasksOverlays,
|
|
793
|
+
);
|
|
794
|
+
gl.uniform1f(this.sliceMMShader.uniforms.drawOpacity, this.drawOpacity);
|
|
795
|
+
gl.enable(gl.BLEND);
|
|
796
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
797
|
+
gl.uniform1f(this.sliceMMShader.uniforms.opacity, this.volumes[0].opacity);
|
|
798
|
+
gl.uniform1i(this.sliceMMShader.uniforms.axCorSag, axCorSag);
|
|
799
|
+
gl.uniform1f(this.sliceMMShader.uniforms.slice, sliceFrac);
|
|
800
|
+
gl.uniformMatrix4fv(
|
|
801
|
+
this.sliceMMShader.uniforms.frac2mm,
|
|
802
|
+
false,
|
|
803
|
+
frac2mmTexture, // this.volumes[0].frac2mm
|
|
804
|
+
);
|
|
805
|
+
gl.uniformMatrix4fv(
|
|
806
|
+
this.sliceMMShader.uniforms.mvpMtx,
|
|
807
|
+
false,
|
|
808
|
+
obj.modelViewProjectionMatrix.slice(),
|
|
809
|
+
);
|
|
810
|
+
gl.bindVertexArray(this.genericVAO); // set vertex attributes
|
|
811
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
812
|
+
gl.bindVertexArray(this.unusedVAO); // set vertex attributes
|
|
813
|
+
// record screenSlices to detect mouse click positions
|
|
814
|
+
this.screenSlices.push({
|
|
815
|
+
leftTopWidthHeight,
|
|
816
|
+
axCorSag,
|
|
817
|
+
sliceFrac,
|
|
818
|
+
AxyzMxy: this.xyMM2xyzMM(axCorSag, sliceFrac),
|
|
819
|
+
leftTopMM: obj.leftTopMM,
|
|
820
|
+
screen2frac: [],
|
|
821
|
+
fovMM: obj.fovMM,
|
|
822
|
+
});
|
|
823
|
+
if (isNaN(customMM)) {
|
|
824
|
+
// draw crosshairs
|
|
825
|
+
this.drawCrosshairs3D(
|
|
826
|
+
true,
|
|
827
|
+
1.0,
|
|
828
|
+
obj.modelViewProjectionMatrix,
|
|
829
|
+
true,
|
|
830
|
+
this.opts.isSliceMM,
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
if (this.opts.meshThicknessOn2D > 0.0) {
|
|
834
|
+
if (this.opts.meshThicknessOn2D !== Infinity) {
|
|
835
|
+
obj = this.calculateMvpMatrix2D(
|
|
836
|
+
leftTopWidthHeight,
|
|
837
|
+
screen.mnMM,
|
|
838
|
+
screen.mxMM,
|
|
839
|
+
this.opts.meshThicknessOn2D,
|
|
840
|
+
sliceMM,
|
|
841
|
+
azimuth,
|
|
842
|
+
elevation,
|
|
843
|
+
isRadiolgical,
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
// we may need to transform mesh vertices to the orthogonal voxel space
|
|
847
|
+
const mx = mat4.clone(obj.modelViewProjectionMatrix);
|
|
848
|
+
mat4.multiply(mx, mx, mesh2ortho);
|
|
849
|
+
this.drawMesh3D(
|
|
850
|
+
true,
|
|
851
|
+
1,
|
|
852
|
+
mx, // obj.modelViewProjectionMatrix,
|
|
853
|
+
obj.modelMatrix,
|
|
854
|
+
obj.normalMatrix,
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
if (isNaN(customMM)) {
|
|
858
|
+
// no crossbars for mosaic view
|
|
859
|
+
this.drawCrosshairs3D(
|
|
860
|
+
false,
|
|
861
|
+
0.15,
|
|
862
|
+
obj.modelViewProjectionMatrix,
|
|
863
|
+
true,
|
|
864
|
+
this.opts.isSliceMM,
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
this.drawSliceOrientationText(leftTopWidthHeight, axCorSag);
|
|
868
|
+
this.readyForSync = true;
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
// not included in public docs
|
|
872
|
+
// set color of single voxel in drawing
|
|
873
|
+
// Include thickness in opts
|
|
874
|
+
/**
|
|
875
|
+
* Pen bounds specify the padding added
|
|
876
|
+
* around the drawing center
|
|
877
|
+
* @param x
|
|
878
|
+
* @param y
|
|
879
|
+
* @param z
|
|
880
|
+
* @param penValue
|
|
881
|
+
*/
|
|
882
|
+
Niivue.prototype.drawPt = function (x, y, z, penValue) {
|
|
883
|
+
const penBounds = this.opts.penBounds ? this.opts.penBounds : 0;
|
|
884
|
+
const dx = this.back.dims[1];
|
|
885
|
+
const dy = this.back.dims[2];
|
|
886
|
+
const dz = this.back.dims[3];
|
|
887
|
+
//Sweep through cubic area, filter by radius
|
|
888
|
+
for (let i = x - penBounds; i <= x + penBounds; i++) {
|
|
889
|
+
for (let j = y - penBounds; j <= y + penBounds; j++) {
|
|
890
|
+
for (let k = z - penBounds; k <= z + penBounds; k++) {
|
|
891
|
+
// (penBounds+1)*(penBounds) as radius filter makes better circles in discrete case
|
|
892
|
+
if (
|
|
893
|
+
(i - x) * (i - x) + (j - y) * (j - y) + (k - z) * (k - z) <=
|
|
894
|
+
(penBounds + 1) * penBounds
|
|
895
|
+
) {
|
|
896
|
+
let xn = Math.min(Math.max(i, 0), dx - 1);
|
|
897
|
+
let yn = Math.min(Math.max(j, 0), dy - 1);
|
|
898
|
+
let zn = Math.min(Math.max(k, 0), dz - 1);
|
|
899
|
+
this.drawBitmap[xn + yn * dx + zn * dx * dy] = penValue;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
// not included in public docs
|
|
907
|
+
// given series of line segments, connect first and last
|
|
908
|
+
// voxel and fill the interior of the line segments
|
|
909
|
+
// yuelong: fill volumetric interior of the paint space,
|
|
910
|
+
// if active draw pen set to invisible
|
|
911
|
+
Niivue.prototype.drawPenFilled = function () {
|
|
912
|
+
const nPts = this.drawPenFillPts.length;
|
|
913
|
+
if (nPts < 2) {
|
|
914
|
+
// can not fill single line
|
|
915
|
+
this.drawPenFillPts = [];
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
// do fill in 2D, based on axial (0), coronal (1) or sagittal drawing (2
|
|
919
|
+
const axCorSag = this.drawPenAxCorSag;
|
|
920
|
+
// axial is x(0)*y(1) horizontal*vertical
|
|
921
|
+
let h = 0;
|
|
922
|
+
let v = 1;
|
|
923
|
+
if (axCorSag === 1) {
|
|
924
|
+
v = 2;
|
|
925
|
+
} // coronal is x(0)*z(0)
|
|
926
|
+
if (axCorSag === 2) {
|
|
927
|
+
// sagittal is y(1)*z(2)
|
|
928
|
+
h = 1;
|
|
929
|
+
v = 2;
|
|
930
|
+
}
|
|
931
|
+
const penBounds = this.opts.penBounds ? this.opts.penBounds : 0;
|
|
932
|
+
const w = penBounds * 2 + 1; // Yuelong: paint fill with thickness
|
|
933
|
+
const dims3D = [this.back.dims[h + 1], this.back.dims[v + 1], w]; // +1: dims indexed from 0!
|
|
934
|
+
// create bitmap of horizontal*vertical voxels:
|
|
935
|
+
const img3D = new Uint8Array(dims3D[0] * dims3D[1] * dims3D[2]);
|
|
936
|
+
let pen = 1; // do not use this.opts.penValue, as "erase" is zero
|
|
937
|
+
function drawPt3D(x, y, penValue) {
|
|
938
|
+
const dx = dims3D[0];
|
|
939
|
+
const dy = dims3D[1];
|
|
940
|
+
const dz = w;
|
|
941
|
+
const z = (w - 1) / 2;
|
|
942
|
+
//Sweep through cubic area, filter by radius
|
|
943
|
+
for (let i = x - penBounds; i <= x + penBounds; i++) {
|
|
944
|
+
for (let j = y - penBounds; j <= y + penBounds; j++) {
|
|
945
|
+
for (let k = z - penBounds; k <= z + penBounds; k++) {
|
|
946
|
+
// (penBounds+1)*(penBounds) as radius filter makes better circles in discrete case
|
|
947
|
+
if (
|
|
948
|
+
(i - x) * (i - x) + (j - y) * (j - y) + (k - z) * (k - z) <=
|
|
949
|
+
(penBounds + 1) * penBounds
|
|
950
|
+
) {
|
|
951
|
+
let xn = Math.min(Math.max(i, 0), dx - 1);
|
|
952
|
+
let yn = Math.min(Math.max(j, 0), dy - 1);
|
|
953
|
+
let zn = Math.min(Math.max(k, 0), dz - 1);
|
|
954
|
+
img3D[xn + yn * dx + zn * dx * dy] = penValue;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
function drawLine2D(ptA, ptB /* penValue */) {
|
|
961
|
+
const dx = Math.abs(ptA[0] - ptB[0]);
|
|
962
|
+
const dy = Math.abs(ptA[1] - ptB[1]);
|
|
963
|
+
// img2D[ptA[0] + ptA[1] * dims2D[0]] = pen
|
|
964
|
+
// img2D[ptB[0] + ptB[1] * dims2D[0]] = pen
|
|
965
|
+
drawPt3D(ptA[0], ptA[1], pen);
|
|
966
|
+
drawPt3D(ptB[0], ptB[1], pen);
|
|
967
|
+
let xs = -1;
|
|
968
|
+
let ys = -1;
|
|
969
|
+
if (ptB[0] > ptA[0]) {
|
|
970
|
+
xs = 1;
|
|
971
|
+
}
|
|
972
|
+
if (ptB[1] > ptA[1]) {
|
|
973
|
+
ys = 1;
|
|
974
|
+
}
|
|
975
|
+
let x1 = ptA[0];
|
|
976
|
+
let y1 = ptA[1];
|
|
977
|
+
const x2 = ptB[0];
|
|
978
|
+
const y2 = ptB[1];
|
|
979
|
+
if (dx >= dy) {
|
|
980
|
+
// Driving axis is X-axis"
|
|
981
|
+
let p1 = 2 * dy - dx;
|
|
982
|
+
while (x1 !== x2) {
|
|
983
|
+
x1 += xs;
|
|
984
|
+
if (p1 >= 0) {
|
|
985
|
+
y1 += ys;
|
|
986
|
+
p1 -= 2 * dx;
|
|
987
|
+
}
|
|
988
|
+
p1 += 2 * dy;
|
|
989
|
+
drawPt3D(x1, y1, pen);
|
|
990
|
+
}
|
|
991
|
+
} else {
|
|
992
|
+
// Driving axis is Y-axis"
|
|
993
|
+
let p1 = 2 * dx - dy;
|
|
994
|
+
while (y1 !== y2) {
|
|
995
|
+
y1 += ys;
|
|
996
|
+
if (p1 >= 0) {
|
|
997
|
+
x1 += xs;
|
|
998
|
+
p1 -= 2 * dy;
|
|
999
|
+
}
|
|
1000
|
+
p1 += 2 * dx;
|
|
1001
|
+
drawPt3D(x1, y1, pen);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
const startPt = [this.drawPenFillPts[0][h], this.drawPenFillPts[0][v]];
|
|
1006
|
+
let prevPt = startPt;
|
|
1007
|
+
for (let i = 1; i < nPts; i++) {
|
|
1008
|
+
const pt = [this.drawPenFillPts[i][h], this.drawPenFillPts[i][v]];
|
|
1009
|
+
drawLine2D(prevPt, pt);
|
|
1010
|
+
prevPt = pt;
|
|
1011
|
+
}
|
|
1012
|
+
drawLine2D(startPt, prevPt); // close drawing
|
|
1013
|
+
// flood fill
|
|
1014
|
+
const seeds = [];
|
|
1015
|
+
function setSeed(pt) {
|
|
1016
|
+
// pt 2D -> 3D
|
|
1017
|
+
if (
|
|
1018
|
+
pt[0] < 0 ||
|
|
1019
|
+
pt[1] < 0 ||
|
|
1020
|
+
pt[2] < 0 ||
|
|
1021
|
+
pt[0] >= dims3D[0] ||
|
|
1022
|
+
pt[1] >= dims3D[1] ||
|
|
1023
|
+
pt[3] >= dims3D[2]
|
|
1024
|
+
) {
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
const pxl = pt[0] + pt[1] * dims3D[0] + pt[2] * dims3D[0] * dims3D[1];
|
|
1028
|
+
if (img3D[pxl] !== 0) {
|
|
1029
|
+
return;
|
|
1030
|
+
} // not blank
|
|
1031
|
+
seeds.push(pt);
|
|
1032
|
+
img3D[pxl] = 2;
|
|
1033
|
+
}
|
|
1034
|
+
// https://en.wikipedia.org/wiki/Flood_fill
|
|
1035
|
+
// first seed all edges
|
|
1036
|
+
// // bottom row
|
|
1037
|
+
// for (let i = 0; i < dims2D[0]; i++) {
|
|
1038
|
+
// setSeed([i, 0])
|
|
1039
|
+
// }
|
|
1040
|
+
// // top row
|
|
1041
|
+
// for (let i = 0; i < dims2D[0]; i++) {
|
|
1042
|
+
// setSeed([i, dims2D[1] - 1])
|
|
1043
|
+
// }
|
|
1044
|
+
// // left column
|
|
1045
|
+
// for (let i = 0; i < dims2D[1]; i++) {
|
|
1046
|
+
// setSeed([0, i])
|
|
1047
|
+
// }
|
|
1048
|
+
// // right columns
|
|
1049
|
+
// for (let i = 0; i < dims2D[1]; i++) {
|
|
1050
|
+
// setSeed([dims2D[0] - 1, i])
|
|
1051
|
+
// }
|
|
1052
|
+
// Yuelong: Instead of seeding all edges, seed eight corners of the bounding box,
|
|
1053
|
+
setSeed([0, 0, 0]);
|
|
1054
|
+
setSeed([dims3D[0] - 1, 0, 0]);
|
|
1055
|
+
setSeed([0, dims3D[1] - 1, 0]);
|
|
1056
|
+
setSeed([dims3D[0] - 1, dims3D[1] - 1, 0]);
|
|
1057
|
+
setSeed([0, 0, dims3D[2] - 1]);
|
|
1058
|
+
setSeed([dims3D[0] - 1, 0, dims3D[2] - 1]);
|
|
1059
|
+
setSeed([0, dims3D[1] - 1, dims3D[2] - 1]);
|
|
1060
|
+
setSeed([dims3D[0] - 1, dims3D[1] - 1, dims3D[2] - 1]);
|
|
1061
|
+
// now retire first in first out
|
|
1062
|
+
while (seeds.length > 0) {
|
|
1063
|
+
// always remove one seed, plant 0..4 new ones
|
|
1064
|
+
const seed = seeds.shift();
|
|
1065
|
+
setSeed([seed[0] - 1, seed[1], seed[2]]);
|
|
1066
|
+
setSeed([seed[0] + 1, seed[1], seed[2]]);
|
|
1067
|
+
setSeed([seed[0], seed[1] - 1, seed[2]]);
|
|
1068
|
+
setSeed([seed[0], seed[1] + 1, seed[2]]);
|
|
1069
|
+
// Yuelong: flood fill z-axis as well
|
|
1070
|
+
setSeed([seed[0], seed[1], seed[2] + 1]);
|
|
1071
|
+
setSeed([seed[0], seed[1], seed[2] - 1]);
|
|
1072
|
+
}
|
|
1073
|
+
// all voxels with value of zero have no path to edges
|
|
1074
|
+
// insert surviving pixels from 2D bitmap into 3D bitmap
|
|
1075
|
+
pen = this.opts.penValue;
|
|
1076
|
+
const slice = this.drawPenFillPts[0][3 - (h + v)];
|
|
1077
|
+
// if (axCorSag === 0) {
|
|
1078
|
+
// // axial
|
|
1079
|
+
// const offset = slice * dims2D[0] * dims2D[1]
|
|
1080
|
+
// for (let i = 0; i < dims2D[0] * dims2D[1]; i++) {
|
|
1081
|
+
// if (img2D[i] !== 2) {
|
|
1082
|
+
// this.drawBitmap[i + offset] = pen
|
|
1083
|
+
// }
|
|
1084
|
+
// }
|
|
1085
|
+
// } else {
|
|
1086
|
+
// let xStride = 1 // coronal: horizontal LR pixels contiguous
|
|
1087
|
+
// const yStride = this.back.dims[1] * this.back.dims[2] // coronal: vertical is slice
|
|
1088
|
+
// let zOffset = slice * this.back.dims[1] // coronal: slice is number of columns
|
|
1089
|
+
// if (axCorSag === 2) {
|
|
1090
|
+
// // sagittal
|
|
1091
|
+
// xStride = this.back.dims[1]
|
|
1092
|
+
// zOffset = slice
|
|
1093
|
+
// }
|
|
1094
|
+
// let i = 0
|
|
1095
|
+
// for (let y = 0; y < dims2D[1]; y++) {
|
|
1096
|
+
// for (let x = 0; x < dims2D[0]; x++) {
|
|
1097
|
+
// if (img2D[i] !== 2) {
|
|
1098
|
+
// this.drawBitmap[x * xStride + y * yStride + zOffset] = pen
|
|
1099
|
+
// }
|
|
1100
|
+
// i++
|
|
1101
|
+
// }
|
|
1102
|
+
// }
|
|
1103
|
+
// }
|
|
1104
|
+
// Stride with permutation symmetry
|
|
1105
|
+
let strides = [1, this.back.dims[1], this.back.dims[1] * this.back.dims[2]];
|
|
1106
|
+
// xStride = s0 for axial and coronal, s1 for sagital
|
|
1107
|
+
let xStride = axCorSag == 2 ? strides[1] : strides[0];
|
|
1108
|
+
// yStride = s1 for axial, s2 for coronal and sagital
|
|
1109
|
+
const yStride = axCorSag == 0 ? strides[1] : strides[2];
|
|
1110
|
+
// zStride = s2 for axial, s1 for coronal, s0 for sagital
|
|
1111
|
+
const zStride = strides[2 - axCorSag];
|
|
1112
|
+
const zOffset = slice * zStride;
|
|
1113
|
+
let i = 0;
|
|
1114
|
+
for (let z = -penBounds; z <= penBounds; z++) {
|
|
1115
|
+
for (let y = 0; y < dims3D[1]; y++) {
|
|
1116
|
+
for (let x = 0; x < dims3D[0]; x++) {
|
|
1117
|
+
if (img3D[i] !== 2) {
|
|
1118
|
+
// Fill by 3D traversal
|
|
1119
|
+
this.drawBitmap[x * xStride + y * yStride + zOffset + z * zStride] =
|
|
1120
|
+
pen;
|
|
1121
|
+
}
|
|
1122
|
+
i++;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
// this.drawUndoBitmaps[this.currentDrawUndoBitmap]
|
|
1127
|
+
if (
|
|
1128
|
+
!this.drawFillOverwrites &&
|
|
1129
|
+
this.drawUndoBitmaps[this.currentDrawUndoBitmap].length > 0
|
|
1130
|
+
) {
|
|
1131
|
+
const nv = this.drawBitmap.length;
|
|
1132
|
+
const bmp = decodeRLE(this.drawUndoBitmaps[this.currentDrawUndoBitmap], nv);
|
|
1133
|
+
for (let i = 0; i < nv; i++) {
|
|
1134
|
+
if (bmp[i] === 0) {
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
this.drawBitmap[i] = bmp[i];
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
this.drawPenFillPts = [];
|
|
1141
|
+
// First imprint all hiddenBitmaps into the draw bitmap,
|
|
1142
|
+
// visible voxels take precedence
|
|
1143
|
+
if (this.hiddenBitmap)
|
|
1144
|
+
this.hiddenBitmap.map((value, index) => {
|
|
1145
|
+
if (value !== 0 && this.drawBitmap[index] === 0) {
|
|
1146
|
+
this.drawBitmap[index] = value;
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1149
|
+
this.drawAddUndoBitmap();
|
|
1150
|
+
// Post-processing to hide hidden voxels
|
|
1151
|
+
this.hiddenBitmap = new Uint8Array(this.drawBitmap.length);
|
|
1152
|
+
for (let i = 0; i < this.drawBitmap.length; i++) {
|
|
1153
|
+
let pen = this.drawBitmap[i];
|
|
1154
|
+
if (!this.getLabelVisibility(pen)) {
|
|
1155
|
+
this.hiddenBitmap[i] = this.drawBitmap[i];
|
|
1156
|
+
this.drawBitmap[i] = 0;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
this.refreshDrawing(false);
|
|
1160
|
+
};
|
|
1161
|
+
|
|
1162
|
+
Niivue.prototype.fillRange = function (
|
|
1163
|
+
min,
|
|
1164
|
+
max,
|
|
1165
|
+
penValue,
|
|
1166
|
+
inverted = false,
|
|
1167
|
+
original = undefined,
|
|
1168
|
+
setOriginal = (original) => {},
|
|
1169
|
+
) {
|
|
1170
|
+
console.log(this.volumes);
|
|
1171
|
+
let volume = this.volumes[0];
|
|
1172
|
+
if (volume == undefined) {
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
if (!this.drawBitmap) {
|
|
1176
|
+
this.createEmptyDrawing();
|
|
1177
|
+
}
|
|
1178
|
+
// First load underlying imprinting
|
|
1179
|
+
if (original == undefined) {
|
|
1180
|
+
setOriginal([...this.drawBitmap]);
|
|
1181
|
+
} else {
|
|
1182
|
+
this.drawBitmap = new Uint8Array(original);
|
|
1183
|
+
}
|
|
1184
|
+
console.log(volume.img.length);
|
|
1185
|
+
console.log(this.drawBitmap.length);
|
|
1186
|
+
// Next write into the drawbitmap where voxel value are within range,
|
|
1187
|
+
// no need to write into hidden bitmap specifically due to the post-processing
|
|
1188
|
+
// step, where draw bitmap values will be hidden accordingly
|
|
1189
|
+
|
|
1190
|
+
const dx = this.back.dims[1];
|
|
1191
|
+
const dy = this.back.dims[2];
|
|
1192
|
+
const dz = this.back.dims[3];
|
|
1193
|
+
for (let x = 0; x < dx; x++)
|
|
1194
|
+
for (let y = 0; y < dy; y++)
|
|
1195
|
+
for (let z = 0; z < dz; z++) {
|
|
1196
|
+
let i = x + y * dx + z * dx * dy;
|
|
1197
|
+
let val = volume.getValue(x, y, z);
|
|
1198
|
+
if (
|
|
1199
|
+
(!inverted && min <= val && max >= val) ||
|
|
1200
|
+
//Note here that e-4 is not a trivial value,
|
|
1201
|
+
// we can do this here because ranges beneath e-4 will be rescaled inside
|
|
1202
|
+
// the checkRange function inside Niivue.jsx. It is still not entirely safe
|
|
1203
|
+
// but a necessary step for inclusive range checking under float inprecisions
|
|
1204
|
+
(inverted && (min >= val || max < val))
|
|
1205
|
+
) {
|
|
1206
|
+
// console.log('filling');
|
|
1207
|
+
this.drawBitmap[i] = penValue;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
// Next update drawUndoBitmaps pipeline
|
|
1211
|
+
// First imprint all hiddenBitmaps into the draw bitmap,
|
|
1212
|
+
// visible voxels take precedence
|
|
1213
|
+
if (this.hiddenBitmap)
|
|
1214
|
+
this.hiddenBitmap.map((value, index) => {
|
|
1215
|
+
if (value !== 0 && this.drawBitmap[index] === 0) {
|
|
1216
|
+
this.drawBitmap[index] = value;
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
// Post-processing to hide invisible voxels
|
|
1220
|
+
this.hiddenBitmap = new Uint8Array(this.drawBitmap.length);
|
|
1221
|
+
for (let i = 0; i < this.drawBitmap.length; i++) {
|
|
1222
|
+
let pen = this.drawBitmap[i];
|
|
1223
|
+
if (!this.getLabelVisibility(pen)) {
|
|
1224
|
+
this.hiddenBitmap[i] = this.drawBitmap[i];
|
|
1225
|
+
this.drawBitmap[i] = 0;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
this.refreshDrawing(true);
|
|
1229
|
+
// props.nv.drawScene()
|
|
1230
|
+
};
|
|
1231
|
+
|
|
1232
|
+
Niivue.prototype.drawAddUndoBitmapWithHiddenVoxels = function () {
|
|
1233
|
+
// Next update drawUndoBitmaps pipeline
|
|
1234
|
+
// First imprint all hiddenBitmaps into the draw bitmap,
|
|
1235
|
+
// visible voxels take precedence
|
|
1236
|
+
if (this.hiddenBitmap)
|
|
1237
|
+
this.hiddenBitmap.map((value, index) => {
|
|
1238
|
+
if (value !== 0 && this.drawBitmap[index] === 0) {
|
|
1239
|
+
this.drawBitmap[index] = value;
|
|
1240
|
+
}
|
|
1241
|
+
});
|
|
1242
|
+
this.drawAddUndoBitmap();
|
|
1243
|
+
// Post-processing to hide invisible voxels
|
|
1244
|
+
this.hiddenBitmap = new Uint8Array(this.drawBitmap.length);
|
|
1245
|
+
for (let i = 0; i < this.drawBitmap.length; i++) {
|
|
1246
|
+
let pen = this.drawBitmap[i];
|
|
1247
|
+
if (!this.getLabelVisibility(pen)) {
|
|
1248
|
+
this.hiddenBitmap[i] = this.drawBitmap[i];
|
|
1249
|
+
this.drawBitmap[i] = 0;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
|
|
1254
|
+
/**
|
|
1255
|
+
* Restore drawing to previous state
|
|
1256
|
+
* @example niivue.drawUndo();
|
|
1257
|
+
* @see {@link https://niivue.github.io/niivue/features/draw.ui.html|live demo usage}
|
|
1258
|
+
* Yuelong: drawundo hides invisible rois in post processing
|
|
1259
|
+
*/
|
|
1260
|
+
Niivue.prototype.drawUndo = function () {
|
|
1261
|
+
if (this.drawUndoBitmaps.length < 1) {
|
|
1262
|
+
console.debug("undo bitmaps not loaded");
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
this.currentDrawUndoBitmap--;
|
|
1266
|
+
if (this.currentDrawUndoBitmap < 0) {
|
|
1267
|
+
this.currentDrawUndoBitmap = this.drawUndoBitmaps.length - 1;
|
|
1268
|
+
}
|
|
1269
|
+
if (this.currentDrawUndoBitmap >= this.drawUndoBitmaps.length) {
|
|
1270
|
+
this.currentDrawUndoBitmap = 0;
|
|
1271
|
+
}
|
|
1272
|
+
if (this.drawUndoBitmaps[this.currentDrawUndoBitmap].length < 2) {
|
|
1273
|
+
console.debug("drawUndo is misbehaving");
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
this.drawBitmap = decodeRLE(
|
|
1277
|
+
this.drawUndoBitmaps[this.currentDrawUndoBitmap],
|
|
1278
|
+
this.drawBitmap.length,
|
|
1279
|
+
);
|
|
1280
|
+
// Post-processing to hide invisible region and reveal hidden ones
|
|
1281
|
+
this.hiddenBitmap = new Uint8Array(this.drawBitmap.length);
|
|
1282
|
+
for (let i = 0; i < this.drawBitmap.length; i++) {
|
|
1283
|
+
let pen = this.drawBitmap[i];
|
|
1284
|
+
if (!this.getLabelVisibility(pen)) {
|
|
1285
|
+
this.hiddenBitmap[i] = this.drawBitmap[i];
|
|
1286
|
+
this.drawBitmap[i] = 0;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
this.refreshDrawing(true);
|
|
1290
|
+
};
|
|
1291
|
+
|
|
1292
|
+
/**
|
|
1293
|
+
* save voxel-based image to disk
|
|
1294
|
+
* @param {string} fnm filename of NIfTI image to create
|
|
1295
|
+
* @param {boolean} [false] isSaveDrawing determines whether drawing or background image is saved
|
|
1296
|
+
* @param {number} [0] volumeByIndex determines layer to save (0 for background)
|
|
1297
|
+
* @param {number} [0] volumeByIndex determines layer to save (0 for background)
|
|
1298
|
+
* @example niivue.saveImage('test.nii', true);
|
|
1299
|
+
* @see {@link https://niivue.github.io/niivue/features/draw.ui.html|live demo usage}
|
|
1300
|
+
*/
|
|
1301
|
+
Niivue.prototype.saveImageByLabels = async function (fnm, labels = [1]) {
|
|
1302
|
+
if (this.back.dims === undefined) {
|
|
1303
|
+
console.debug("No voxelwise image open");
|
|
1304
|
+
return false;
|
|
1305
|
+
}
|
|
1306
|
+
if (!this.drawBitmap) {
|
|
1307
|
+
console.debug("No drawing open");
|
|
1308
|
+
return false;
|
|
1309
|
+
}
|
|
1310
|
+
const perm = this.volumes[0].permRAS;
|
|
1311
|
+
if (perm[0] === 1 && perm[1] === 2 && perm[2] === 3) {
|
|
1312
|
+
await this.volumes[0].saveToDisk(fnm, this.drawBitmap); // createEmptyDrawing
|
|
1313
|
+
return true;
|
|
1314
|
+
} else {
|
|
1315
|
+
const dims = this.volumes[0].hdr.dims; // reverse to original
|
|
1316
|
+
// reverse RAS to native space, layout is mrtrix MIF format
|
|
1317
|
+
// for details see NVImage.readMIF()
|
|
1318
|
+
const layout = [0, 0, 0];
|
|
1319
|
+
for (let i = 0; i < 3; i++) {
|
|
1320
|
+
for (let j = 0; j < 3; j++) {
|
|
1321
|
+
if (Math.abs(perm[i]) - 1 !== j) continue;
|
|
1322
|
+
layout[j] = i * Math.sign(perm[i]);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
let stride = 1;
|
|
1326
|
+
const instride = [1, 1, 1];
|
|
1327
|
+
const inflip = [false, false, false];
|
|
1328
|
+
for (let i = 0; i < layout.length; i++) {
|
|
1329
|
+
for (let j = 0; j < layout.length; j++) {
|
|
1330
|
+
const a = Math.abs(layout[j]);
|
|
1331
|
+
if (a !== i) continue;
|
|
1332
|
+
instride[j] = stride;
|
|
1333
|
+
// detect -0: https://medium.com/coding-at-dawn/is-negative-zero-0-a-number-in-javascript-c62739f80114
|
|
1334
|
+
if (layout[j] < 0 || Object.is(layout[j], -0)) inflip[j] = true;
|
|
1335
|
+
stride *= dims[j + 1];
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
// lookup table for flips and stride offsets:
|
|
1339
|
+
const range = (start, stop, step) =>
|
|
1340
|
+
Array.from(
|
|
1341
|
+
{ length: (stop - start) / step + 1 },
|
|
1342
|
+
(_, i) => start + i * step,
|
|
1343
|
+
);
|
|
1344
|
+
let xlut = range(0, dims[1] - 1, 1);
|
|
1345
|
+
if (inflip[0]) xlut = range(dims[1] - 1, 0, -1);
|
|
1346
|
+
for (let i = 0; i < dims[1]; i++) xlut[i] *= instride[0];
|
|
1347
|
+
let ylut = range(0, dims[2] - 1, 1);
|
|
1348
|
+
if (inflip[1]) ylut = range(dims[2] - 1, 0, -1);
|
|
1349
|
+
for (let i = 0; i < dims[2]; i++) ylut[i] *= instride[1];
|
|
1350
|
+
let zlut = range(0, dims[3] - 1, 1);
|
|
1351
|
+
if (inflip[2]) zlut = range(dims[3] - 1, 0, -1);
|
|
1352
|
+
for (let i = 0; i < dims[3]; i++) zlut[i] *= instride[2];
|
|
1353
|
+
// convert data
|
|
1354
|
+
|
|
1355
|
+
const inVs = new Uint8Array(this.drawBitmap);
|
|
1356
|
+
const outVs = new Uint8Array(dims[1] * dims[2] * dims[3]);
|
|
1357
|
+
let j = 0;
|
|
1358
|
+
for (let z = 0; z < dims[3]; z++) {
|
|
1359
|
+
for (let y = 0; y < dims[2]; y++) {
|
|
1360
|
+
for (let x = 0; x < dims[1]; x++) {
|
|
1361
|
+
let bit = inVs[xlut[x] + ylut[y] + zlut[z]];
|
|
1362
|
+
//Only fill matched bits
|
|
1363
|
+
outVs[j] = labels.indexOf(bit) >= 0 ? bit : 0;
|
|
1364
|
+
j++;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
await this.volumes[0].saveToDisk(fnm, outVs);
|
|
1369
|
+
return true;
|
|
1370
|
+
}
|
|
1371
|
+
};
|
|
1372
|
+
|
|
1373
|
+
Niivue.prototype.deleteDrawingByLabel = function (labels = [0]) {
|
|
1374
|
+
for (let i = 0; i < this.drawBitmap.length; i++) {
|
|
1375
|
+
this.drawBitmap[i] =
|
|
1376
|
+
labels.indexOf(this.drawBitmap[i]) < 0 ? this.drawBitmap[i] : 0;
|
|
1377
|
+
}
|
|
1378
|
+
this.refreshDrawing(false);
|
|
1379
|
+
};
|
|
1380
|
+
|
|
1381
|
+
/*
|
|
1382
|
+
For each spatial point, bitmap overlay tracks
|
|
1383
|
+
the layers of bitmaps during the grouping operation.
|
|
1384
|
+
For now, a single label for each voxel suffices, in future
|
|
1385
|
+
entries will be replaced with bit strings of the combination
|
|
1386
|
+
of labels
|
|
1387
|
+
*/
|
|
1388
|
+
const bitmapOverlay = [];
|
|
1389
|
+
|
|
1390
|
+
Niivue.prototype.groupLabelsInto = function (
|
|
1391
|
+
sourceLabels = [0],
|
|
1392
|
+
targetLabel = 7,
|
|
1393
|
+
) {
|
|
1394
|
+
for (let i = 0; i < this.drawBitmap.length; i++) {
|
|
1395
|
+
if (sourceLabels.indexOf(this.drawBitmap[i]) >= 0) {
|
|
1396
|
+
bitmapOverlay.push([i, this.drawBitmap[i]]);
|
|
1397
|
+
this.drawBitmap[i] = targetLabel;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
this.refreshDrawing(false);
|
|
1401
|
+
};
|
|
1402
|
+
|
|
1403
|
+
Niivue.prototype.ungroup = function () {
|
|
1404
|
+
for (let tuple of bitmapOverlay) {
|
|
1405
|
+
this.drawBitmap[tuple[0]] = tuple[1];
|
|
1406
|
+
}
|
|
1407
|
+
bitmapOverlay.length = 0;
|
|
1408
|
+
this.refreshDrawing(false);
|
|
1409
|
+
};
|
|
1410
|
+
|
|
1411
|
+
Niivue.prototype.resetScene = function () {
|
|
1412
|
+
this.scene.pan2Dxyzmm = [0, 0, 0, 1];
|
|
1413
|
+
this.drawScene();
|
|
1414
|
+
};
|
|
1415
|
+
|
|
1416
|
+
Niivue.prototype.recenter = function () {
|
|
1417
|
+
// this.scene.pan2Dxyzmm[0] = 0;
|
|
1418
|
+
// this.scene.pan2Dxyzmm[1] = 0;
|
|
1419
|
+
// this.scene.pan2Dxyzmm[2] = 0;
|
|
1420
|
+
|
|
1421
|
+
const zoom = this.scene.pan2Dxyzmm[3];
|
|
1422
|
+
this.scene.pan2Dxyzmm = [0, 0, 0, 1];
|
|
1423
|
+
const zoomChange = this.scene.pan2Dxyzmm[3] - zoom;
|
|
1424
|
+
this.scene.pan2Dxyzmm[3] = zoom;
|
|
1425
|
+
const mm = this.frac2mm([0.5, 0.5, 0.5]);
|
|
1426
|
+
this.scene.pan2Dxyzmm[0] += zoomChange * mm[0];
|
|
1427
|
+
this.scene.pan2Dxyzmm[1] += zoomChange * mm[1];
|
|
1428
|
+
this.scene.pan2Dxyzmm[2] += zoomChange * mm[2];
|
|
1429
|
+
this.drawScene();
|
|
1430
|
+
};
|
|
1431
|
+
|
|
1432
|
+
Niivue.prototype.resetZoom = function () {
|
|
1433
|
+
// this.scene.pan2Dxyzmm[0] = 0;
|
|
1434
|
+
// this.scene.pan2Dxyzmm[1] = 0;
|
|
1435
|
+
// this.scene.pan2Dxyzmm[2] = 0;
|
|
1436
|
+
|
|
1437
|
+
const zoom = 1;
|
|
1438
|
+
|
|
1439
|
+
const zoomChange = this.scene.pan2Dxyzmm[3] - zoom;
|
|
1440
|
+
this.scene.pan2Dxyzmm[3] = zoom;
|
|
1441
|
+
const mm = this.frac2mm(this.scene.crosshairPos);
|
|
1442
|
+
this.scene.pan2Dxyzmm[0] += zoomChange * mm[0];
|
|
1443
|
+
this.scene.pan2Dxyzmm[1] += zoomChange * mm[1];
|
|
1444
|
+
this.scene.pan2Dxyzmm[2] += zoomChange * mm[2];
|
|
1445
|
+
this.drawScene();
|
|
1446
|
+
};
|
|
1447
|
+
|
|
1448
|
+
Niivue.prototype.setCenteredZoom = function (zoom) {
|
|
1449
|
+
this.scene.pan2Dxyzmm[0] = 0;
|
|
1450
|
+
this.scene.pan2Dxyzmm[1] = 0;
|
|
1451
|
+
this.scene.pan2Dxyzmm[2] = 0;
|
|
1452
|
+
|
|
1453
|
+
const zoomChange = this.scene.pan2Dxyzmm[3] - zoom;
|
|
1454
|
+
this.scene.pan2Dxyzmm[3] = zoom;
|
|
1455
|
+
const mm = this.frac2mm(this.scene.crosshairPos);
|
|
1456
|
+
this.scene.pan2Dxyzmm[0] += zoomChange * mm[0];
|
|
1457
|
+
this.scene.pan2Dxyzmm[1] += zoomChange * mm[1];
|
|
1458
|
+
this.scene.pan2Dxyzmm[2] += zoomChange * mm[2];
|
|
1459
|
+
this.drawScene();
|
|
1460
|
+
};
|
|
1461
|
+
|
|
1462
|
+
Niivue.prototype.resetContrast = function () {
|
|
1463
|
+
this.volumes[0].cal_min = this.volumes[0].robust_min;
|
|
1464
|
+
this.volumes[0].cal_max = this.volumes[0].robust_max;
|
|
1465
|
+
this.onIntensityChange(this.volumes[0]);
|
|
1466
|
+
this.refreshLayers(this.volumes[0], 0);
|
|
1467
|
+
this.drawScene();
|
|
1468
|
+
if (this.onResetContrast) this.onResetContrast();
|
|
1469
|
+
};
|
|
1470
|
+
|
|
1471
|
+
Niivue.prototype.relabelROIs = function (source = 0, target = 0) {
|
|
1472
|
+
for (let i = 0; i < this.drawBitmap.length; i++) {
|
|
1473
|
+
if (this.drawBitmap[i] === source) {
|
|
1474
|
+
this.drawBitmap[i] = target;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
this.refreshDrawing(true);
|
|
1478
|
+
};
|
|
1479
|
+
|
|
1480
|
+
Niivue.prototype.loadDrawingFromBase64 = async function (fnm, base64) {
|
|
1481
|
+
if (this.drawBitmap) {
|
|
1482
|
+
console.debug("Overwriting open drawing!");
|
|
1483
|
+
}
|
|
1484
|
+
this.drawClearAllUndoBitmaps();
|
|
1485
|
+
try {
|
|
1486
|
+
// const volume = await NVImage.loadFromUrl()
|
|
1487
|
+
if (base64) {
|
|
1488
|
+
let imageOptions = NVImageFromUrlOptions(fnm);
|
|
1489
|
+
const drawingBitmap = NVImage.loadFromBase64({ name: fnm, base64 });
|
|
1490
|
+
if (drawingBitmap) {
|
|
1491
|
+
this.loadDrawing(drawingBitmap);
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
} catch (err) {
|
|
1495
|
+
console.error(err);
|
|
1496
|
+
console.error("loadDrawingFromBlob() failed to load " + fnm);
|
|
1497
|
+
this.drawClearAllUndoBitmaps();
|
|
1498
|
+
}
|
|
1499
|
+
return base64 !== undefined;
|
|
1500
|
+
};
|
|
1501
|
+
|
|
1502
|
+
// not included in public docs
|
|
1503
|
+
// show text labels for L/R, A/P, I/S dimensions
|
|
1504
|
+
Niivue.prototype.drawSliceOrientationText = function (
|
|
1505
|
+
leftTopWidthHeight,
|
|
1506
|
+
axCorSag,
|
|
1507
|
+
) {
|
|
1508
|
+
if (this.hideText) {
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
|
|
1512
|
+
let topText = "S";
|
|
1513
|
+
if (axCorSag === SLICE_TYPE.AXIAL) {
|
|
1514
|
+
topText = "A";
|
|
1515
|
+
}
|
|
1516
|
+
let leftText = this.opts.isRadiologicalConvention ? "R" : "L";
|
|
1517
|
+
if (axCorSag === SLICE_TYPE.SAGITTAL) {
|
|
1518
|
+
leftText = this.opts.sagittalNoseLeft ? "A" : "P";
|
|
1519
|
+
}
|
|
1520
|
+
if (this.opts.isCornerOrientationText) {
|
|
1521
|
+
this.drawTextRightBelow(
|
|
1522
|
+
[leftTopWidthHeight[0], leftTopWidthHeight[1]],
|
|
1523
|
+
leftText + topText,
|
|
1524
|
+
);
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
this.drawTextBelow(
|
|
1528
|
+
[
|
|
1529
|
+
leftTopWidthHeight[0] + leftTopWidthHeight[2] * 0.5,
|
|
1530
|
+
leftTopWidthHeight[1],
|
|
1531
|
+
],
|
|
1532
|
+
topText,
|
|
1533
|
+
);
|
|
1534
|
+
|
|
1535
|
+
this.drawTextRight(
|
|
1536
|
+
[
|
|
1537
|
+
leftTopWidthHeight[0],
|
|
1538
|
+
leftTopWidthHeight[1] + leftTopWidthHeight[3] * 0.5,
|
|
1539
|
+
],
|
|
1540
|
+
leftText,
|
|
1541
|
+
);
|
|
1542
|
+
};
|
|
1543
|
+
|
|
1544
|
+
// not included in public docs
|
|
1545
|
+
// draw line (can be diagonal)
|
|
1546
|
+
// unless Alpha is > 0, default color is opts.crosshairColor
|
|
1547
|
+
Niivue.prototype.drawLine = function (
|
|
1548
|
+
startXYendXY,
|
|
1549
|
+
thickness = 1,
|
|
1550
|
+
lineColor = [1, 0, 0, -1],
|
|
1551
|
+
) {
|
|
1552
|
+
console.log(startXYendXY);
|
|
1553
|
+
this.gl.bindVertexArray(this.genericVAO);
|
|
1554
|
+
if (!this.lineShader) {
|
|
1555
|
+
throw new Error("lineShader undefined");
|
|
1556
|
+
}
|
|
1557
|
+
this.lineShader.use(this.gl);
|
|
1558
|
+
if (lineColor[3] < 0) {
|
|
1559
|
+
lineColor = this.opts.crosshairColor;
|
|
1560
|
+
}
|
|
1561
|
+
this.gl.uniform4fv(this.lineShader.uniforms.lineColor, lineColor);
|
|
1562
|
+
this.gl.uniform2fv(this.lineShader.uniforms.canvasWidthHeight, [
|
|
1563
|
+
this.gl.canvas.width,
|
|
1564
|
+
this.gl.canvas.height,
|
|
1565
|
+
]);
|
|
1566
|
+
// draw Line
|
|
1567
|
+
this.gl.uniform1f(this.lineShader.uniforms.thickness, thickness);
|
|
1568
|
+
this.gl.uniform4fv(this.lineShader.uniforms.startXYendXY, startXYendXY);
|
|
1569
|
+
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
1570
|
+
this.gl.bindVertexArray(this.unusedVAO); // set vertex attributes
|
|
1571
|
+
};
|
|
1572
|
+
|
|
1573
|
+
// not included in public docs
|
|
1574
|
+
// note: no test yet
|
|
1575
|
+
Niivue.prototype.calculateNewRange = function ({ volIdx = 0 } = {}) {
|
|
1576
|
+
if (
|
|
1577
|
+
this.opts.sliceType === SLICE_TYPE.RENDER &&
|
|
1578
|
+
this.sliceMosaicString.length < 1
|
|
1579
|
+
) {
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
if (
|
|
1583
|
+
this.uiData.dragStart[0] === this.uiData.dragEnd[0] &&
|
|
1584
|
+
this.uiData.dragStart[1] === this.uiData.dragEnd[1]
|
|
1585
|
+
) {
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
// calculate our box
|
|
1589
|
+
let frac = this.canvasPos2frac([
|
|
1590
|
+
this.uiData.dragStart[0],
|
|
1591
|
+
this.uiData.dragStart[1],
|
|
1592
|
+
]);
|
|
1593
|
+
if (frac[0] < 0) {
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
const startVox = this.frac2vox(frac, volIdx);
|
|
1597
|
+
frac = this.canvasPos2frac([this.uiData.dragEnd[0], this.uiData.dragEnd[1]]);
|
|
1598
|
+
if (frac[0] < 0) {
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
const endVox = this.frac2vox(frac, volIdx);
|
|
1602
|
+
|
|
1603
|
+
let hi = -Number.MAX_VALUE;
|
|
1604
|
+
let lo = Number.MAX_VALUE;
|
|
1605
|
+
const xrange = this.calculateMinMaxVoxIdx([startVox[0], endVox[0]]);
|
|
1606
|
+
const yrange = this.calculateMinMaxVoxIdx([startVox[1], endVox[1]]);
|
|
1607
|
+
const zrange = this.calculateMinMaxVoxIdx([startVox[2], endVox[2]]);
|
|
1608
|
+
|
|
1609
|
+
// for our constant dimension we add one so that the for loop runs at least once
|
|
1610
|
+
if (startVox[0] - endVox[0] === 0) {
|
|
1611
|
+
xrange[1] = startVox[0] + 1;
|
|
1612
|
+
} else if (startVox[1] - endVox[1] === 0) {
|
|
1613
|
+
yrange[1] = startVox[1] + 1;
|
|
1614
|
+
} else if (startVox[2] - endVox[2] === 0) {
|
|
1615
|
+
zrange[1] = startVox[2] + 1;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
const hdr = this.volumes[volIdx].hdr;
|
|
1619
|
+
const img = this.volumes[volIdx].img;
|
|
1620
|
+
if (!hdr || !img) {
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
const xdim = hdr.dims[1];
|
|
1625
|
+
const ydim = hdr.dims[2];
|
|
1626
|
+
for (let z = zrange[0]; z < zrange[1]; z++) {
|
|
1627
|
+
const zi = z * xdim * ydim;
|
|
1628
|
+
for (let y = yrange[0]; y < yrange[1]; y++) {
|
|
1629
|
+
const yi = y * xdim;
|
|
1630
|
+
for (let x = xrange[0]; x < xrange[1]; x++) {
|
|
1631
|
+
const index = zi + yi + x;
|
|
1632
|
+
if (lo > img[index]) {
|
|
1633
|
+
lo = img[index];
|
|
1634
|
+
}
|
|
1635
|
+
if (hi < img[index]) {
|
|
1636
|
+
hi = img[index];
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
if (lo >= hi) {
|
|
1642
|
+
return;
|
|
1643
|
+
} // no variability or outside volume
|
|
1644
|
+
const mnScale = intensityRaw2Scaled(hdr, lo);
|
|
1645
|
+
const mxScale = intensityRaw2Scaled(hdr, hi);
|
|
1646
|
+
this.volumes[volIdx].cal_min = mnScale;
|
|
1647
|
+
this.volumes[volIdx].cal_max = mxScale;
|
|
1648
|
+
console.log(mnScale);
|
|
1649
|
+
console.log(mxScale);
|
|
1650
|
+
this.onIntensityChange(this.volumes[volIdx]);
|
|
1651
|
+
};
|
|
1652
|
+
|
|
1653
|
+
function create3() {
|
|
1654
|
+
var out = new Float32Array();
|
|
1655
|
+
// if (ARRAY_TYPE != Float32Array) {
|
|
1656
|
+
// out[0] = 0;
|
|
1657
|
+
// out[1] = 0;
|
|
1658
|
+
// out[2] = 0;
|
|
1659
|
+
// }
|
|
1660
|
+
return out;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
function swizzleVec3(vec, order = [0, 1, 2]) {
|
|
1664
|
+
const vout = create3();
|
|
1665
|
+
vout[0] = vec[order[0]];
|
|
1666
|
+
vout[1] = vec[order[1]];
|
|
1667
|
+
vout[2] = vec[order[2]];
|
|
1668
|
+
return vout;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
Niivue.prototype.drawCrossLinesMM = function (
|
|
1672
|
+
sliceIndex,
|
|
1673
|
+
axCorSag,
|
|
1674
|
+
axiMM,
|
|
1675
|
+
corMM,
|
|
1676
|
+
sagMM,
|
|
1677
|
+
) {
|
|
1678
|
+
console.log("method called");
|
|
1679
|
+
if (sliceIndex < 0 || this.screenSlices.length <= sliceIndex) {
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
const tile = this.screenSlices[sliceIndex];
|
|
1683
|
+
let sliceFrac = tile.sliceFrac;
|
|
1684
|
+
const isRender = sliceFrac === Infinity;
|
|
1685
|
+
if (isRender) {
|
|
1686
|
+
console.warn("Rendering approximate cross lines in world view mode");
|
|
1687
|
+
}
|
|
1688
|
+
if (sliceFrac === Infinity) {
|
|
1689
|
+
sliceFrac = 0.5;
|
|
1690
|
+
}
|
|
1691
|
+
let linesH = corMM.slice();
|
|
1692
|
+
let linesV = sagMM.slice();
|
|
1693
|
+
const thick = Math.max(7, this.opts.crosshairWidth);
|
|
1694
|
+
if (axCorSag === SLICE_TYPE.CORONAL) {
|
|
1695
|
+
linesH = axiMM.slice();
|
|
1696
|
+
}
|
|
1697
|
+
if (axCorSag === SLICE_TYPE.SAGITTAL) {
|
|
1698
|
+
linesH = axiMM.slice();
|
|
1699
|
+
linesV = corMM.slice();
|
|
1700
|
+
}
|
|
1701
|
+
function mm2screen(mm) {
|
|
1702
|
+
const screenXY = [0, 0];
|
|
1703
|
+
screenXY[0] =
|
|
1704
|
+
tile.leftTopWidthHeight[0] +
|
|
1705
|
+
((mm[0] - tile.leftTopMM[0]) / tile.fovMM[0]) *
|
|
1706
|
+
tile.leftTopWidthHeight[2];
|
|
1707
|
+
screenXY[1] =
|
|
1708
|
+
tile.leftTopWidthHeight[1] +
|
|
1709
|
+
tile.leftTopWidthHeight[3] -
|
|
1710
|
+
((mm[1] - tile.leftTopMM[1]) / tile.fovMM[1]) *
|
|
1711
|
+
tile.leftTopWidthHeight[3];
|
|
1712
|
+
return screenXY;
|
|
1713
|
+
}
|
|
1714
|
+
if (linesH.length > 0 && axCorSag === 0) {
|
|
1715
|
+
const fracZ = sliceFrac;
|
|
1716
|
+
const dimV = 1;
|
|
1717
|
+
for (let i2 = 0; i2 < linesH.length; i2++) {
|
|
1718
|
+
const mmV = this.frac2mm([0.5, 0.5, 0.5]);
|
|
1719
|
+
mmV[dimV] = linesH[i2];
|
|
1720
|
+
let fracY = this.mm2frac(mmV);
|
|
1721
|
+
fracY = fracY[dimV];
|
|
1722
|
+
let left = this.frac2mm([0, fracY, fracZ]);
|
|
1723
|
+
left = swizzleVec3(left, [0, 1, 2]);
|
|
1724
|
+
let right = this.frac2mm([1, fracY, fracZ]);
|
|
1725
|
+
right = swizzleVec3(right, [0, 1, 2]);
|
|
1726
|
+
left = mm2screen(left);
|
|
1727
|
+
right = mm2screen(right);
|
|
1728
|
+
this.drawLine([left[0], left[1], right[0], right[1]], thick);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
if (linesH.length > 0 && axCorSag === 1) {
|
|
1732
|
+
const fracH = sliceFrac;
|
|
1733
|
+
const dimV = 2;
|
|
1734
|
+
for (let i2 = 0; i2 < linesH.length; i2++) {
|
|
1735
|
+
const mmV = this.frac2mm([0.5, 0.5, 0.5]);
|
|
1736
|
+
mmV[dimV] = linesH[i2];
|
|
1737
|
+
let fracV = this.mm2frac(mmV);
|
|
1738
|
+
fracV = fracV[dimV];
|
|
1739
|
+
let left = this.frac2mm([0, fracH, fracV]);
|
|
1740
|
+
left = swizzleVec3(left, [0, 2, 1]);
|
|
1741
|
+
let right = this.frac2mm([1, fracH, fracV]);
|
|
1742
|
+
right = swizzleVec3(right, [0, 2, 1]);
|
|
1743
|
+
left = mm2screen(left);
|
|
1744
|
+
right = mm2screen(right);
|
|
1745
|
+
this.drawLine([left[0], left[1], right[0], right[1]], thick);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
if (linesH.length > 0 && axCorSag === 2) {
|
|
1749
|
+
const fracX = sliceFrac;
|
|
1750
|
+
const dimV = 2;
|
|
1751
|
+
for (let i2 = 0; i2 < linesH.length; i2++) {
|
|
1752
|
+
const mmV = this.frac2mm([0.5, 0.5, 0.5]);
|
|
1753
|
+
mmV[dimV] = linesH[i2];
|
|
1754
|
+
let fracZ = this.mm2frac(mmV);
|
|
1755
|
+
fracZ = fracZ[dimV];
|
|
1756
|
+
let left = this.frac2mm([fracX, 0, fracZ]);
|
|
1757
|
+
left = swizzleVec3(left, [1, 2, 0]);
|
|
1758
|
+
let right = this.frac2mm([fracX, 1, fracZ]);
|
|
1759
|
+
right = swizzleVec3(right, [1, 2, 0]);
|
|
1760
|
+
left = mm2screen(left);
|
|
1761
|
+
right = mm2screen(right);
|
|
1762
|
+
this.drawLine([left[0], left[1], right[0], right[1]], thick);
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
if (linesV.length > 0 && axCorSag === 0) {
|
|
1766
|
+
const fracZ = sliceFrac;
|
|
1767
|
+
const dimH = 0;
|
|
1768
|
+
for (let i2 = 0; i2 < linesV.length; i2++) {
|
|
1769
|
+
const mm = this.frac2mm([0.5, 0.5, 0.5]);
|
|
1770
|
+
mm[dimH] = linesV[i2];
|
|
1771
|
+
let frac = this.mm2frac(mm);
|
|
1772
|
+
frac = frac[dimH];
|
|
1773
|
+
let left = this.frac2mm([frac, 0, fracZ]);
|
|
1774
|
+
left = swizzleVec3(left, [0, 1, 2]);
|
|
1775
|
+
let right = this.frac2mm([frac, 1, fracZ]);
|
|
1776
|
+
right = swizzleVec3(right, [0, 1, 2]);
|
|
1777
|
+
left = mm2screen(left);
|
|
1778
|
+
right = mm2screen(right);
|
|
1779
|
+
this.drawLine([left[0], left[1], right[0], right[1]], thick);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
if (linesV.length > 0 && axCorSag === 1) {
|
|
1783
|
+
const fracY = sliceFrac;
|
|
1784
|
+
const dimH = 0;
|
|
1785
|
+
for (let i2 = 0; i2 < linesV.length; i2++) {
|
|
1786
|
+
const mm = this.frac2mm([0.5, 0.5, 0.5]);
|
|
1787
|
+
mm[dimH] = linesV[i2];
|
|
1788
|
+
let frac = this.mm2frac(mm);
|
|
1789
|
+
frac = frac[dimH];
|
|
1790
|
+
let left = this.frac2mm([frac, fracY, 0]);
|
|
1791
|
+
left = swizzleVec3(left, [0, 2, 1]);
|
|
1792
|
+
let right = this.frac2mm([frac, fracY, 1]);
|
|
1793
|
+
right = swizzleVec3(right, [0, 2, 1]);
|
|
1794
|
+
left = mm2screen(left);
|
|
1795
|
+
right = mm2screen(right);
|
|
1796
|
+
this.drawLine([left[0], left[1], right[0], right[1]], thick);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
if (linesV.length > 0 && axCorSag === 2) {
|
|
1800
|
+
const fracX = sliceFrac;
|
|
1801
|
+
const dimH = 1;
|
|
1802
|
+
for (let i2 = 0; i2 < linesV.length; i2++) {
|
|
1803
|
+
const mm = this.frac2mm([0.5, 0.5, 0.5]);
|
|
1804
|
+
mm[dimH] = linesV[i2];
|
|
1805
|
+
let frac = this.mm2frac(mm);
|
|
1806
|
+
frac = frac[dimH];
|
|
1807
|
+
let left = this.frac2mm([fracX, frac, 0]);
|
|
1808
|
+
left = swizzleVec3(left, [1, 2, 0]);
|
|
1809
|
+
let right = this.frac2mm([fracX, frac, 1]);
|
|
1810
|
+
right = swizzleVec3(right, [1, 2, 0]);
|
|
1811
|
+
left = mm2screen(left);
|
|
1812
|
+
right = mm2screen(right);
|
|
1813
|
+
this.drawLine([left[0], left[1], right[0], right[1]], thick);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
|
|
1818
|
+
// not included in public docs
|
|
1819
|
+
// Niivue.prototype.drawCrosshairs3D=function(isDepthTest = true, alpha = 1, mvpMtx = null, is2DView = false, isSliceMM = true) {
|
|
1820
|
+
// console.log('method called');
|
|
1821
|
+
// if (!this.opts.show3Dcrosshair && !is2DView) {
|
|
1822
|
+
// return;
|
|
1823
|
+
// }
|
|
1824
|
+
// if (this.opts.crosshairWidth <= 0 && is2DView) {
|
|
1825
|
+
// return;
|
|
1826
|
+
// }
|
|
1827
|
+
// const gl = this.gl;
|
|
1828
|
+
// const mm = this.frac2mm(this.scene.crosshairPos, 0, isSliceMM);
|
|
1829
|
+
// if (this.crosshairs3D === null || this.crosshairs3D.mm[0] !== mm[0] || this.crosshairs3D.mm[1] !== mm[1] || this.crosshairs3D.mm[2] !== mm[2]) {
|
|
1830
|
+
// if (this.crosshairs3D !== null) {
|
|
1831
|
+
// gl.deleteBuffer(this.crosshairs3D.indexBuffer);
|
|
1832
|
+
// gl.deleteBuffer(this.crosshairs3D.vertexBuffer);
|
|
1833
|
+
// }
|
|
1834
|
+
// const [mn, mx, range] = this.sceneExtentsMinMax(isSliceMM);
|
|
1835
|
+
// let radius = 1;
|
|
1836
|
+
// if (this.volumes.length > 0) {
|
|
1837
|
+
// radius = 0.5 * Math.min(Math.min(this.back.pixDims[1], this.back.pixDims[2]), this.back.pixDims[3]);
|
|
1838
|
+
// } else if (range[0] < 50 || range[0] > 1e3) {
|
|
1839
|
+
// radius = range[0] * 0.02;
|
|
1840
|
+
// }
|
|
1841
|
+
// radius *= this.opts.crosshairWidth;
|
|
1842
|
+
// this.crosshairs3D = NiivueObject3D.generateCrosshairs(this.gl, 1, mm, mn, mx, radius);
|
|
1843
|
+
// this.crosshairs3D.mm = mm;
|
|
1844
|
+
// }
|
|
1845
|
+
// const crosshairsShader = this.surfaceShader;
|
|
1846
|
+
// crosshairsShader.use(this.gl);
|
|
1847
|
+
// if (mvpMtx == null) {
|
|
1848
|
+
// ;
|
|
1849
|
+
// [mvpMtx] = this.calculateMvpMatrix(this.crosshairs3D, this.scene.renderAzimuth, this.scene.renderElevation);
|
|
1850
|
+
// }
|
|
1851
|
+
// gl.uniformMatrix4fv(crosshairsShader.mvpLoc, false, mvpMtx);
|
|
1852
|
+
// gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.crosshairs3D.indexBuffer);
|
|
1853
|
+
// gl.enable(gl.DEPTH_TEST);
|
|
1854
|
+
// const color = [...this.opts.crosshairColor];
|
|
1855
|
+
// if (isDepthTest) {
|
|
1856
|
+
// gl.disable(gl.BLEND);
|
|
1857
|
+
// gl.depthFunc(gl.GREATER);
|
|
1858
|
+
// } else {
|
|
1859
|
+
// gl.enable(gl.BLEND);
|
|
1860
|
+
// gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
1861
|
+
// gl.depthFunc(gl.ALWAYS);
|
|
1862
|
+
// }
|
|
1863
|
+
// color[3] = alpha;
|
|
1864
|
+
// gl.uniform4fv(crosshairsShader.colorLoc, color);
|
|
1865
|
+
// gl.bindVertexArray(this.crosshairs3D.vao);
|
|
1866
|
+
// gl.drawElements(
|
|
1867
|
+
// gl.TRIANGLES,
|
|
1868
|
+
// this.crosshairs3D.indexCount,
|
|
1869
|
+
// gl.UNSIGNED_INT,
|
|
1870
|
+
// // gl.UNSIGNED_SHORT,
|
|
1871
|
+
// 0
|
|
1872
|
+
// );
|
|
1873
|
+
// gl.bindVertexArray(this.unusedVAO);
|
|
1874
|
+
// }
|
|
1875
|
+
export { Niivue };
|