cloudmr-ux 2.0.6 → 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.
Files changed (206) hide show
  1. package/README.md +24 -24
  2. package/dist/CmrComponents/CmrButton/CmrButton.css +0 -0
  3. package/dist/CmrComponents/CmrButton/CmrButton.d.ts +4 -0
  4. package/dist/CmrComponents/CmrButton/CmrButton.js +30 -0
  5. package/dist/CmrComponents/CmrButton/index.d.ts +1 -0
  6. package/dist/CmrComponents/CmrButton/index.js +1 -0
  7. package/dist/CmrComponents/CmrCheckbox/CmrCheckbox.css +29 -0
  8. package/dist/CmrComponents/CmrCheckbox/CmrCheckbox.d.ts +14 -0
  9. package/dist/CmrComponents/CmrCheckbox/CmrCheckbox.js +30 -0
  10. package/dist/CmrComponents/CmrCheckbox/index.d.ts +1 -0
  11. package/dist/CmrComponents/CmrCheckbox/index.js +1 -0
  12. package/dist/CmrComponents/CmrColorPicker/CmrColorPicker.d.ts +8 -0
  13. package/dist/CmrComponents/CmrColorPicker/CmrColorPicker.js +29 -0
  14. package/dist/CmrComponents/CmrColorPicker/CmrColorPicker.scss +27 -0
  15. package/dist/CmrComponents/CmrInput/CmrInput.css +27 -0
  16. package/dist/CmrComponents/CmrInput/CmrInput.d.ts +17 -0
  17. package/dist/CmrComponents/CmrInput/CmrInput.js +29 -0
  18. package/dist/CmrComponents/CmrInput/index.d.ts +1 -0
  19. package/dist/CmrComponents/CmrInput/index.js +1 -0
  20. package/dist/CmrComponents/CmrRadioGroup/CmrRadioGroup.css +25 -0
  21. package/dist/CmrComponents/CmrRadioGroup/CmrRadioGroup.d.ts +15 -0
  22. package/dist/CmrComponents/CmrRadioGroup/CmrRadioGroup.js +37 -0
  23. package/dist/CmrComponents/CmrRadioGroup/index.d.ts +1 -0
  24. package/dist/CmrComponents/CmrRadioGroup/index.js +1 -0
  25. package/dist/CmrComponents/CmrSelect/CmrSelect.css +1 -0
  26. package/dist/CmrComponents/CmrSelect/CmrSelect.d.ts +24 -0
  27. package/dist/CmrComponents/CmrSelect/CmrSelect.js +46 -0
  28. package/dist/CmrComponents/CmrSelect/index.d.ts +1 -0
  29. package/dist/CmrComponents/CmrSelect/index.js +1 -0
  30. package/dist/CmrComponents/checkbox/Checkbox.css +8 -0
  31. package/dist/CmrComponents/checkbox/Checkbox.d.ts +15 -0
  32. package/dist/CmrComponents/checkbox/Checkbox.js +25 -0
  33. package/dist/CmrComponents/collapse/Collapse.css +3 -0
  34. package/dist/CmrComponents/collapse/Collapse.d.ts +18 -0
  35. package/dist/CmrComponents/collapse/Collapse.js +87 -0
  36. package/dist/CmrComponents/dialogue/Confirmation.d.ts +20 -0
  37. package/dist/CmrComponents/dialogue/Confirmation.js +36 -0
  38. package/dist/CmrComponents/dialogue/DeletionDialog.d.ts +4 -0
  39. package/dist/CmrComponents/dialogue/DeletionDialog.js +39 -0
  40. package/dist/CmrComponents/dialogue/EditConfirmation.d.ts +13 -0
  41. package/dist/CmrComponents/dialogue/EditConfirmation.js +45 -0
  42. package/dist/CmrComponents/double-slider/DualSlider.d.ts +21 -0
  43. package/dist/CmrComponents/double-slider/DualSlider.js +152 -0
  44. package/dist/CmrComponents/double-slider/InvertibleDualSlider.d.ts +24 -0
  45. package/dist/CmrComponents/double-slider/InvertibleDualSlider.js +174 -0
  46. package/dist/CmrComponents/gui-slider/ControlledSlider.d.ts +9 -0
  47. package/dist/CmrComponents/gui-slider/ControlledSlider.js +96 -0
  48. package/dist/CmrComponents/gui-slider/Slider.d.ts +20 -0
  49. package/dist/CmrComponents/gui-slider/Slider.js +127 -0
  50. package/dist/CmrComponents/header/Header.d.ts +17 -0
  51. package/dist/CmrComponents/header/Header.js +90 -0
  52. package/dist/CmrComponents/header/Header.scss +32 -0
  53. package/dist/CmrComponents/input-number/InputNumber.css +0 -0
  54. package/dist/CmrComponents/input-number/InputNumber.d.ts +17 -0
  55. package/dist/CmrComponents/input-number/InputNumber.js +30 -0
  56. package/dist/CmrComponents/label/Label.css +13 -0
  57. package/dist/CmrComponents/label/Label.d.ts +9 -0
  58. package/dist/CmrComponents/label/Label.js +18 -0
  59. package/dist/CmrComponents/panel/Panel.css +5 -0
  60. package/dist/CmrComponents/panel/Panel.d.ts +12 -0
  61. package/dist/CmrComponents/panel/Panel.js +42 -0
  62. package/dist/CmrComponents/rename/edit.d.ts +7 -0
  63. package/dist/CmrComponents/rename/edit.js +117 -0
  64. package/dist/CmrComponents/select-upload/SelectUpload.css +26 -0
  65. package/dist/CmrComponents/select-upload/SelectUpload.d.ts +33 -0
  66. package/dist/CmrComponents/select-upload/SelectUpload.js +90 -0
  67. package/dist/CmrComponents/tk-dualrange/TKDualRange.d.ts +17 -0
  68. package/dist/CmrComponents/tk-dualrange/TKDualRange.js +65 -0
  69. package/dist/CmrComponents/tk-dualrange/tk-dual-range.css +140 -0
  70. package/dist/CmrComponents/tooltip/Tooltip.css +0 -0
  71. package/dist/CmrComponents/tooltip/Tooltip.d.ts +18 -0
  72. package/dist/CmrComponents/tooltip/Tooltip.js +30 -0
  73. package/dist/CmrComponents/upload/Upload.css +5 -0
  74. package/dist/CmrComponents/upload/Upload.d.ts +80 -0
  75. package/dist/CmrComponents/upload/Upload.js +185 -0
  76. package/dist/CmrComponents/upload/UploadWindow.d.ts +15 -0
  77. package/dist/CmrComponents/upload/UploadWindow.js +286 -0
  78. package/dist/CmrTable/CmrTable.css +26 -0
  79. package/dist/CmrTable/CmrTable.d.ts +13 -0
  80. package/dist/CmrTable/CmrTable.js +47 -0
  81. package/dist/CmrTabs/CmrTabs.d.ts +7 -0
  82. package/dist/CmrTabs/CmrTabs.js +64 -0
  83. package/dist/CmrTabs/tab.model.d.ts +12 -0
  84. package/dist/CmrTabs/tab.model.js +1 -0
  85. package/dist/core/app/main/Main.d.ts +6 -0
  86. package/dist/core/app/main/Main.js +18 -0
  87. package/dist/core/app/results/Logs.d.ts +1 -0
  88. package/dist/core/app/results/Logs.js +33 -0
  89. package/dist/core/app/results/PreprocessJob.d.ts +1 -0
  90. package/dist/core/app/results/PreprocessJob.js +100 -0
  91. package/dist/core/app/results/Results.d.ts +15 -0
  92. package/dist/core/app/results/Results.js +372 -0
  93. package/dist/core/app/results/Results.scss +92 -0
  94. package/dist/core/app/results/Rois.d.ts +11 -0
  95. package/dist/core/app/results/Rois.js +269 -0
  96. package/dist/core/app/settings/Settings.d.ts +1 -0
  97. package/dist/core/app/settings/Settings.js +109 -0
  98. package/dist/core/app/signin/ForgotPassword.d.ts +3 -0
  99. package/dist/core/app/signin/ForgotPassword.js +142 -0
  100. package/dist/core/app/signin/Register.d.ts +3 -0
  101. package/dist/core/app/signin/Register.js +126 -0
  102. package/dist/core/app/signin/Signin.d.ts +5 -0
  103. package/dist/core/app/signin/Signin.js +84 -0
  104. package/dist/core/app/signin/Signin.scss +86 -0
  105. package/dist/core/app/upload/Upload.d.ts +3 -0
  106. package/dist/core/app/upload/Upload.js +261 -0
  107. package/dist/core/app/upload/Upload.scss +0 -0
  108. package/dist/core/common/components/CmrColorPicker/CmrColorPicker.d.ts +8 -0
  109. package/dist/core/common/components/CmrColorPicker/CmrColorPicker.js +29 -0
  110. package/dist/core/common/components/CmrColorPicker/CmrColorPicker.scss +27 -0
  111. package/dist/core/common/components/NiivueTools/Niivue.css +8 -0
  112. package/dist/core/common/components/NiivueTools/Niivue.d.ts +14 -0
  113. package/dist/core/common/components/NiivueTools/Niivue.js +1270 -0
  114. package/dist/core/common/components/NiivueTools/NiivuePatcher.js +1875 -0
  115. package/dist/core/common/components/NiivueTools/components/ColorPicker.d.ts +5 -0
  116. package/dist/core/common/components/NiivueTools/components/ColorPicker.js +68 -0
  117. package/dist/core/common/components/NiivueTools/components/DrawPlatte.d.ts +10 -0
  118. package/dist/core/common/components/NiivueTools/components/DrawPlatte.js +88 -0
  119. package/dist/core/common/components/NiivueTools/components/DrawToolKit.d.ts +32 -0
  120. package/dist/core/common/components/NiivueTools/components/DrawToolKit.js +164 -0
  121. package/dist/core/common/components/NiivueTools/components/EraserPlatte.d.ts +10 -0
  122. package/dist/core/common/components/NiivueTools/components/EraserPlatte.js +43 -0
  123. package/dist/core/common/components/NiivueTools/components/Layer.d.ts +10 -0
  124. package/dist/core/common/components/NiivueTools/components/Layer.js +117 -0
  125. package/dist/core/common/components/NiivueTools/components/LayersPanel.d.ts +8 -0
  126. package/dist/core/common/components/NiivueTools/components/LayersPanel.js +108 -0
  127. package/dist/core/common/components/NiivueTools/components/LocationTable.d.ts +9 -0
  128. package/dist/core/common/components/NiivueTools/components/LocationTable.js +42 -0
  129. package/dist/core/common/components/NiivueTools/components/MaskPlatte.d.ts +10 -0
  130. package/dist/core/common/components/NiivueTools/components/MaskPlatte.js +123 -0
  131. package/dist/core/common/components/NiivueTools/components/NiivuePanel.d.ts +34 -0
  132. package/dist/core/common/components/NiivueTools/components/NiivuePanel.js +305 -0
  133. package/dist/core/common/components/NiivueTools/components/NumberPicker.d.ts +8 -0
  134. package/dist/core/common/components/NiivueTools/components/NumberPicker.js +40 -0
  135. package/dist/core/common/components/NiivueTools/components/SettingsPanel.d.ts +7 -0
  136. package/dist/core/common/components/NiivueTools/components/SettingsPanel.js +30 -0
  137. package/dist/core/common/components/NiivueTools/components/Switch.d.ts +5 -0
  138. package/dist/core/common/components/NiivueTools/components/Switch.js +26 -0
  139. package/dist/core/common/components/NiivueTools/components/Toolbar.d.ts +40 -0
  140. package/dist/core/common/components/NiivueTools/components/Toolbar.js +184 -0
  141. package/dist/core/common/components/NiivueTools/components/Toolbar.scss +39 -0
  142. package/dist/core/common/components/NiivueTools/components/stats.d.ts +2 -0
  143. package/dist/core/common/components/NiivueTools/components/stats.js +13 -0
  144. package/dist/core/common/components/NiivueTools/index.css +14 -0
  145. package/dist/core/common/components/NiivueTools/util.js +309 -0
  146. package/dist/core/common/components/footer/Footer.d.ts +3 -0
  147. package/dist/core/common/components/footer/Footer.js +20 -0
  148. package/dist/core/common/components/footer/Footer.scss +5 -0
  149. package/dist/core/common/utilities/AuthenticatedRequests.d.ts +16 -0
  150. package/dist/core/common/utilities/AuthenticatedRequests.js +158 -0
  151. package/dist/core/common/utilities/CalendarHelper.d.ts +5 -0
  152. package/dist/core/common/utilities/CalendarHelper.js +27 -0
  153. package/dist/core/common/utilities/DownloadFromText.d.ts +3 -0
  154. package/dist/core/common/utilities/DownloadFromText.js +20 -0
  155. package/dist/core/common/utilities/StoreToRequest.d.ts +1 -0
  156. package/dist/core/common/utilities/StoreToRequest.js +4 -0
  157. package/dist/core/common/utilities/SystemUtilities.d.ts +4 -0
  158. package/dist/core/common/utilities/SystemUtilities.js +79 -0
  159. package/dist/core/common/utilities/file-transformation/anonymize.d.ts +1 -0
  160. package/dist/core/common/utilities/file-transformation/anonymize.js +114 -0
  161. package/dist/core/common/utilities/file-transformation/utilities.d.ts +2 -0
  162. package/dist/core/common/utilities/file-transformation/utilities.js +23 -0
  163. package/dist/core/common/utilities/index.d.ts +25 -0
  164. package/dist/core/common/utilities/index.js +118 -0
  165. package/dist/core/common/utilities/parse-jwt.d.ts +1 -0
  166. package/dist/core/common/utilities/parse-jwt.js +14 -0
  167. package/dist/core/components/PasswordRequirements.d.ts +7 -0
  168. package/dist/core/components/PasswordRequirements.js +30 -0
  169. package/dist/core/config/AppConfig.d.ts +5 -0
  170. package/dist/core/config/AppConfig.js +42 -0
  171. package/dist/core/config/types.d.ts +40 -0
  172. package/dist/core/config/types.js +1 -0
  173. package/dist/core/features/authenticate/authenticateActionCreation.d.ts +46 -0
  174. package/dist/core/features/authenticate/authenticateActionCreation.js +326 -0
  175. package/dist/core/features/authenticate/authenticateSlice.d.ts +45 -0
  176. package/dist/core/features/authenticate/authenticateSlice.js +203 -0
  177. package/dist/core/features/data/dataActionCreation.d.ts +40 -0
  178. package/dist/core/features/data/dataActionCreation.js +340 -0
  179. package/dist/core/features/data/dataSlice.d.ts +37 -0
  180. package/dist/core/features/data/dataSlice.js +87 -0
  181. package/dist/core/features/jobs/jobActionCreation.d.ts +35 -0
  182. package/dist/core/features/jobs/jobActionCreation.js +242 -0
  183. package/dist/core/features/jobs/jobsSlice.d.ts +57 -0
  184. package/dist/core/features/jobs/jobsSlice.js +54 -0
  185. package/dist/core/features/rois/resultActionCreation.d.ts +21 -0
  186. package/dist/core/features/rois/resultActionCreation.js +114 -0
  187. package/dist/core/features/rois/resultSlice.d.ts +24 -0
  188. package/dist/core/features/rois/resultSlice.js +68 -0
  189. package/dist/core/features/rois/roiTypes.d.ts +44 -0
  190. package/dist/core/features/rois/roiTypes.js +1 -0
  191. package/dist/core/features/setup/setupActionCreation.d.ts +7 -0
  192. package/dist/core/features/setup/setupActionCreation.js +100 -0
  193. package/dist/core/index.d.ts +22 -0
  194. package/dist/core/index.js +27 -0
  195. package/dist/core/store/configureStore.d.ts +13 -0
  196. package/dist/core/store/configureStore.js +38 -0
  197. package/dist/core/store/hooks.d.ts +11 -0
  198. package/dist/core/store/hooks.js +5 -0
  199. package/dist/core/utils/passwordValidation.d.ts +25 -0
  200. package/dist/core/utils/passwordValidation.js +19 -0
  201. package/dist/index.d.ts +29 -329
  202. package/dist/index.js +26 -1402
  203. package/dist/style.css +47 -0
  204. package/package.json +309 -41
  205. package/dist/index.css +0 -170
  206. 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 };