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.
- package/dist/CmrComponents/niivue-contrast-adjustments/NiivueContrastAdjustments.d.ts +4 -3
- package/dist/CmrComponents/niivue-contrast-adjustments/NiivueContrastAdjustments.js +5 -5
- package/dist/CmrComponents/niivue-slice-position/NiivueSlicePosition.js +2 -2
- package/dist/CmrComponents/niivue-viewer/CloudMrNiivuePanel.d.ts +38 -0
- package/dist/CmrComponents/niivue-viewer/CloudMrNiivuePanel.js +197 -0
- package/dist/CmrComponents/niivue-viewer/CloudMrNiivueViewer.d.ts +41 -0
- package/dist/CmrComponents/niivue-viewer/CloudMrNiivueViewer.js +1239 -0
- package/dist/CmrComponents/niivue-viewer/ColorPicker.d.ts +1 -0
- package/dist/CmrComponents/niivue-viewer/ColorPicker.js +65 -0
- package/dist/CmrComponents/niivue-viewer/Layer.d.ts +1 -0
- package/dist/CmrComponents/niivue-viewer/Layer.js +122 -0
- package/dist/CmrComponents/niivue-viewer/LayersPanel.d.ts +1 -0
- package/dist/CmrComponents/niivue-viewer/LayersPanel.js +107 -0
- package/dist/CmrComponents/niivue-viewer/Niivue.css +8 -0
- package/dist/CmrComponents/niivue-viewer/NiivuePatcher.d.ts +2 -0
- package/dist/CmrComponents/niivue-viewer/NiivuePatcher.js +1402 -0
- package/dist/CmrComponents/niivue-viewer/NumberPicker.d.ts +1 -0
- package/dist/CmrComponents/niivue-viewer/NumberPicker.js +40 -0
- package/dist/CmrComponents/niivue-viewer/SettingsPanel.d.ts +1 -0
- package/dist/CmrComponents/niivue-viewer/SettingsPanel.js +30 -0
- package/dist/CmrComponents/niivue-viewer/Switch.d.ts +1 -0
- package/dist/CmrComponents/niivue-viewer/Switch.js +27 -0
- package/dist/CmrComponents/niivue-viewer/Toolbar.d.ts +48 -0
- package/dist/CmrComponents/niivue-viewer/Toolbar.js +276 -0
- package/dist/CmrComponents/niivue-viewer/Toolbar.scss +40 -0
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/DrawColorPlatte.d.ts +2 -0
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/DrawColorPlatte.js +61 -0
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/EraserPlatte.d.ts +2 -0
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/EraserPlatte.js +56 -0
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/MaskPlatte.d.ts +2 -0
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/MaskPlatte.js +148 -0
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/MroDrawToolkit.d.ts +1 -0
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/MroDrawToolkit.js +177 -0
- package/dist/CmrComponents/niivue-viewer/niivuePenType.d.ts +10 -0
- package/dist/CmrComponents/niivue-viewer/niivuePenType.js +10 -0
- package/dist/core/common/components/NiivueTools/NiivuePatcher.d.ts +2 -0
- package/dist/core/common/components/NiivueTools/components/ControlThemes.d.ts +1 -0
- package/dist/core/common/components/NiivueTools/components/ControlThemes.js +123 -0
- package/dist/core/common/components/NiivueTools/components/Example.d.ts +10 -0
- package/dist/core/common/components/NiivueTools/components/Example.js +326 -0
- package/dist/core/common/components/NiivueTools/components/ImageList.d.ts +1 -0
- package/dist/core/common/components/NiivueTools/components/ImageList.js +22 -0
- package/dist/core/common/components/NiivueTools/components/ImageListItem.d.ts +1 -0
- package/dist/core/common/components/NiivueTools/components/ImageListItem.js +103 -0
- package/dist/core/common/components/NiivueTools/main.d.ts +1 -0
- package/dist/core/common/components/NiivueTools/main.js +16 -0
- package/dist/core/common/components/NiivueTools/util.d.ts +21 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- 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
|
+
}
|