cloudmr-ux 4.3.7 → 4.3.9

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 (50) hide show
  1. package/dist/CmrComponents/niivue-contrast-adjustments/NiivueContrastAdjustments.d.ts +4 -3
  2. package/dist/CmrComponents/niivue-contrast-adjustments/NiivueContrastAdjustments.js +5 -5
  3. package/dist/CmrComponents/niivue-slice-position/NiivueSlicePosition.js +2 -2
  4. package/dist/CmrComponents/niivue-viewer/CloudMrNiivuePanel.d.ts +38 -0
  5. package/dist/CmrComponents/niivue-viewer/CloudMrNiivuePanel.js +197 -0
  6. package/dist/CmrComponents/niivue-viewer/CloudMrNiivueViewer.d.ts +41 -0
  7. package/dist/CmrComponents/niivue-viewer/CloudMrNiivueViewer.js +1239 -0
  8. package/dist/CmrComponents/niivue-viewer/ColorPicker.d.ts +1 -0
  9. package/dist/CmrComponents/niivue-viewer/ColorPicker.js +65 -0
  10. package/dist/CmrComponents/niivue-viewer/Layer.d.ts +1 -0
  11. package/dist/CmrComponents/niivue-viewer/Layer.js +122 -0
  12. package/dist/CmrComponents/niivue-viewer/LayersPanel.d.ts +1 -0
  13. package/dist/CmrComponents/niivue-viewer/LayersPanel.js +107 -0
  14. package/dist/CmrComponents/niivue-viewer/Niivue.css +8 -0
  15. package/dist/CmrComponents/niivue-viewer/NiivuePatcher.d.ts +2 -0
  16. package/dist/CmrComponents/niivue-viewer/NiivuePatcher.js +1402 -0
  17. package/dist/CmrComponents/niivue-viewer/NumberPicker.d.ts +1 -0
  18. package/dist/CmrComponents/niivue-viewer/NumberPicker.js +40 -0
  19. package/dist/CmrComponents/niivue-viewer/SettingsPanel.d.ts +1 -0
  20. package/dist/CmrComponents/niivue-viewer/SettingsPanel.js +30 -0
  21. package/dist/CmrComponents/niivue-viewer/Switch.d.ts +1 -0
  22. package/dist/CmrComponents/niivue-viewer/Switch.js +27 -0
  23. package/dist/CmrComponents/niivue-viewer/Toolbar.d.ts +48 -0
  24. package/dist/CmrComponents/niivue-viewer/Toolbar.js +276 -0
  25. package/dist/CmrComponents/niivue-viewer/Toolbar.scss +40 -0
  26. package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/DrawColorPlatte.d.ts +2 -0
  27. package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/DrawColorPlatte.js +61 -0
  28. package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/EraserPlatte.d.ts +2 -0
  29. package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/EraserPlatte.js +56 -0
  30. package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/MaskPlatte.d.ts +2 -0
  31. package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/MaskPlatte.js +148 -0
  32. package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/MroDrawToolkit.d.ts +1 -0
  33. package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/MroDrawToolkit.js +177 -0
  34. package/dist/CmrComponents/niivue-viewer/niivuePenType.d.ts +10 -0
  35. package/dist/CmrComponents/niivue-viewer/niivuePenType.js +10 -0
  36. package/dist/core/common/components/NiivueTools/NiivuePatcher.d.ts +2 -0
  37. package/dist/core/common/components/NiivueTools/components/ControlThemes.d.ts +1 -0
  38. package/dist/core/common/components/NiivueTools/components/ControlThemes.js +123 -0
  39. package/dist/core/common/components/NiivueTools/components/Example.d.ts +10 -0
  40. package/dist/core/common/components/NiivueTools/components/Example.js +326 -0
  41. package/dist/core/common/components/NiivueTools/components/ImageList.d.ts +1 -0
  42. package/dist/core/common/components/NiivueTools/components/ImageList.js +22 -0
  43. package/dist/core/common/components/NiivueTools/components/ImageListItem.d.ts +1 -0
  44. package/dist/core/common/components/NiivueTools/components/ImageListItem.js +103 -0
  45. package/dist/core/common/components/NiivueTools/main.d.ts +1 -0
  46. package/dist/core/common/components/NiivueTools/main.js +16 -0
  47. package/dist/core/common/components/NiivueTools/util.d.ts +21 -0
  48. package/dist/index.d.ts +3 -0
  49. package/dist/index.js +2 -0
  50. package/package.json +3 -3
@@ -0,0 +1,1239 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
13
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
14
+ return new (P || (P = Promise))(function (resolve, reject) {
15
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
16
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
17
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
18
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
19
+ });
20
+ };
21
+ var __generator = (this && this.__generator) || function (thisArg, body) {
22
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
23
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
24
+ function verb(n) { return function (v) { return step([n, v]); }; }
25
+ function step(op) {
26
+ if (f) throw new TypeError("Generator is already executing.");
27
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
28
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
29
+ if (y = 0, t) op = [op[0] & 2, t.value];
30
+ switch (op[0]) {
31
+ case 0: case 1: t = op; break;
32
+ case 4: _.label++; return { value: op[1], done: false };
33
+ case 5: _.label++; y = op[1]; op = [0]; continue;
34
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
35
+ default:
36
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
37
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
38
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
39
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
40
+ if (t[2]) _.ops.pop();
41
+ _.trys.pop(); continue;
42
+ }
43
+ op = body.call(thisArg, _);
44
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
45
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
46
+ }
47
+ };
48
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
49
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
50
+ if (ar || !(i in from)) {
51
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
52
+ ar[i] = from[i];
53
+ }
54
+ }
55
+ return to.concat(ar || Array.prototype.slice.call(from));
56
+ };
57
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
58
+ import React, { useState } from 'react';
59
+ import { Box, Button } from '@mui/material';
60
+ import { NVImage, DRAG_MODE } from '@niivue/niivue';
61
+ import { NI_PEN_TYPE } from './niivuePenType';
62
+ import { SettingsPanel } from './SettingsPanel';
63
+ import { NumberPicker } from './NumberPicker';
64
+ import { ColorPicker } from './ColorPicker';
65
+ import { LayersPanel } from './LayersPanel';
66
+ import { CloudMrNiivuePanel } from './CloudMrNiivuePanel';
67
+ import { Niivue } from './NiivuePatcher';
68
+ import NVSwitch from './Switch';
69
+ import Toolbar from './Toolbar';
70
+ import Layer from './Layer';
71
+ import './Niivue.css';
72
+ import CmrEditConfirmation from '../dialogue/EditConfirmation';
73
+ import CmrConfirmation from '../dialogue/Confirmation';
74
+ import { resampleNiivueRoiHistogram } from '../niivue-roi-histogram/resampleNiivueRoiHistogram';
75
+ import axios from "axios";
76
+ import JSZip from "jszip";
77
+ import { getMax, getMin } from "../../core/common/utilities";
78
+ import { AuthenticatedHttpClient, getEndpoints } from "../../core";
79
+ /**
80
+ * @typedef {Object} CloudMrNiivueViewerProps
81
+ * @property {any[]} niis
82
+ * @property {number} selectedVolume
83
+ * @property {(i: number) => void} setSelectedVolume
84
+ * @property {any[]} rois
85
+ * @property {string} pipelineID
86
+ * @property {string} [accessToken]
87
+ * @property {(msg: string) => void} [setWarning]
88
+ * @property {(open: boolean) => void} [setWarningOpen]
89
+ * @property {(fn: () => void) => void} [saveROICallback]
90
+ * @property {string} roiDeleteUrl — full URL for `ROI_DELETE` (app resolves from env)
91
+ * @property {() => Promise<void>} refreshPipelineRois — e.g. dispatch getPipelineROI
92
+ * @property {() => any[]} getPipelineRois — returns current ROI list for pipeline (e.g. from Redux)
93
+ */
94
+ export var nv = new Niivue({
95
+ loadingText: '',
96
+ isColorbar: true,
97
+ isRadiologicalConvention: true,
98
+ textHeight: 0.12,
99
+ colorbarHeight: 0.03,
100
+ dragMode: DRAG_MODE.pan,
101
+ crosshairWidth: 0,
102
+ // crosshairColor: [0.098,0.453,0.824]
103
+ crosshairColor: [1, 1, 0],
104
+ fontColor: [0.00, 0.94, 0.37, 1],
105
+ isNearestInterpolation: true,
106
+ isFilledPen: true,
107
+ penValue: 1,
108
+ penType: NI_PEN_TYPE.PEN
109
+ });
110
+ nv.opts.penBounds = 0;
111
+ window.nv = nv;
112
+ // The NiiVue component wraps all other components in the UI.
113
+ // It is exported so that it can be used in other projects easily
114
+ export default function CloudMrNiivueViewer(props) {
115
+ var _this = this;
116
+ var endpoints = getEndpoints(); // CloudMR core endpoints (mode1/mode2 helper)
117
+ var selectedVolume = props.selectedVolume;
118
+ var setSelectedVolume = props.setSelectedVolume;
119
+ var setWarning = props.setWarning, setWarningOpen = props.setWarningOpen;
120
+ // const nv = props.nv;
121
+ var _a = React.useState(false), openSettings = _a[0], setOpenSettings = _a[1];
122
+ var _b = React.useState(false), openLayers = _b[0], setOpenLayers = _b[1];
123
+ var _c = React.useState(nv.opts.crosshairColor), crosshairColor = _c[0], setCrosshairColor = _c[1];
124
+ var _d = React.useState(nv.opts.selectionBoxColor), selectionBoxColor = _d[0], setSelectionBoxColor = _d[1];
125
+ var _e = React.useState(nv.opts.backColor), backColor = _e[0], setBackColor = _e[1];
126
+ var _f = React.useState(nv.opts.clipPlaneColor), clipPlaneColor = _f[0], setClipPlaneColor = _f[1];
127
+ var _g = React.useState(nv.volumes), layers = _g[0], setLayers = _g[1];
128
+ var _h = React.useState(false), cornerText = _h[0], setCornerText = _h[1];
129
+ var _j = React.useState(true), radiological = _j[0], setRadiological = _j[1];
130
+ var _k = React.useState(false), crosshair3D = _k[0], setCrosshair3D = _k[1];
131
+ var _l = React.useState(nv.opts.textHeight), textSize = _l[0], setTextSize = _l[1];
132
+ var _m = React.useState(nv.opts.isColorbar), colorBar = _m[0], setColorBar = _m[1];
133
+ var _o = React.useState(nv.opts.isSliceMM), worldSpace = _o[0], setWorldSpace = _o[1];
134
+ var _p = React.useState(nv.currentClipPlaneIndex > 0 ? true : false), clipPlane = _p[0], setClipPlane = _p[1];
135
+ // TODO: add crosshair size state and setter
136
+ var _q = React.useState(1.0), opacity = _q[0], setopacity = _q[1];
137
+ var _r = React.useState(nv.opts.drawingEnabled), drawingEnabled = _r[0], setDrawingEnabled = _r[1];
138
+ var _s = React.useState(nv.opts.penValue), drawPen = _s[0], setDrawPen = _s[1];
139
+ var _t = React.useState(0.8), drawOpacity = _t[0], setDrawOpacity = _t[1];
140
+ var _u = React.useState(nv.opts.crosshairColor[3]), crosshairOpacity = _u[0], setCrosshairOpacity = _u[1];
141
+ var _v = React.useState(nv.opts.clipPlaneColor[3]), clipPlaneOpacity = _v[0], setClipPlaneOpacity = _v[1];
142
+ var _w = React.useState(true), locationTableVisible = _w[0], setLocationTableVisible = _w[1];
143
+ var _x = React.useState([]), locationData = _x[0], setLocationData = _x[1];
144
+ var _y = React.useState(2), decimalPrecision = _y[0], setDecimalPrecision = _y[1];
145
+ var _z = React.useState(nv.opts.isOrientCube), orientCube = _z[0], setOrientCube = _z[1];
146
+ var _0 = React.useState(nv.opts.isRuler), ruler = _0[0], setRuler = _0[1];
147
+ var _1 = React.useState(nv.opts.multiplanarPadPixels), multiplanarPadPixels = _1[0], setMultiplanarPadPixels = _1[1];
148
+ var _2 = React.useState(nv.opts.maxDrawUndoBitmaps), maxDrawUndoBitmaps = _2[0], setMaxDrawUndoBitmaps = _2[1];
149
+ var _3 = React.useState(nv.opts.sagittalNoseLeft), sagittalNoseLeft = _3[0], setSagittalNoseLeft = _3[1];
150
+ var _4 = React.useState(nv.opts.rulerWidth), rulerWidth = _4[0], setRulerWidth = _4[1];
151
+ var _5 = React.useState(nv.opts.longTouchTimeout), longTouchTimeout = _5[0], setLongTouchTimeout = _5[1];
152
+ var _6 = React.useState(nv.opts.doubleTouchTimeout), doubleTouchTimeout = _6[0], setDoubleTouchTimeout = _6[1];
153
+ var _7 = React.useState(nv.opts.isDragShowsMeasurementTool), dragToMeasure = _7[0], setDragToMeasure = _7[1];
154
+ var _8 = React.useState(nv.opts.rulerColor), rulerColor = _8[0], setRulerColor = _8[1];
155
+ var _9 = React.useState(nv.opts.rulerColor[3]), rulerOpacity = _9[0], setRulerOpacity = _9[1];
156
+ var _10 = React.useState(false), highDPI = _10[0], setHighDPI = _10[1];
157
+ var _11 = React.useState([]), rois = _11[0], setROIs = _11[1];
158
+ // Persists zoom, gamma and opacity across volume/channel switches so they are not reset
159
+ var savedViewStateRef = React.useRef(null);
160
+ var _12 = React.useState(false), showCrosshair = _12[0], setShowCrosshair = _12[1];
161
+ var _13 = useState('absolute'), complexMode = _13[0], setComplexMode = _13[1];
162
+ var _14 = useState(['absolute']), complexOptions = _14[0], setComplexOptions = _14[1];
163
+ var _15 = useState(true), roiVisible = _15[0], setROIVisible = _15[1];
164
+ var _16 = useState(0.8), drawingOpacity = _16[0], setDrawingOpacity = _16[1];
165
+ var _17 = useState(0), min = _17[0], setMin = _17[1];
166
+ var _18 = useState(1), max = _18[0], setMax = _18[1];
167
+ var _19 = useState(false), textsVisible = _19[0], setTextsVisible = _19[1];
168
+ var _20 = useState({ a: 1, b: 0 }), transformFactors = _20[0], setTransformFactors = _20[1];
169
+ var _21 = useState(false), saving = _21[0], setSaving = _21[1];
170
+ var _22 = useState(null), drawShapeTool = _22[0], setDrawShapeTool = _22[1];
171
+ React.useEffect(function () {
172
+ nv.opts.penBounds = 0;
173
+ }, []);
174
+ var _23 = React.useState(1.0), gamma = _23[0], setGamma = _23[1];
175
+ var _24 = React.useState(0), gammaKey = _24[0], setGammaKey = _24[1];
176
+ // Niivue → React bridge so other places (Toolbar) can force the UI to reset
177
+ nv.onResetGamma = function () {
178
+ setGamma(1.0);
179
+ setGammaKey(function (k) { return k + 1; }); // re-mounts the slider to reflect the reset
180
+ };
181
+ // Do not call resampleImage() here: NiivuePanel (which mounts `#histoplot`) only renders when
182
+ // props.niis[selectedVolume] exists — running on mount throws "No DOM element with id 'histoplot'".
183
+ // NiivuePanel’s own effect calls resampleImage after the histogram node exists.
184
+ React.useEffect(function () {
185
+ if (nv.volumes.length !== 0) {
186
+ setLayers(__spreadArray([], nv.volumes, true));
187
+ setBoundMins(nv.frac2mm([0, 0, 0]));
188
+ setBoundMaxs(nv.frac2mm([1, 1, 1]));
189
+ // setMMs(nv.frac2mm([0.5, 0.5, 0.5])); // Commented to prevent recentering on volume load; we now re-apply saved crosshair if available
190
+ try {
191
+ // Initialize sliders to the engine’s current crosshair (in mm), not a hard-coded 0.5
192
+ setMMs(nv.frac2mm(nv.scene.crosshairPos));
193
+ }
194
+ catch (_a) { }
195
+ setTimeout(function (args) { return nv.resizeListener(); }, 700);
196
+ }
197
+ }, []);
198
+ React.useEffect(function () {
199
+ var _a;
200
+ var nii = (_a = props.niis) === null || _a === void 0 ? void 0 : _a[props.selectedVolume];
201
+ if (nii)
202
+ stylingProxy(nii);
203
+ }, [props.selectedVolume, props.niis]);
204
+ // only run this when the component is mounted on the page
205
+ // or else it will be recursive and continuously add all
206
+ // initial images supplied to the NiiVue component
207
+ //
208
+ // All subsequent imgaes should be added via a
209
+ // button or drag and drop
210
+ // React.useEffect(async ()=>{
211
+ // // props.volumes.map(async (vol)=>{
212
+ // // let image = await NVImage.loadFromUrl({url:vol.url})
213
+ // // nv.addVolume(image)
214
+ // // setLayers([...nv.volumes])
215
+ // // })
216
+ // await nv.loadVolumes(props.volumes)
217
+ // setLayers([...nv.volumes])
218
+ // }, [])
219
+ // values dualslider
220
+ var _25 = useState(0), rangeKey = _25[0], setRangeKey = _25[1];
221
+ nv.onResetContrast = function () {
222
+ setRangeKey(rangeKey + 1);
223
+ };
224
+ var _26 = useState([0, 0, 0]), boundMins = _26[0], setBoundMins = _26[1];
225
+ var _27 = useState([1, 1, 1]), boundMaxs = _27[0], setBoundMaxs = _27[1];
226
+ var _28 = useState([0.5, 0.5, 0.5]), mms = _28[0], setMMs = _28[1];
227
+ nv.onImageLoaded = function () {
228
+ var _a, _b;
229
+ var oldCrosshairPos = __spreadArray([], nv.scene.crosshairPos, true);
230
+ if (nv.volumes.length > 1) {
231
+ var nii = (_a = props.niis) === null || _a === void 0 ? void 0 : _a[props.selectedVolume];
232
+ if (nii === null || nii === void 0 ? void 0 : nii.link) {
233
+ nv.loadVolumes([niiToVolume(nii)]);
234
+ setWarning("Error loading results, please check internet connectivity");
235
+ setWarningOpen(true);
236
+ setTimeout(function () {
237
+ setWarningOpen(false);
238
+ }, 2500);
239
+ setWarning("");
240
+ return;
241
+ }
242
+ // One logical job file can decode as multiple Niivue volumes (e.g. some 4D / replica layouts) while Redux
243
+ // still has a single `niis[]` entry (common for Multiple Replica without a separate noise channel). Drop
244
+ // extras and finish init on the first volume instead of bailing with a bogus network error.
245
+ try {
246
+ var extras = nv.volumes.slice(1);
247
+ for (var _i = 0, extras_1 = extras; _i < extras_1.length; _i++) {
248
+ var vol = extras_1[_i];
249
+ nv.removeVolume(vol);
250
+ }
251
+ }
252
+ catch (e) {
253
+ console.warn("Niivue onImageLoaded: could not trim extra volumes", e);
254
+ return;
255
+ }
256
+ if (nv.volumes.length !== 1)
257
+ return;
258
+ }
259
+ // console.log(nv.volumes);
260
+ // Restore zoom, gamma, opacity and colormap if switching channels/volumes, otherwise reset to defaults.
261
+ // Do this BEFORE setLayers so that Layer mounts with the correct opacity already on the volume object.
262
+ var saved = savedViewStateRef.current;
263
+ if (saved) {
264
+ nv.scene.pan2Dxyzmm = __spreadArray([], saved.pan2Dxyzmm, true);
265
+ nv.setGamma(saved.gamma);
266
+ setGamma(saved.gamma);
267
+ setGammaKey(function (k) { return k + 1; });
268
+ var vol = nv.volumes[0];
269
+ if (vol) {
270
+ nv.setColorMap(vol.id, saved.colormap);
271
+ vol.opacity = saved.opacity;
272
+ nv.updateGLVolume();
273
+ }
274
+ setopacity(saved.opacity);
275
+ savedViewStateRef.current = null;
276
+ }
277
+ else {
278
+ nv.setGamma(1.0);
279
+ (_b = nv.onResetGamma) === null || _b === void 0 ? void 0 : _b.call(nv);
280
+ nv.resetScene();
281
+ }
282
+ setLayers(__spreadArray([], nv.volumes, true));
283
+ setBoundMins(nv.frac2mm([0, 0, 0]));
284
+ setBoundMaxs(nv.frac2mm([1, 1, 1]));
285
+ // setMMs(nv.frac2mm([0.5, 0.5, 0.5])); // Commented to prevent recentering on volume load; we now re-apply saved crosshair if available
286
+ if (verifyComplex(nv.volumes[0])) //Check if there are complex components
287
+ nvSetDisplayedVoxels('absolute');
288
+ else
289
+ nvSetDisplayedVoxels('absolute');
290
+ // let volume = nv.volumes[0];
291
+ nvSetDragMode(dragMode); // keep engine behavior in sync with dropdown
292
+ // Re-apply world/voxel mode and last crosshair after resets
293
+ nv.setSliceMM(worldSpace);
294
+ // applySavedCrosshairIfAny();
295
+ nv.scene.crosshairPos = __spreadArray([], oldCrosshairPos, true);
296
+ // keep display mode consistent after resets
297
+ nvUpdateSliceType(sliceType);
298
+ nv.opts.crosshairWidth = showCrosshair ? 1 : 0;
299
+ setMMs(nv.frac2mm(nv.scene.crosshairPos));
300
+ };
301
+ function checkRange(numbers) {
302
+ // console.log(numbers);
303
+ var range_min = getMin(numbers);
304
+ var range_max = getMax(numbers);
305
+ var range = range_max - range_min;
306
+ if (range == 0) {
307
+ return numbers;
308
+ }
309
+ if (range < 1e-2) {
310
+ // Find a suitable 'a' that is a whole power of 10
311
+ // Here, we want 'a' to scale the range to fit within [1, 10)
312
+ var a_1 = 1;
313
+ var power = 0;
314
+ while ((range * a_1) < 1) {
315
+ a_1 *= 10;
316
+ power += 1;
317
+ }
318
+ // Calculate 'b' such that the minimum transformed value is 1 (x = 1)
319
+ var b_1 = Math.floor(a_1 * range_min - a_1 * range_min % 10) / a_1;
320
+ console.log(b_1);
321
+ // Apply the transformation ax + b
322
+ var transformed = numbers.map(function (y) { return a_1 * y - a_1 * b_1; });
323
+ setTransformFactors({ a: a_1, b: b_1 });
324
+ nv.transformA = a_1;
325
+ nv.transformB = b_1;
326
+ nv.power = power;
327
+ return transformed;
328
+ }
329
+ else {
330
+ // If range is not smaller than 10E-2, return the original array
331
+ setTransformFactors({ a: 1, b: 0 });
332
+ nv.transformA = 1;
333
+ nv.transformB = 0;
334
+ nv.power = undefined;
335
+ return numbers;
336
+ }
337
+ }
338
+ function verifyComplex(volume) {
339
+ volume.real = volume.img;
340
+ setComplexMode('absolute');
341
+ // Ensure volume.imaginary is defined and has the same length as volume.img
342
+ if (!volume.imaginary || volume.imaginary.length !== volume.img.length) {
343
+ setComplexOptions(['absolute', 'real']);
344
+ // Initialize absolute and phase arrays
345
+ volume.absolute = new volume.img.constructor(volume.img.length);
346
+ // Calculate absolute and phase values
347
+ for (var i = 0; i < volume.img.length; i++) {
348
+ var realPart = volume.real[i];
349
+ // Calculate the absolute value (magnitude)
350
+ volume.absolute[i] = Math.sqrt(realPart * realPart);
351
+ }
352
+ return false;
353
+ }
354
+ var allZero = true;
355
+ // Test for imaginary nulls
356
+ for (var i = 0; i < volume.img.length; i++) {
357
+ if (volume.imaginary[i] !== 0) {
358
+ allZero = false;
359
+ break;
360
+ }
361
+ }
362
+ // Initialize absolute and phase arrays
363
+ volume.absolute = new Float32Array(volume.img.length);
364
+ volume.phase = new Float32Array(volume.img.length);
365
+ // Calculate absolute and phase values
366
+ for (var i = 0; i < volume.img.length; i++) {
367
+ var realPart = volume.real[i];
368
+ var imaginaryPart = volume.imaginary[i];
369
+ // Calculate the absolute value (magnitude)
370
+ volume.absolute[i] = Math.sqrt(realPart * realPart + imaginaryPart * imaginaryPart);
371
+ // Calculate the phase (argument)
372
+ volume.phase[i] = Math.atan2(imaginaryPart, realPart);
373
+ }
374
+ setComplexOptions((allZero) ? ['absolute', 'real'] : ['absolute', 'imaginary', 'real', 'phase']);
375
+ return !allZero;
376
+ }
377
+ function nvSetDisplayedVoxels(voxelType) {
378
+ setComplexMode(voxelType);
379
+ var volume = nv.volumes[0];
380
+ switch (voxelType) {
381
+ case 'phase':
382
+ volume.img = checkRange(volume.phase);
383
+ break;
384
+ case 'absolute':
385
+ volume.img = checkRange(volume.absolute);
386
+ break;
387
+ case 'real':
388
+ volume.img = checkRange(volume.real);
389
+ break;
390
+ case 'imaginary':
391
+ volume.img = checkRange(volume.imaginary);
392
+ break;
393
+ }
394
+ volume.calMinMax();
395
+ setMin(volume.cal_min);
396
+ setMax(volume.cal_max);
397
+ volume.vox_min = getMin(volume.img);
398
+ volume.vox_max = getMax(volume.img);
399
+ nv.setVolume(volume);
400
+ nv.drawScene();
401
+ resampleImage();
402
+ }
403
+ nv.onLocationChange = function (data) {
404
+ if (data.values[0]) {
405
+ setMMs(__spreadArray([], data.values[0].mm, true)); // ensure new array -> React re-renders
406
+ data.values[0].transformA = nv.transformA;
407
+ data.values[0].transformB = nv.transformB;
408
+ data.values[0].power = nv.power;
409
+ }
410
+ setLocationData(data.values);
411
+ // if(drawingEnabled){
412
+ // setDrawingChanged(true);
413
+ // // resampleImage();
414
+ // }
415
+ // console.log(nv.scene.pan2Dxyzmm);
416
+ };
417
+ nv.onMouseUp = function (data) {
418
+ if (drawingEnabled) {
419
+ setDrawingChanged(true);
420
+ resampleImage();
421
+ }
422
+ };
423
+ /**
424
+ * Way to test all value changes
425
+ */
426
+ nv.onIntensityChange = function () {
427
+ var volume = nv.volumes[0];
428
+ setMin(volume.cal_min);
429
+ setMax(volume.cal_max);
430
+ };
431
+ // nv.createEmptyDrawing();
432
+ // construct an array of <Layer> components. Each layer is a NVImage or NVMesh
433
+ var layerList = layers.map(function (layer, index) {
434
+ return (index === 0) ? ( //Yuelong: we shall expect only one effective layer in this implementation
435
+ _jsx(Layer, { image: layer, nv: nv, nii: props.niis[props.selectedVolume], onColorMapChange: nvUpdateColorMap, onRemoveLayer: nvRemoveLayer, onOpacityChange: nvUpdateLayerOpacity, opacity: opacity, colorMapValues: nv.colormapFromKey(layer.colormap), getColorMapValues: function (colorMapName) {
436
+ return nv.colormapFromKey(colorMapName);
437
+ } }, layer.name)) : undefined;
438
+ });
439
+ function addLayer(file) {
440
+ return __awaiter(this, void 0, void 0, function () {
441
+ var nvimage;
442
+ return __generator(this, function (_a) {
443
+ switch (_a.label) {
444
+ case 0: return [4 /*yield*/, NVImage.loadFromFile({
445
+ file: file
446
+ })];
447
+ case 1:
448
+ nvimage = _a.sent();
449
+ nv.addVolume(nvimage);
450
+ setLayers(__spreadArray([], nv.volumes, true));
451
+ return [2 /*return*/];
452
+ }
453
+ });
454
+ });
455
+ }
456
+ function toggleSettings() {
457
+ setOpenSettings(!openSettings);
458
+ }
459
+ function toggleLayers() {
460
+ setOpenLayers(!openLayers);
461
+ }
462
+ function toggleLocationTable() {
463
+ setLocationTableVisible(!locationTableVisible);
464
+ }
465
+ function toggleROIVisible() {
466
+ if (roiVisible) {
467
+ setDrawingOpacity(nv.drawOpacity);
468
+ setROIVisible(false);
469
+ nv.setDrawOpacity(0);
470
+ resampleImage();
471
+ }
472
+ else {
473
+ nv.setDrawOpacity(drawingOpacity);
474
+ setROIVisible(true);
475
+ resampleImage();
476
+ }
477
+ }
478
+ function nvUpdateDrawingOpacity(opacity) {
479
+ setDrawingOpacity(opacity);
480
+ if (roiVisible) {
481
+ nv.setDrawOpacity(opacity);
482
+ }
483
+ }
484
+ function nvUpdateOpacity(a) {
485
+ console.log("Opacity = " + a);
486
+ setopacity(a);
487
+ var n = nv.volumes.length;
488
+ for (var i = 0; i < n; i++) {
489
+ nv.volumes[i].opacity = a;
490
+ }
491
+ nv.updateGLVolume();
492
+ }
493
+ function nvToggleLabelVisible() {
494
+ if (textsVisible) {
495
+ nv.hideText = true;
496
+ nv.drawScene();
497
+ setTextsVisible(false);
498
+ }
499
+ else {
500
+ nv.hideText = false;
501
+ nv.drawScene();
502
+ setTextsVisible(true);
503
+ }
504
+ }
505
+ var _29 = useState("pan"), dragMode = _29[0], setDragMode = _29[1];
506
+ function nvSetDragMode(dragMode) {
507
+ switch (dragMode) {
508
+ case "none":
509
+ nv.opts.dragMode = nv.dragModes.none;
510
+ break;
511
+ case "contrast":
512
+ console.log('setting drag mode to contrast');
513
+ nv.opts.dragMode = nv.dragModes.contrast;
514
+ break;
515
+ case "measurement":
516
+ nv.opts.dragMode = nv.dragModes.measurement;
517
+ break;
518
+ case "pan":
519
+ // nv.opts.dragMode = 3;
520
+ nv.opts.dragMode = nv.dragModes.pan;
521
+ break;
522
+ }
523
+ // nv.drawScene();
524
+ setDragMode(dragMode);
525
+ }
526
+ function nvSaveImage() {
527
+ nv.saveImage({
528
+ filename: 'roi.nii',
529
+ isSaveDrawing: true
530
+ });
531
+ }
532
+ function nvUpdateDrawingEnabled() {
533
+ setDrawingEnabled(!drawingEnabled);
534
+ nv.setDrawingEnabled(!drawingEnabled);
535
+ nv.drawScene();
536
+ }
537
+ function nvSetDrawingEnabled(enabled) {
538
+ setDrawingEnabled(enabled);
539
+ nv.setDrawingEnabled(enabled);
540
+ nv.drawScene();
541
+ }
542
+ function nvUpdateDrawPen(a) {
543
+ var raw = Number(a.target.value);
544
+ setDrawPen(raw);
545
+ var penValue = raw;
546
+ nv.opts.penBounds = 0;
547
+ nv.setPenValue(penValue & 7, penValue > 0);
548
+ if (penValue == 8) {
549
+ nv.setPenValue(0, true);
550
+ }
551
+ }
552
+ function nvUpdateDrawOpacity(a) {
553
+ setDrawOpacity(a);
554
+ nv.setDrawOpacity(a);
555
+ }
556
+ function nvUpdateCrosshairColor(rgb01, a) {
557
+ if (a === void 0) { a = 1; }
558
+ setCrosshairColor(__spreadArray(__spreadArray([], rgb01, true), [a], false));
559
+ nv.setCrosshairColor(__spreadArray(__spreadArray([], rgb01, true), [a], false));
560
+ }
561
+ function nvUpdateOrientCube() {
562
+ nv.opts.isOrientCube = !orientCube;
563
+ setOrientCube(!orientCube);
564
+ nv.drawScene();
565
+ }
566
+ function nvUpdateHighDPI() {
567
+ nv.setHighResolutionCapable(!highDPI);
568
+ setHighDPI(!highDPI);
569
+ }
570
+ function nvUpdateMultiplanarPadPixels(v) {
571
+ nv.opts.multiplanarPadPixels = v;
572
+ setMultiplanarPadPixels(v);
573
+ nv.drawScene();
574
+ }
575
+ function nvUpdateRuler() {
576
+ nv.opts.isRuler = !ruler;
577
+ setRuler(!ruler);
578
+ nv.drawScene();
579
+ }
580
+ function nvUpdateSagittalNoseLeft() {
581
+ nv.opts.sagittalNoseLeft = !sagittalNoseLeft;
582
+ setSagittalNoseLeft(!sagittalNoseLeft);
583
+ nv.drawScene();
584
+ }
585
+ function nvUpdateRulerWidth(v) {
586
+ nv.opts.rulerWidth = v;
587
+ setRulerWidth(v);
588
+ nv.drawScene();
589
+ }
590
+ function nvUpdateRulerOpacity(a) {
591
+ nv.opts.rulerColor = [
592
+ rulerColor[0],
593
+ rulerColor[1],
594
+ rulerColor[2],
595
+ a
596
+ ];
597
+ setRulerOpacity(a);
598
+ nv.drawScene();
599
+ }
600
+ function nvUpdateLongTouchTimeout(v) {
601
+ nv.opts.longTouchTimeout = v;
602
+ setLongTouchTimeout(v);
603
+ }
604
+ function nvUpdateDoubleTouchTimeout(v) {
605
+ nv.opts.doubleTouchTimeout = v;
606
+ setDoubleTouchTimeout(v);
607
+ }
608
+ function nvUpdateDragToMeasure() {
609
+ nv.opts.isDragShowsMeasurementTool = !dragToMeasure;
610
+ setDragToMeasure(!dragToMeasure);
611
+ }
612
+ function nvUpdateMaxDrawUndoBitmaps(v) {
613
+ nv.opts.maxDrawUndoBitmaps = v;
614
+ setMaxDrawUndoBitmaps(v);
615
+ }
616
+ function nvUpdateBackColor(rgb01, a) {
617
+ if (a === void 0) { a = 1; }
618
+ setBackColor(__spreadArray(__spreadArray([], rgb01, true), [a], false));
619
+ nv.opts.backColor = __spreadArray(__spreadArray([], rgb01, true), [a], false);
620
+ nv.drawScene();
621
+ }
622
+ function nvUpdateRulerColor(rgb01, a) {
623
+ if (a === void 0) { a = 1; }
624
+ setRulerColor(__spreadArray(__spreadArray([], rgb01, true), [a], false));
625
+ nv.opts.rulerColor = __spreadArray(__spreadArray([], rgb01, true), [a], false);
626
+ if (!ruler) {
627
+ nv.opts.isRuler = !ruler;
628
+ setRuler(!ruler);
629
+ }
630
+ nv.drawScene();
631
+ }
632
+ function nvUpdateClipPlaneColor(rgb01, a) {
633
+ if (a === void 0) { a = 1; }
634
+ setClipPlaneColor(__spreadArray(__spreadArray([], rgb01, true), [a], false));
635
+ nv.opts.clipPlaneColor = __spreadArray(__spreadArray([], rgb01, true), [a], false);
636
+ setClipPlane(true);
637
+ nv.setClipPlane([0, 270, 0]); //left
638
+ nv.updateGLVolume();
639
+ }
640
+ function nvUpdateClipPlane() {
641
+ if (!clipPlane) {
642
+ setClipPlane(true);
643
+ nv.setClipPlane([0, 270, 0]); //left
644
+ }
645
+ else {
646
+ setClipPlane(false);
647
+ nv.setClipPlane([2, 0, 0]); //none
648
+ }
649
+ }
650
+ function nvUpdateColorBar() {
651
+ setColorBar(!colorBar);
652
+ nv.opts.isColorbar = !colorBar;
653
+ nv.drawScene();
654
+ }
655
+ function nvUpdateTextSize(v) {
656
+ setTextSize(v);
657
+ nv.opts.textHeight = v;
658
+ nv.drawScene();
659
+ }
660
+ function updateDecimalPrecision(v) {
661
+ setDecimalPrecision(v);
662
+ }
663
+ function nvUpdateWorldSpace() {
664
+ nvUpdateCrosshair3D(!worldSpace);
665
+ setWorldSpace(!worldSpace);
666
+ nv.setSliceMM(!worldSpace);
667
+ // keep sliders synced to the currently displayed crosshair
668
+ try {
669
+ setMMs(nv.frac2mm(nv.scene.crosshairPos));
670
+ }
671
+ catch (_a) { }
672
+ }
673
+ function nvUpdateCornerText() {
674
+ nv.setCornerOrientationText(!cornerText);
675
+ setCornerText(!cornerText);
676
+ }
677
+ function nvUpdateCrosshair3D() {
678
+ nv.opts.show3Dcrosshair = !crosshair3D;
679
+ nv.updateGLVolume();
680
+ setCrosshair3D(!crosshair3D);
681
+ }
682
+ function nvUpdateCrosshair() {
683
+ nv.opts.crosshairWidth = showCrosshair ? 0 : 1;
684
+ nv.drawScene();
685
+ setShowCrosshair(!showCrosshair);
686
+ }
687
+ function nvUpdateRadiological() {
688
+ nv.setRadiologicalConvention(!radiological);
689
+ setRadiological(!radiological);
690
+ }
691
+ function nvUpdateCrosshairOpacity(a) {
692
+ nv.setCrosshairColor([
693
+ crosshairColor[0],
694
+ crosshairColor[1],
695
+ crosshairColor[2],
696
+ a
697
+ ]);
698
+ setCrosshairOpacity(a);
699
+ }
700
+ function nvUpdateClipPlaneOpacity(a) {
701
+ nv.opts.clipPlaneColor = [
702
+ clipPlaneColor[0],
703
+ clipPlaneColor[1],
704
+ clipPlaneColor[2],
705
+ a
706
+ ];
707
+ setClipPlaneOpacity(a);
708
+ nv.updateGLVolume();
709
+ }
710
+ function nvUpdateCrosshairSize(w) {
711
+ nv.opts.crosshairWidth = w;
712
+ nv.drawScene();
713
+ }
714
+ var _30 = useState({}), labelMapping = _30[0], setLabelMapping = _30[1];
715
+ function resampleImage(mapping) {
716
+ if (mapping === void 0) { mapping = labelMapping; }
717
+ var el = typeof document !== "undefined" ? document.getElementById("histoplot") : null;
718
+ var next = resampleNiivueRoiHistogram({
719
+ nv: nv,
720
+ labelMapping: mapping,
721
+ plotRoot: el
722
+ });
723
+ if (next !== null) {
724
+ setROIs(next);
725
+ }
726
+ }
727
+ function nvUpdateSelectionBoxColor(rgb01) {
728
+ setSelectionBoxColor(__spreadArray(__spreadArray([], rgb01, true), [0.5], false));
729
+ nv.setSelectionBoxColor(__spreadArray(__spreadArray([], rgb01, true), [0.5], false));
730
+ }
731
+ var _31 = React.useState('axial'), sliceType = _31[0], setSliceType = _31[1];
732
+ function nvUpdateSliceType(newSliceType) {
733
+ setSliceType(newSliceType);
734
+ if (newSliceType === 'axial') {
735
+ nv.setSliceType(nv.sliceTypeAxial);
736
+ }
737
+ else if (newSliceType === 'coronal') {
738
+ nv.setSliceType(nv.sliceTypeCoronal);
739
+ }
740
+ else if (newSliceType === 'sagittal') {
741
+ nv.setSliceType(nv.sliceTypeSagittal);
742
+ }
743
+ else if (newSliceType === 'multi') {
744
+ nv.setSliceType(nv.sliceTypeMultiplanar);
745
+ }
746
+ else if (newSliceType === '3d') {
747
+ nv.setSliceType(nv.sliceTypeRender);
748
+ }
749
+ nvSetDragMode(dragMode); // some Niivue builds reset interaction on slice change
750
+ // Re-apply world/voxel mode and last crosshair after slice type changes
751
+ nv.setSliceMM(worldSpace);
752
+ //applySavedCrosshairIfAny();
753
+ }
754
+ function nvUpdateLayerOpacity(a) {
755
+ setopacity(a);
756
+ nv.updateGLVolume();
757
+ }
758
+ function nvUpdateColorMap(id, clr) {
759
+ nv.setColorMap(id, clr);
760
+ var volume = nv.volumes[0];
761
+ setMin(volume.cal_min);
762
+ setMax(volume.cal_max);
763
+ }
764
+ function nvRemoveLayer(imageToRemove) {
765
+ nv.removeVolume(imageToRemove);
766
+ setLayers(__spreadArray([], nv.volumes, true));
767
+ }
768
+ function stylingProxy(nii) {
769
+ if (nii.dim === 2) {
770
+ nvUpdateSliceType('axial');
771
+ setShowCrosshair(false);
772
+ setTextsVisible(false);
773
+ nv.opts.crosshairWidth = 0;
774
+ nv.hideText = true;
775
+ setTimeout(function () {
776
+ nv.setCenteredZoom(0.7);
777
+ }, 300);
778
+ }
779
+ else {
780
+ // nvUpdateSliceType('multi');
781
+ nvUpdateSliceType(sliceType);
782
+ setShowCrosshair(false);
783
+ setTextsVisible(false);
784
+ nv.opts.crosshairWidth = 0;
785
+ nv.hideText = true;
786
+ }
787
+ }
788
+ var selectVolume = function (volumeIndex) { return __awaiter(_this, void 0, void 0, function () {
789
+ var openVolume;
790
+ var _this = this;
791
+ return __generator(this, function (_a) {
792
+ openVolume = function () { return __awaiter(_this, void 0, void 0, function () {
793
+ var vol, e_1;
794
+ var _a;
795
+ return __generator(this, function (_b) {
796
+ switch (_b.label) {
797
+ case 0:
798
+ nv.closeDrawing();
799
+ setDrawingChanged(false);
800
+ if (drawingEnabled)
801
+ nvUpdateDrawingEnabled();
802
+ vol = nv.volumes[0];
803
+ savedViewStateRef.current = {
804
+ pan2Dxyzmm: __spreadArray([], nv.scene.pan2Dxyzmm, true),
805
+ gamma: gamma,
806
+ opacity: opacity,
807
+ colormap: (_a = vol === null || vol === void 0 ? void 0 : vol.colormap) !== null && _a !== void 0 ? _a : 'gray'
808
+ };
809
+ if (props.niis[selectedVolume] !== undefined) {
810
+ nv.removeVolume(niiToVolume(props.niis[selectedVolume]));
811
+ }
812
+ _b.label = 1;
813
+ case 1:
814
+ _b.trys.push([1, 3, , 4]);
815
+ return [4 /*yield*/, nv.loadVolumes([niiToVolume(props.niis[volumeIndex])])];
816
+ case 2:
817
+ _b.sent();
818
+ nvSetDragMode(dragMode); // re-apply user's drag mode after Niivue resets
819
+ // Re-apply world/voxel mode and last crosshair after loading a new volume
820
+ nv.setSliceMM(worldSpace);
821
+ //applySavedCrosshairIfAny();
822
+ // ensure engine mode matches the remembered selection
823
+ nvUpdateSliceType(sliceType);
824
+ nv.opts.crosshairWidth = showCrosshair ? 1 : 0;
825
+ return [3 /*break*/, 4];
826
+ case 3:
827
+ e_1 = _b.sent();
828
+ setWarning("Error loading results, please check internet connectivity");
829
+ setWarningOpen(true);
830
+ setTimeout(function () {
831
+ setWarningOpen(false);
832
+ setWarning("");
833
+ }, 2500);
834
+ return [2 /*return*/];
835
+ case 4:
836
+ setSelectedVolume(volumeIndex);
837
+ setSelectedDrawingLayer('');
838
+ return [2 /*return*/];
839
+ }
840
+ });
841
+ }); };
842
+ // In case that changes has been made
843
+ if (drawingChanged) {
844
+ setWarningConfirmationCallback(function () { return (function () {
845
+ saveDrawingLayer(function () { return __awaiter(_this, void 0, void 0, function () {
846
+ var _a;
847
+ return __generator(this, function (_b) {
848
+ switch (_b.label) {
849
+ case 0: return [4 /*yield*/, ((_a = props.refreshPipelineRois) === null || _a === void 0 ? void 0 : _a.call(props))];
850
+ case 1:
851
+ _b.sent();
852
+ setSaving(false);
853
+ openVolume();
854
+ return [2 /*return*/];
855
+ }
856
+ });
857
+ }); }, function () {
858
+ setSaving(true);
859
+ });
860
+ }); });
861
+ setWarningCancelCallback(function () { return (function () {
862
+ openVolume();
863
+ }); });
864
+ setConfirmationOpen(true);
865
+ }
866
+ else
867
+ openVolume();
868
+ return [2 /*return*/];
869
+ });
870
+ }); };
871
+ var _32 = useState(''), selectedROI = _32[0], setSelectedDrawingLayer = _32[1];
872
+ var _33 = useState(false), saveDialogOpen = _33[0], setSaveDialogOpen = _33[1];
873
+ var _34 = useState(function () {
874
+ }), saveConfirmCallback = _34[0], setSaveConfirmCallback = _34[1];
875
+ var _35 = useState(false), confirmationOpen = _35[0], setConfirmationOpen = _35[1];
876
+ var _36 = useState(function () { }), warningConfirmationCallback = _36[0], setWarningConfirmationCallback = _36[1];
877
+ var _37 = useState(function () { }), warningCancelCallback = _37[0], setWarningCancelCallback = _37[1];
878
+ var _38 = useState(false), drawingChanged = _38[0], setDrawingChanged = _38[1];
879
+ // When new drawing strokes are made on top of a saved ROI, reset the dropdown
880
+ // so it shows the "ROI Layer" placeholder instead of the stale saved name.
881
+ React.useEffect(function () {
882
+ if (drawingChanged && selectedROI !== '') {
883
+ setSelectedDrawingLayer('');
884
+ }
885
+ }, [drawingChanged]);
886
+ var setLabelAlias = function (label, alias) {
887
+ labelMapping[label] = alias;
888
+ setLabelMapping(labelMapping);
889
+ resampleImage(labelMapping);
890
+ };
891
+ var zipAndSendDrawingLayer = function (uploadURL, filename, blob) {
892
+ return __awaiter(this, void 0, void 0, function () {
893
+ var zip, descriptor, content, file;
894
+ return __generator(this, function (_a) {
895
+ switch (_a.label) {
896
+ case 0:
897
+ zip = new JSZip();
898
+ descriptor = {
899
+ "data": [
900
+ {
901
+ "filename": "".concat(filename, ".nii"),
902
+ "id": 1,
903
+ "name": filename,
904
+ "type": 'image',
905
+ // "numpyPixelType": "complex64",
906
+ // "pixelType": "complex"
907
+ "labelMapping": labelMapping
908
+ }
909
+ ]
910
+ };
911
+ zip.file("info.json", JSON.stringify(descriptor));
912
+ zip.file("".concat(filename, ".nii"), blob, { base64: true });
913
+ return [4 /*yield*/, zip.generateAsync({ type: "blob" })];
914
+ case 1:
915
+ content = _a.sent();
916
+ file = new File([content], filename, {
917
+ type: content.type,
918
+ lastModified: Date.now()
919
+ });
920
+ // Upload to bucket
921
+ return [4 /*yield*/, axios.put(uploadURL, file, {
922
+ headers: {
923
+ 'Content-Type': "application/octet-stream"
924
+ }
925
+ })];
926
+ case 2:
927
+ // Upload to bucket
928
+ _a.sent();
929
+ return [2 /*return*/];
930
+ }
931
+ });
932
+ });
933
+ };
934
+ /** @param mergeIntoExisting - if true (ROI upload), merge into current drawing; if false (pick saved ROI), replace */
935
+ var unzipAndRenderDrawingLayer = function (accessURL, mergeIntoExisting) {
936
+ if (mergeIntoExisting === void 0) { mergeIntoExisting = false; }
937
+ return __awaiter(_this, void 0, void 0, function () {
938
+ var response, blob, zip, fileInfo, content, info, niiFilePath, importedLabels, niiDrawing, base64, mergeResult, merged, _i, _a, _b, oldL, newId, oldN, newN, alias;
939
+ var _c, _d;
940
+ return __generator(this, function (_e) {
941
+ switch (_e.label) {
942
+ case 0: return [4 /*yield*/, fetch(accessURL)];
943
+ case 1:
944
+ response = _e.sent();
945
+ // Check if the request was successful
946
+ if (!response.ok) {
947
+ props.warn('Failed to load the requested ROI Layer (was not properly saved)');
948
+ return [2 /*return*/];
949
+ }
950
+ return [4 /*yield*/, response.blob()];
951
+ case 2:
952
+ blob = _e.sent();
953
+ zip = new JSZip();
954
+ return [4 /*yield*/, zip.loadAsync(blob)];
955
+ case 3:
956
+ _e.sent();
957
+ fileInfo = zip.file("info.json");
958
+ if (!fileInfo) return [3 /*break*/, 12];
959
+ return [4 /*yield*/, fileInfo.async("string")];
960
+ case 4:
961
+ content = _e.sent();
962
+ info = JSON.parse(content);
963
+ niiFilePath = info.data[0].filename;
964
+ importedLabels = info.data[0].labelMapping || {};
965
+ niiDrawing = zip.file(niiFilePath);
966
+ if (!niiDrawing) return [3 /*break*/, 10];
967
+ return [4 /*yield*/, niiDrawing.async("base64")];
968
+ case 5:
969
+ base64 = _e.sent();
970
+ console.log(niiFilePath);
971
+ if (!(mergeIntoExisting && typeof nv.mergeDrawingFromBase64 === 'function')) return [3 /*break*/, 7];
972
+ return [4 /*yield*/, nv.mergeDrawingFromBase64(niiFilePath, base64)];
973
+ case 6:
974
+ mergeResult = _e.sent();
975
+ if (!mergeResult || !mergeResult.ok) {
976
+ props.warn('Failed to merge ROI upload (invalid file or size mismatch with volume).');
977
+ return [2 /*return*/, null];
978
+ }
979
+ if (mergeResult.importLabelRemap === null) {
980
+ setLabelMapping(importedLabels);
981
+ resampleImage(importedLabels);
982
+ }
983
+ else {
984
+ merged = __assign({}, labelMapping);
985
+ for (_i = 0, _a = Object.entries(mergeResult.importLabelRemap); _i < _a.length; _i++) {
986
+ _b = _a[_i], oldL = _b[0], newId = _b[1];
987
+ oldN = Number(oldL);
988
+ newN = Number(newId);
989
+ alias = oldN === newN
990
+ ? ((_d = (_c = importedLabels[oldL]) !== null && _c !== void 0 ? _c : importedLabels[String(oldL)]) !== null && _d !== void 0 ? _d : String(newId))
991
+ : String(newId);
992
+ merged[String(newId)] = alias;
993
+ }
994
+ setLabelMapping(merged);
995
+ resampleImage(merged);
996
+ }
997
+ return [3 /*break*/, 9];
998
+ case 7: return [4 /*yield*/, nv.loadDrawingFromBase64(niiFilePath, base64)];
999
+ case 8:
1000
+ _e.sent();
1001
+ setLabelMapping(importedLabels);
1002
+ resampleImage(importedLabels);
1003
+ _e.label = 9;
1004
+ case 9: return [3 /*break*/, 11];
1005
+ case 10:
1006
+ console.log("".concat(niiFilePath, " not found in the ZIP file."));
1007
+ return [2 /*return*/, null];
1008
+ case 11: return [3 /*break*/, 13];
1009
+ case 12:
1010
+ console.log("info.json not found in the ZIP file.");
1011
+ return [2 /*return*/, null];
1012
+ case 13: return [2 /*return*/];
1013
+ }
1014
+ });
1015
+ });
1016
+ };
1017
+ // Normalize ROI.data.sliceMM into [number, number, number]
1018
+ function getSavedSliceMMFromROI(roi) {
1019
+ var _a;
1020
+ try {
1021
+ var raw = (_a = roi === null || roi === void 0 ? void 0 : roi.data) === null || _a === void 0 ? void 0 : _a.sliceMM;
1022
+ if (!raw)
1023
+ return null;
1024
+ // Case A: already an array
1025
+ if (Array.isArray(raw)) {
1026
+ var arr = raw.slice(0, 3).map(function (v) { return typeof v === 'string' ? parseFloat(v) : v; });
1027
+ return arr.every(Number.isFinite) ? arr : null;
1028
+ }
1029
+ // Case B: object like {0:'125.29', 1:'162.80', 2:'-18.83', 3:1}
1030
+ if (typeof raw === 'object') {
1031
+ var arr = [raw[0], raw[1], raw[2]].map(function (v) { return typeof v === 'string' ? parseFloat(v) : v; });
1032
+ return arr.every(Number.isFinite) ? arr : null;
1033
+ }
1034
+ return null;
1035
+ }
1036
+ catch (_b) {
1037
+ return null;
1038
+ }
1039
+ }
1040
+ var selectDrawingLayer = function (roiIndex) { return __awaiter(_this, void 0, void 0, function () {
1041
+ var sliceMM, frac;
1042
+ var _a, _b, _c, _d;
1043
+ return __generator(this, function (_e) {
1044
+ switch (_e.label) {
1045
+ case 0:
1046
+ // console.log(nv.drawBitmap);
1047
+ console.log(props.rois[roiIndex].link);
1048
+ return [4 /*yield*/, unzipAndRenderDrawingLayer(props.rois[roiIndex].link)];
1049
+ case 1:
1050
+ _e.sent();
1051
+ // If this ROI carries a saved slice position, jump to it
1052
+ try {
1053
+ // Log raw & normalized for debugging
1054
+ console.log('ROI sliceMM (raw):', (_c = (_b = (_a = props.rois) === null || _a === void 0 ? void 0 : _a[roiIndex]) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.sliceMM);
1055
+ sliceMM = getSavedSliceMMFromROI((_d = props.rois) === null || _d === void 0 ? void 0 : _d[roiIndex]);
1056
+ console.log('ROI sliceMM (normalized):', sliceMM);
1057
+ if (sliceMM) {
1058
+ // Ensure engine uses the currently selected coordinate system
1059
+ nv.setSliceMM(worldSpace);
1060
+ frac = nv.mm2frac(sliceMM);
1061
+ console.log(1, frac);
1062
+ // if (Array.isArray(frac) && frac.length === 3) {
1063
+ console.log(2, frac);
1064
+ nv.scene.crosshairPos = frac;
1065
+ nv.drawScene(); ////;
1066
+ // use fresh array to guarantee React state update
1067
+ setMMs(nv.frac2mm(frac));
1068
+ // }
1069
+ }
1070
+ }
1071
+ catch (e) {
1072
+ // Intentionally ignore, if conversion fails we simply skip re-applying
1073
+ }
1074
+ setSelectedDrawingLayer(roiIndex);
1075
+ setDrawingChanged(false);
1076
+ return [2 /*return*/];
1077
+ }
1078
+ });
1079
+ }); };
1080
+ var unpackROI = function (accessURL) { return __awaiter(_this, void 0, void 0, function () {
1081
+ return __generator(this, function (_a) {
1082
+ switch (_a.label) {
1083
+ case 0: return [4 /*yield*/, unzipAndRenderDrawingLayer(accessURL, true)];
1084
+ case 1:
1085
+ _a.sent();
1086
+ setDrawingChanged(false);
1087
+ setSelectedDrawingLayer(props.rois.length);
1088
+ return [2 /*return*/];
1089
+ }
1090
+ });
1091
+ }); };
1092
+ var refreshROI = function () { return __awaiter(_this, void 0, void 0, function () {
1093
+ var roiIndex, load;
1094
+ return __generator(this, function (_a) {
1095
+ roiIndex = selectedROI;
1096
+ console.log(nv.drawBitmap);
1097
+ load = function () {
1098
+ console.log(props.rois[roiIndex].link);
1099
+ console.trace();
1100
+ nv.loadDrawingFromUrl(props.rois[roiIndex].link).then(function (value) {
1101
+ resampleImage();
1102
+ });
1103
+ setSelectedDrawingLayer(roiIndex);
1104
+ setDrawingChanged(false);
1105
+ };
1106
+ load();
1107
+ return [2 /*return*/];
1108
+ });
1109
+ }); };
1110
+ var saveDrawingLayer = function (afterSaveCallback, preSaveCallback) {
1111
+ if (preSaveCallback === void 0) { preSaveCallback = function () { }; }
1112
+ setSaveDialogOpen(true);
1113
+ setSaveConfirmCallback(function () { return (function (filename) { return __awaiter(_this, void 0, void 0, function () {
1114
+ var config, sliceMM, response, originalCreateObjectURL, successful;
1115
+ return __generator(this, function (_a) {
1116
+ switch (_a.label) {
1117
+ case 0:
1118
+ preSaveCallback();
1119
+ config = {
1120
+ headers: {
1121
+ Authorization: "Bearer ".concat(props.accessToken)
1122
+ }
1123
+ };
1124
+ sliceMM = undefined;
1125
+ try {
1126
+ // Niivue keeps crosshair in fractional coords; convert to mm
1127
+ sliceMM = nv.frac2mm(nv.scene.crosshairPos);
1128
+ }
1129
+ catch (e) {
1130
+ // If conversion fails, use the last known mm state if you keep one,
1131
+ // or omit the field, sliceMM stays undefined in that case.
1132
+ console.log("Error with saving crosshair");
1133
+ }
1134
+ return [4 /*yield*/, AuthenticatedHttpClient.post(endpoints.ROI_UPLOAD, {
1135
+ "filename": "".concat(filename),
1136
+ "pipeline_id": props.pipelineID,
1137
+ "type": "image",
1138
+ "contentType": "application/octet-stream",
1139
+ data: sliceMM ? { sliceMM: sliceMM } : {} // persist current slice position (mm) into ROI.data dictionary
1140
+ }, config)];
1141
+ case 1:
1142
+ response = _a.sent();
1143
+ console.log('Save ROI response:', response.data);
1144
+ console.log('Sent ROI payload sliceMM:', sliceMM);
1145
+ originalCreateObjectURL = URL.createObjectURL;
1146
+ // Redefine the method
1147
+ URL.createObjectURL = function (blob) {
1148
+ var _this = this;
1149
+ console.log('saving blob');
1150
+ console.log(blob);
1151
+ zipAndSendDrawingLayer(response.data.upload_url, filename, blob).then(function () { return __awaiter(_this, void 0, void 0, function () {
1152
+ var freshRois, savedIndex;
1153
+ var _a, _b;
1154
+ return __generator(this, function (_c) {
1155
+ switch (_c.label) {
1156
+ case 0:
1157
+ // Update available rois with this callback
1158
+ // props.saveROICallback();
1159
+ setDrawingChanged(false);
1160
+ if (!(afterSaveCallback instanceof Function)) return [3 /*break*/, 2];
1161
+ return [4 /*yield*/, afterSaveCallback()];
1162
+ case 1:
1163
+ _c.sent();
1164
+ _c.label = 2;
1165
+ case 2:
1166
+ freshRois = (_b = (_a = props.getPipelineRois) === null || _a === void 0 ? void 0 : _a.call(props)) !== null && _b !== void 0 ? _b : [];
1167
+ savedIndex = freshRois.findIndex(function (r) { return r.filename === filename; });
1168
+ setSelectedDrawingLayer(savedIndex !== -1 ? savedIndex : freshRois.length - 1);
1169
+ return [2 /*return*/];
1170
+ }
1171
+ });
1172
+ }); });
1173
+ // Call the original method and return its result
1174
+ return 'javascript:void(0);';
1175
+ };
1176
+ successful = nv.saveImage({
1177
+ filename: filename,
1178
+ isSaveDrawing: true
1179
+ });
1180
+ // De-patch
1181
+ URL.createObjectURL = originalCreateObjectURL;
1182
+ return [2 /*return*/];
1183
+ }
1184
+ });
1185
+ }); }); });
1186
+ };
1187
+ var drawToolkitProps = {
1188
+ nv: nv,
1189
+ volumes: props.niis.map(niiToVolume),
1190
+ selectedVolume: selectedVolume,
1191
+ setSelectedVolume: selectVolume,
1192
+ updateDrawPen: nvUpdateDrawPen,
1193
+ drawPen: drawPen,
1194
+ drawingEnabled: drawingEnabled,
1195
+ setDrawingEnabled: nvSetDrawingEnabled,
1196
+ showColorBar: colorBar,
1197
+ toggleColorBar: nvUpdateColorBar,
1198
+ changesMade: drawingChanged,
1199
+ // toggleSampleDistribution,
1200
+ drawUndo: function () {
1201
+ nv.drawUndo();
1202
+ resampleImage();
1203
+ if (nv.drawBitmap && nv.drawBitmap.every(function (v) { return v === 0; })) {
1204
+ setDrawingChanged(false);
1205
+ }
1206
+ },
1207
+ resampleImage: resampleImage,
1208
+ roiVisible: roiVisible,
1209
+ toggleROIVisible: toggleROIVisible,
1210
+ drawingOpacity: drawingOpacity,
1211
+ setDrawingOpacity: nvUpdateDrawingOpacity,
1212
+ setDrawingChanged: setDrawingChanged
1213
+ };
1214
+ return (_jsxs(Box, __assign({ sx: {
1215
+ display: 'flex',
1216
+ flexDirection: 'column',
1217
+ height: '100%',
1218
+ width: '100%',
1219
+ alignItems: 'center',
1220
+ justifyContent: 'center'
1221
+ } }, { children: [_jsxs(SettingsPanel, __assign({ open: openSettings, width: 300, toggleMenu: toggleSettings }, { children: [_jsx(ColorPicker, { colorRGB01: backColor, onSetColor: nvUpdateBackColor, title: 'Background color' }), _jsx(ColorPicker, { colorRGB01: clipPlaneColor, onSetColor: nvUpdateClipPlaneColor, title: 'Clip plane color' }), _jsx(NumberPicker, { value: clipPlaneOpacity, onChange: nvUpdateClipPlaneOpacity, title: 'Clip plane opacity', min: 0, max: 1, step: 0.1 }), _jsx(ColorPicker, { colorRGB01: crosshairColor, onSetColor: nvUpdateCrosshairColor, title: 'Crosshair color' }), _jsx(NumberPicker, { value: crosshairOpacity, onChange: nvUpdateCrosshairOpacity, title: 'Crosshair opacity', min: 0, max: 1, step: 0.1 }), _jsx(ColorPicker, { colorRGB01: selectionBoxColor, onSetColor: nvUpdateSelectionBoxColor, title: 'Selection box color' }), _jsx(NumberPicker, { value: nv.opts.crosshairWidth, onChange: nvUpdateCrosshairSize, title: 'Crosshair size', min: 0, max: 10, step: 1 }), _jsx(NumberPicker, { value: textSize, onChange: nvUpdateTextSize, title: 'Text size', min: 0, max: 0.2, step: 0.01 }), _jsx(ColorPicker, { colorRGB01: rulerColor, onSetColor: nvUpdateRulerColor, title: 'Ruler color' }), _jsx(NumberPicker, { value: rulerWidth, onChange: nvUpdateRulerWidth, title: 'Ruler thickness', min: 0, max: 10, step: 1 }), _jsx(NumberPicker, { value: rulerOpacity, onChange: nvUpdateRulerOpacity, title: 'Ruler opacity', min: 0, max: 1, step: 0.1 }), _jsx(NumberPicker, { value: opacity, onChange: nvUpdateOpacity, title: 'Opacity', min: 0, max: 1, step: 0.01 }), _jsx(NumberPicker, { value: drawOpacity, onChange: nvUpdateDrawOpacity, title: 'Draw Opacity', min: 0, max: 1, step: 0.01 }), _jsx("label", __assign({ htmlFor: "drawPen" }, { children: "Draw color:" })), _jsxs("select", __assign({ name: "drawPen", id: "drawPen", onChange: nvUpdateDrawPen, value: drawPen }, { children: [_jsx("option", __assign({ value: "0" }, { children: "Erase" })), _jsx("option", __assign({ value: "1" }, { children: "Red" })), _jsx("option", __assign({ value: "2" }, { children: "Green" })), _jsx("option", __assign({ value: "3" }, { children: "Blue" })), _jsx("option", __assign({ value: "8" }, { children: "Filled Erase" })), _jsx("option", __assign({ value: "9" }, { children: "Filled Red" })), _jsx("option", __assign({ value: "10" }, { children: "Filled Green" })), _jsx("option", __assign({ value: "11" }, { children: "Filled Blue" })), _jsx("option", __assign({ value: "12" }, { children: "Erase Selected Cluster" }))] })), _jsx(Button, __assign({ title: 'Save image', onClick: nvSaveImage }, { children: "Save image" })), _jsx(NVSwitch, { checked: locationTableVisible, title: 'Location table', onChange: toggleLocationTable }), _jsx(NVSwitch, { checked: drawingEnabled, title: 'Enable drawing', onChange: nvUpdateDrawingEnabled }), _jsx(NVSwitch, { checked: orientCube, title: 'Orientation cube', onChange: nvUpdateOrientCube }), _jsx(NVSwitch, { checked: ruler, title: 'Ruler', onChange: nvUpdateRuler }), _jsx(NVSwitch, { checked: clipPlane, title: 'Clip plane', onChange: nvUpdateClipPlane }), _jsx(NVSwitch, { checked: cornerText, title: 'Corner text', onChange: nvUpdateCornerText }), _jsx(NVSwitch, { checked: radiological, title: 'radiological', onChange: nvUpdateRadiological }), _jsx(NVSwitch, { checked: crosshair3D, title: '3D crosshair', onChange: nvUpdateCrosshair3D }), _jsx(NVSwitch, { checked: colorBar, title: 'Show color bar', onChange: nvUpdateColorBar }), _jsx(NVSwitch, { checked: worldSpace, title: 'World space', onChange: nvUpdateWorldSpace }), _jsx(NVSwitch, { checked: sagittalNoseLeft, title: 'Nose left', onChange: nvUpdateSagittalNoseLeft }), _jsx(NVSwitch, { checked: dragToMeasure, title: 'Drag to measure', onChange: nvUpdateDragToMeasure }), _jsx(NVSwitch, { checked: highDPI, title: 'High DPI', onChange: nvUpdateHighDPI }), _jsx(NumberPicker, { value: decimalPrecision, onChange: updateDecimalPrecision, title: 'Decimal precision', min: 0, max: 8, step: 1 }), _jsx(NumberPicker, { value: multiplanarPadPixels, onChange: nvUpdateMultiplanarPadPixels, title: 'Multiplanar padding', min: 0, max: 20, step: 2 }), _jsx(NumberPicker, { value: maxDrawUndoBitmaps, onChange: nvUpdateMaxDrawUndoBitmaps, title: 'Max Draw Undos', min: 8, max: 28, step: 1 }), _jsx(NumberPicker, { value: longTouchTimeout, onChange: nvUpdateLongTouchTimeout, title: 'Long touch timeout msec', min: 1000, max: 5000, step: 100 }), _jsx(NumberPicker, { value: doubleTouchTimeout, onChange: nvUpdateDoubleTouchTimeout, title: 'Double touch timeout msec', min: 500, max: 999, step: 25 })] })), _jsx(LayersPanel, __assign({ open: openLayers, width: 320, onToggleMenu: toggleLayers, onAddLayer: addLayer }, { children: layerList })), _jsx(Toolbar, { nv: nv, nvUpdateSliceType: nvUpdateSliceType, sliceType: sliceType, toggleSettings: toggleSettings, toggleLayers: toggleLayers, volumes: props.niis.map(niiToVolume), selectedVolume: selectedVolume, setSelectedVolume: selectVolume, showColorBar: colorBar, toggleColorBar: nvUpdateColorBar, rois: props.rois, selectedROI: selectedROI, refreshROI: refreshROI, setSelectedROI: selectDrawingLayer, toggleShowCrosshair: nvUpdateCrosshair, showCrosshair: showCrosshair, dragMode: dragMode, setDragMode: nvSetDragMode, toggleRadiological: nvUpdateRadiological, radiological: radiological, saveROI: saveDrawingLayer, complexMode: complexMode, setComplexMode: nvSetDisplayedVoxels, complexOptions: complexOptions, labelsVisible: textsVisible, toggleLabelsVisible: nvToggleLabelVisible, saving: saving, setSaving: setSaving, drawingChanged: drawingChanged, resampleImage: resampleImage, accessToken: props.accessToken, pipelineId: props.pipelineID, roiDeleteUrl: props.roiDeleteUrl, refreshPipelineRois: props.refreshPipelineRois }), _jsx(CmrConfirmation, { name: 'New Changes Made', message: "Consider saving your drawing before switching.", open: confirmationOpen, setOpen: setConfirmationOpen, cancellable: true, confirmCallback: warningConfirmationCallback, cancelCallback: warningCancelCallback, cancelText: "Don't save" }), _jsx(CmrEditConfirmation, { name: 'Save drawings', message: 'Please enter the name of the saved drawing', open: saveDialogOpen, setOpen: setSaveDialogOpen, confirmCallback: saveConfirmCallback, cancellable: true, cancelCallback: function () {
1222
+ },
1223
+ // suffix={'.zip'}
1224
+ defaultText: (props.rois[selectedROI] !== undefined ?
1225
+ props.rois[selectedROI].filename : undefined) }), props.niis[selectedVolume] != undefined && _jsx(CloudMrNiivuePanel, { nv: nv, transformFactors: transformFactors, decimalPrecision: decimalPrecision, locationData: locationData, locationTableVisible: locationTableVisible, pipelineID: props.pipelineID, resampleImage: resampleImage, rois: rois, drawToolkitProps: drawToolkitProps, drawShapeTool: drawShapeTool, setDrawShapeTool: setDrawShapeTool, mins: boundMins, maxs: boundMaxs, mms: mms, min: min, max: max, setMin: setMin, setMax: setMax, unzipAndRenderROI: unpackROI, zipAndSendROI: zipAndSendDrawingLayer, setLabelAlias: setLabelAlias, onAfterRoiUpload: function () {
1226
+ var _a;
1227
+ void ((_a = props.refreshPipelineRois) === null || _a === void 0 ? void 0 : _a.call(props));
1228
+ }, gamma: gamma, gammaKey: gammaKey, setGamma: setGamma }, "".concat(selectedVolume))] })));
1229
+ }
1230
+ function niiToVolume(nii) {
1231
+ return {
1232
+ //URL is for NiiVue blob loading
1233
+ url: nii.link,
1234
+ //name is for NiiVue name replacer (needs proper extension like .nii)
1235
+ name: (nii.filename.split('/').pop()),
1236
+ //alias is for user selection in toolbar
1237
+ alias: nii.name
1238
+ };
1239
+ }