cloudmr-ux 4.8.8 → 4.9.0

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.
@@ -118,67 +118,69 @@ window.nv = nv;
118
118
  // It is exported so that it can be used in other projects easily
119
119
  export default function CloudMrNiivueViewer(props) {
120
120
  var _this = this;
121
+ var _a, _b;
121
122
  var endpoints = getEndpoints(); // CloudMR core endpoints (mode1/mode2 helper)
122
123
  var selectedVolume = props.selectedVolume;
123
124
  var setSelectedVolume = props.setSelectedVolume;
124
125
  var setWarning = props.setWarning, setWarningOpen = props.setWarningOpen;
125
126
  // const nv = props.nv;
126
- var _a = React.useState(false), openSettings = _a[0], setOpenSettings = _a[1];
127
- var _b = React.useState(false), openLayers = _b[0], setOpenLayers = _b[1];
128
- var _c = React.useState(nv.opts.crosshairColor), crosshairColor = _c[0], setCrosshairColor = _c[1];
129
- var _d = React.useState(nv.opts.selectionBoxColor), selectionBoxColor = _d[0], setSelectionBoxColor = _d[1];
130
- var _e = React.useState(nv.opts.backColor), backColor = _e[0], setBackColor = _e[1];
131
- var _f = React.useState(nv.opts.clipPlaneColor), clipPlaneColor = _f[0], setClipPlaneColor = _f[1];
132
- var _g = React.useState(nv.volumes), layers = _g[0], setLayers = _g[1];
133
- var _h = React.useState(false), cornerText = _h[0], setCornerText = _h[1];
134
- var _j = React.useState(true), radiological = _j[0], setRadiological = _j[1];
135
- var _k = React.useState(false), crosshair3D = _k[0], setCrosshair3D = _k[1];
136
- var _l = React.useState(nv.opts.textHeight), textSize = _l[0], setTextSize = _l[1];
137
- var _m = React.useState(nv.opts.isColorbar), colorBar = _m[0], setColorBar = _m[1];
138
- var _o = React.useState(nv.opts.isSliceMM), worldSpace = _o[0], setWorldSpace = _o[1];
139
- var _p = React.useState(nv.currentClipPlaneIndex > 0 ? true : false), clipPlane = _p[0], setClipPlane = _p[1];
127
+ var _c = React.useState(false), openSettings = _c[0], setOpenSettings = _c[1];
128
+ var _d = React.useState(false), openLayers = _d[0], setOpenLayers = _d[1];
129
+ var _e = React.useState(nv.opts.crosshairColor), crosshairColor = _e[0], setCrosshairColor = _e[1];
130
+ var _f = React.useState(nv.opts.selectionBoxColor), selectionBoxColor = _f[0], setSelectionBoxColor = _f[1];
131
+ var _g = React.useState(nv.opts.backColor), backColor = _g[0], setBackColor = _g[1];
132
+ var _h = React.useState(nv.opts.clipPlaneColor), clipPlaneColor = _h[0], setClipPlaneColor = _h[1];
133
+ var _j = React.useState(nv.volumes), layers = _j[0], setLayers = _j[1];
134
+ var _k = React.useState(false), cornerText = _k[0], setCornerText = _k[1];
135
+ var _l = React.useState(true), radiological = _l[0], setRadiological = _l[1];
136
+ var _m = React.useState(false), crosshair3D = _m[0], setCrosshair3D = _m[1];
137
+ var _o = React.useState(nv.opts.textHeight), textSize = _o[0], setTextSize = _o[1];
138
+ var _p = React.useState(nv.opts.isColorbar), colorBar = _p[0], setColorBar = _p[1];
139
+ var _q = React.useState(nv.opts.isSliceMM), worldSpace = _q[0], setWorldSpace = _q[1];
140
+ var _r = React.useState(nv.currentClipPlaneIndex > 0 ? true : false), clipPlane = _r[0], setClipPlane = _r[1];
140
141
  // TODO: add crosshair size state and setter
141
- var _q = React.useState(1.0), opacity = _q[0], setopacity = _q[1];
142
- var _r = React.useState(nv.opts.drawingEnabled), drawingEnabled = _r[0], setDrawingEnabled = _r[1];
143
- var _s = React.useState(nv.opts.penValue), drawPen = _s[0], setDrawPen = _s[1];
144
- var _t = React.useState(0.8), drawOpacity = _t[0], setDrawOpacity = _t[1];
145
- var _u = React.useState(nv.opts.crosshairColor[3]), crosshairOpacity = _u[0], setCrosshairOpacity = _u[1];
146
- var _v = React.useState(nv.opts.clipPlaneColor[3]), clipPlaneOpacity = _v[0], setClipPlaneOpacity = _v[1];
147
- var _w = React.useState(true), locationTableVisible = _w[0], setLocationTableVisible = _w[1];
148
- var _x = React.useState([]), locationData = _x[0], setLocationData = _x[1];
149
- var _y = React.useState(2), decimalPrecision = _y[0], setDecimalPrecision = _y[1];
150
- var _z = React.useState(nv.opts.isOrientCube), orientCube = _z[0], setOrientCube = _z[1];
151
- var _0 = React.useState(nv.opts.isRuler), ruler = _0[0], setRuler = _0[1];
152
- var _1 = React.useState(nv.opts.multiplanarPadPixels), multiplanarPadPixels = _1[0], setMultiplanarPadPixels = _1[1];
153
- var _2 = React.useState(nv.opts.maxDrawUndoBitmaps), maxDrawUndoBitmaps = _2[0], setMaxDrawUndoBitmaps = _2[1];
154
- var _3 = React.useState(nv.opts.sagittalNoseLeft), sagittalNoseLeft = _3[0], setSagittalNoseLeft = _3[1];
155
- var _4 = React.useState(nv.opts.rulerWidth), rulerWidth = _4[0], setRulerWidth = _4[1];
156
- var _5 = React.useState(nv.opts.longTouchTimeout), longTouchTimeout = _5[0], setLongTouchTimeout = _5[1];
157
- var _6 = React.useState(nv.opts.doubleTouchTimeout), doubleTouchTimeout = _6[0], setDoubleTouchTimeout = _6[1];
158
- var _7 = React.useState(nv.opts.isDragShowsMeasurementTool), dragToMeasure = _7[0], setDragToMeasure = _7[1];
159
- var _8 = React.useState(nv.opts.rulerColor), rulerColor = _8[0], setRulerColor = _8[1];
160
- var _9 = React.useState(nv.opts.rulerColor[3]), rulerOpacity = _9[0], setRulerOpacity = _9[1];
161
- var _10 = React.useState(false), highDPI = _10[0], setHighDPI = _10[1];
162
- var _11 = React.useState([]), rois = _11[0], setROIs = _11[1];
142
+ var _s = React.useState(1.0), opacity = _s[0], setopacity = _s[1];
143
+ var _t = React.useState(nv.opts.drawingEnabled), drawingEnabled = _t[0], setDrawingEnabled = _t[1];
144
+ var _u = React.useState(nv.opts.penValue), drawPen = _u[0], setDrawPen = _u[1];
145
+ var _v = React.useState(0.8), drawOpacity = _v[0], setDrawOpacity = _v[1];
146
+ var _w = React.useState(nv.opts.crosshairColor[3]), crosshairOpacity = _w[0], setCrosshairOpacity = _w[1];
147
+ var _x = React.useState(nv.opts.clipPlaneColor[3]), clipPlaneOpacity = _x[0], setClipPlaneOpacity = _x[1];
148
+ var _y = React.useState(true), locationTableVisible = _y[0], setLocationTableVisible = _y[1];
149
+ var _z = React.useState([]), locationData = _z[0], setLocationData = _z[1];
150
+ var _0 = React.useState(2), decimalPrecision = _0[0], setDecimalPrecision = _0[1];
151
+ var _1 = React.useState(nv.opts.isOrientCube), orientCube = _1[0], setOrientCube = _1[1];
152
+ var _2 = React.useState(nv.opts.isRuler), ruler = _2[0], setRuler = _2[1];
153
+ var _3 = React.useState(nv.opts.multiplanarPadPixels), multiplanarPadPixels = _3[0], setMultiplanarPadPixels = _3[1];
154
+ var _4 = React.useState(nv.opts.maxDrawUndoBitmaps), maxDrawUndoBitmaps = _4[0], setMaxDrawUndoBitmaps = _4[1];
155
+ var _5 = React.useState(nv.opts.sagittalNoseLeft), sagittalNoseLeft = _5[0], setSagittalNoseLeft = _5[1];
156
+ var _6 = React.useState(nv.opts.rulerWidth), rulerWidth = _6[0], setRulerWidth = _6[1];
157
+ var _7 = React.useState(nv.opts.longTouchTimeout), longTouchTimeout = _7[0], setLongTouchTimeout = _7[1];
158
+ var _8 = React.useState(nv.opts.doubleTouchTimeout), doubleTouchTimeout = _8[0], setDoubleTouchTimeout = _8[1];
159
+ var _9 = React.useState(nv.opts.isDragShowsMeasurementTool), dragToMeasure = _9[0], setDragToMeasure = _9[1];
160
+ var _10 = React.useState(nv.opts.rulerColor), rulerColor = _10[0], setRulerColor = _10[1];
161
+ var _11 = React.useState(nv.opts.rulerColor[3]), rulerOpacity = _11[0], setRulerOpacity = _11[1];
162
+ var _12 = React.useState(false), highDPI = _12[0], setHighDPI = _12[1];
163
+ var _13 = React.useState([]), rois = _13[0], setROIs = _13[1];
163
164
  // Persists zoom, gamma and opacity across volume/channel switches so they are not reset
164
165
  var savedViewStateRef = React.useRef(null);
165
- var _12 = React.useState(false), showCrosshair = _12[0], setShowCrosshair = _12[1];
166
- var _13 = useState('absolute'), complexMode = _13[0], setComplexMode = _13[1];
167
- var _14 = useState(['absolute']), complexOptions = _14[0], setComplexOptions = _14[1];
168
- var _15 = useState(true), roiVisible = _15[0], setROIVisible = _15[1];
169
- var _16 = useState(0.8), drawingOpacity = _16[0], setDrawingOpacity = _16[1];
170
- var _17 = useState(0), min = _17[0], setMin = _17[1];
171
- var _18 = useState(1), max = _18[0], setMax = _18[1];
172
- var _19 = useState(false), textsVisible = _19[0], setTextsVisible = _19[1];
173
- var _20 = useState({ a: 1, b: 0 }), transformFactors = _20[0], setTransformFactors = _20[1];
174
- var _21 = useState(false), saving = _21[0], setSaving = _21[1];
175
- var _22 = useState(null), drawShapeTool = _22[0], setDrawShapeTool = _22[1];
176
- var _23 = useState(null), shapeDraft = _23[0], setShapeDraft = _23[1];
177
- var _24 = useState("freehand"), penDrawMode = _24[0], setPenDrawMode = _24[1];
178
- var _25 = useState(0), polylineVertexCount = _25[0], setPolylineVertexCount = _25[1];
179
- var _26 = useState(null), penDraft = _26[0], setPenDraft = _26[1];
180
- var _27 = useState(1), brushSize = _27[0], setBrushSize = _27[1];
181
- var _28 = useState(1), eraserSize = _28[0], setEraserSize = _28[1];
166
+ var loadingVolumeIndexRef = React.useRef(props.selectedVolume);
167
+ var _14 = React.useState(false), showCrosshair = _14[0], setShowCrosshair = _14[1];
168
+ var _15 = useState('absolute'), complexMode = _15[0], setComplexMode = _15[1];
169
+ var _16 = useState(['absolute']), complexOptions = _16[0], setComplexOptions = _16[1];
170
+ var _17 = useState(true), roiVisible = _17[0], setROIVisible = _17[1];
171
+ var _18 = useState(0.8), drawingOpacity = _18[0], setDrawingOpacity = _18[1];
172
+ var _19 = useState(0), min = _19[0], setMin = _19[1];
173
+ var _20 = useState(1), max = _20[0], setMax = _20[1];
174
+ var _21 = useState(false), textsVisible = _21[0], setTextsVisible = _21[1];
175
+ var _22 = useState({ a: 1, b: 0 }), transformFactors = _22[0], setTransformFactors = _22[1];
176
+ var _23 = useState(false), saving = _23[0], setSaving = _23[1];
177
+ var _24 = useState(null), drawShapeTool = _24[0], setDrawShapeTool = _24[1];
178
+ var _25 = useState(null), shapeDraft = _25[0], setShapeDraft = _25[1];
179
+ var _26 = useState("freehand"), penDrawMode = _26[0], setPenDrawMode = _26[1];
180
+ var _27 = useState(0), polylineVertexCount = _27[0], setPolylineVertexCount = _27[1];
181
+ var _28 = useState(null), penDraft = _28[0], setPenDraft = _28[1];
182
+ var _29 = useState(1), brushSize = _29[0], setBrushSize = _29[1];
183
+ var _30 = useState(1), eraserSize = _30[0], setEraserSize = _30[1];
182
184
  var brushSizeRef = React.useRef(1);
183
185
  var eraserSizeRef = React.useRef(1);
184
186
  var shapeDraftRef = React.useRef(null);
@@ -193,8 +195,8 @@ export default function CloudMrNiivueViewer(props) {
193
195
  nv.opts.penBounds = 0;
194
196
  nv.opts.penSize = 1;
195
197
  }, []);
196
- var _29 = React.useState(1.0), gamma = _29[0], setGamma = _29[1];
197
- var _30 = React.useState(0), gammaKey = _30[0], setGammaKey = _30[1];
198
+ var _31 = React.useState(1.0), gamma = _31[0], setGamma = _31[1];
199
+ var _32 = React.useState(0), gammaKey = _32[0], setGammaKey = _32[1];
198
200
  // Niivue → React bridge so other places (Toolbar) can force the UI to reset
199
201
  nv.onResetGamma = function () {
200
202
  setGamma(1.0);
@@ -217,6 +219,9 @@ export default function CloudMrNiivueViewer(props) {
217
219
  setTimeout(function (args) { return nv.resizeListener(); }, 700);
218
220
  }
219
221
  }, []);
222
+ React.useEffect(function () {
223
+ loadingVolumeIndexRef.current = props.selectedVolume;
224
+ }, [props.selectedVolume]);
220
225
  React.useEffect(function () {
221
226
  var _a;
222
227
  var nii = (_a = props.niis) === null || _a === void 0 ? void 0 : _a[props.selectedVolume];
@@ -239,18 +244,18 @@ export default function CloudMrNiivueViewer(props) {
239
244
  // setLayers([...nv.volumes])
240
245
  // }, [])
241
246
  // values dualslider
242
- var _31 = useState(0), rangeKey = _31[0], setRangeKey = _31[1];
247
+ var _33 = useState(0), rangeKey = _33[0], setRangeKey = _33[1];
243
248
  nv.onResetContrast = function () {
244
249
  setRangeKey(rangeKey + 1);
245
250
  };
246
- var _32 = useState([0, 0, 0]), boundMins = _32[0], setBoundMins = _32[1];
247
- var _33 = useState([1, 1, 1]), boundMaxs = _33[0], setBoundMaxs = _33[1];
248
- var _34 = useState([0.5, 0.5, 0.5]), mms = _34[0], setMMs = _34[1];
251
+ var _34 = useState([0, 0, 0]), boundMins = _34[0], setBoundMins = _34[1];
252
+ var _35 = useState([1, 1, 1]), boundMaxs = _35[0], setBoundMaxs = _35[1];
253
+ var _36 = useState([0.5, 0.5, 0.5]), mms = _36[0], setMMs = _36[1];
249
254
  nv.onImageLoaded = function () {
250
- var _a, _b;
255
+ var _a, _b, _c, _d, _e, _f, _g;
251
256
  var oldCrosshairPos = __spreadArray([], nv.scene.crosshairPos, true);
252
257
  if (nv.volumes.length > 1) {
253
- var nii = (_a = props.niis) === null || _a === void 0 ? void 0 : _a[props.selectedVolume];
258
+ var nii = (_b = (_a = props.niis) === null || _a === void 0 ? void 0 : _a[loadingVolumeIndexRef.current]) !== null && _b !== void 0 ? _b : (_c = props.niis) === null || _c === void 0 ? void 0 : _c[props.selectedVolume];
254
259
  if (nii === null || nii === void 0 ? void 0 : nii.link) {
255
260
  nv.loadVolumes([niiToVolume(nii)]);
256
261
  setWarning("Error loading results, please check internet connectivity");
@@ -298,17 +303,16 @@ export default function CloudMrNiivueViewer(props) {
298
303
  }
299
304
  else {
300
305
  nv.setGamma(1.0);
301
- (_b = nv.onResetGamma) === null || _b === void 0 ? void 0 : _b.call(nv);
306
+ (_d = nv.onResetGamma) === null || _d === void 0 ? void 0 : _d.call(nv);
302
307
  nv.resetScene();
303
308
  }
304
309
  setLayers(__spreadArray([], nv.volumes, true));
305
310
  setBoundMins(nv.frac2mm([0, 0, 0]));
306
311
  setBoundMaxs(nv.frac2mm([1, 1, 1]));
307
312
  // setMMs(nv.frac2mm([0.5, 0.5, 0.5])); // Commented to prevent recentering on volume load; we now re-apply saved crosshair if available
308
- if (verifyComplex(nv.volumes[0])) //Check if there are complex components
309
- nvSetDisplayedVoxels('absolute');
310
- else
311
- nvSetDisplayedVoxels('absolute');
313
+ var currentNii = (_f = (_e = props.niis) === null || _e === void 0 ? void 0 : _e[loadingVolumeIndexRef.current]) !== null && _f !== void 0 ? _f : (_g = props.niis) === null || _g === void 0 ? void 0 : _g[props.selectedVolume];
314
+ verifyComplex(nv.volumes[0], currentNii);
315
+ nvSetDisplayedVoxels('absolute');
312
316
  // let volume = nv.volumes[0];
313
317
  nvSetDragMode(dragMode); // keep engine behavior in sync with dropdown
314
318
  // Re-apply world/voxel mode and last crosshair after resets
@@ -357,9 +361,18 @@ export default function CloudMrNiivueViewer(props) {
357
361
  return numbers;
358
362
  }
359
363
  }
360
- function verifyComplex(volume) {
364
+ function verifyComplex(volume, nii) {
365
+ var _a, _b;
361
366
  volume.real = volume.img;
362
367
  setComplexMode('absolute');
368
+ if (isAbsoluteOnlyVolume(nii)) {
369
+ volume.absolute = new volume.img.constructor(volume.img.length);
370
+ for (var i = 0; i < volume.img.length; i++) {
371
+ var realPart = volume.real[i];
372
+ var imaginaryPart = (_b = (_a = volume.imaginary) === null || _a === void 0 ? void 0 : _a[i]) !== null && _b !== void 0 ? _b : 0;
373
+ volume.absolute[i] = Math.sqrt(realPart * realPart + imaginaryPart * imaginaryPart);
374
+ }
375
+ }
363
376
  // Ensure volume.imaginary is defined and has the same length as volume.img
364
377
  if (!volume.imaginary || volume.imaginary.length !== volume.img.length) {
365
378
  setComplexOptions(['absolute', 'real']);
@@ -397,6 +410,10 @@ export default function CloudMrNiivueViewer(props) {
397
410
  return !allZero;
398
411
  }
399
412
  function nvSetDisplayedVoxels(voxelType) {
413
+ var _a, _b, _c;
414
+ var nii = (_b = (_a = props.niis) === null || _a === void 0 ? void 0 : _a[loadingVolumeIndexRef.current]) !== null && _b !== void 0 ? _b : (_c = props.niis) === null || _c === void 0 ? void 0 : _c[selectedVolume];
415
+ if (isAbsoluteOnlyVolume(nii) && voxelType !== 'absolute')
416
+ return;
400
417
  setComplexMode(voxelType);
401
418
  var volume = nv.volumes[0];
402
419
  switch (voxelType) {
@@ -533,7 +550,7 @@ export default function CloudMrNiivueViewer(props) {
533
550
  setTextsVisible(true);
534
551
  }
535
552
  }
536
- var _35 = useState("pan"), dragMode = _35[0], setDragMode = _35[1];
553
+ var _37 = useState("pan"), dragMode = _37[0], setDragMode = _37[1];
537
554
  function nvSetDragMode(dragMode) {
538
555
  switch (dragMode) {
539
556
  case "none":
@@ -847,7 +864,7 @@ export default function CloudMrNiivueViewer(props) {
847
864
  nv.opts.crosshairWidth = w;
848
865
  nv.drawScene();
849
866
  }
850
- var _36 = useState({}), labelMapping = _36[0], setLabelMapping = _36[1];
867
+ var _38 = useState({}), labelMapping = _38[0], setLabelMapping = _38[1];
851
868
  function resampleImage(mapping) {
852
869
  if (mapping === void 0) { mapping = labelMapping; }
853
870
  var el = typeof document !== "undefined" ? document.getElementById("histoplot") : null;
@@ -1370,8 +1387,12 @@ export default function CloudMrNiivueViewer(props) {
1370
1387
  setSelectionBoxColor(__spreadArray(__spreadArray([], rgb01, true), [0.5], false));
1371
1388
  nv.setSelectionBoxColor(__spreadArray(__spreadArray([], rgb01, true), [0.5], false));
1372
1389
  }
1373
- var _37 = React.useState('axial'), sliceType = _37[0], setSliceType = _37[1];
1390
+ var _39 = React.useState('axial'), sliceType = _39[0], setSliceType = _39[1];
1391
+ var axialOnlyView = isAxialOnlyVolume((_a = props.niis) === null || _a === void 0 ? void 0 : _a[selectedVolume]);
1392
+ var absoluteOnlyView = isAbsoluteOnlyVolume((_b = props.niis) === null || _b === void 0 ? void 0 : _b[selectedVolume]);
1374
1393
  function nvUpdateSliceType(newSliceType) {
1394
+ if (axialOnlyView && newSliceType !== 'axial')
1395
+ return;
1375
1396
  setSliceType(newSliceType);
1376
1397
  if (newSliceType === 'axial') {
1377
1398
  nv.setSliceType(nv.sliceTypeAxial);
@@ -1408,7 +1429,13 @@ export default function CloudMrNiivueViewer(props) {
1408
1429
  setLayers(__spreadArray([], nv.volumes, true));
1409
1430
  }
1410
1431
  function stylingProxy(nii) {
1411
- if (nii.dim === 2) {
1432
+ var _a;
1433
+ if (isAbsoluteOnlyVolume(nii)) {
1434
+ if (complexMode !== 'absolute' && ((_a = nv.volumes[0]) === null || _a === void 0 ? void 0 : _a.absolute)) {
1435
+ nvSetDisplayedVoxels('absolute');
1436
+ }
1437
+ }
1438
+ if (isAxialOnlyVolume(nii)) {
1412
1439
  nvUpdateSliceType('axial');
1413
1440
  setShowCrosshair(false);
1414
1441
  setTextsVisible(false);
@@ -1432,11 +1459,12 @@ export default function CloudMrNiivueViewer(props) {
1432
1459
  var _this = this;
1433
1460
  return __generator(this, function (_a) {
1434
1461
  openVolume = function () { return __awaiter(_this, void 0, void 0, function () {
1435
- var vol, e_1;
1462
+ var vol, nextNii, e_1;
1436
1463
  var _a;
1437
1464
  return __generator(this, function (_b) {
1438
1465
  switch (_b.label) {
1439
1466
  case 0:
1467
+ loadingVolumeIndexRef.current = volumeIndex;
1440
1468
  nv.closeDrawing();
1441
1469
  setDrawingChanged(false);
1442
1470
  if (drawingEnabled)
@@ -1460,9 +1488,8 @@ export default function CloudMrNiivueViewer(props) {
1460
1488
  nvSetDragMode(dragMode); // re-apply user's drag mode after Niivue resets
1461
1489
  // Re-apply world/voxel mode and last crosshair after loading a new volume
1462
1490
  nv.setSliceMM(worldSpace);
1463
- //applySavedCrosshairIfAny();
1464
- // ensure engine mode matches the remembered selection
1465
- nvUpdateSliceType(sliceType);
1491
+ nextNii = props.niis[volumeIndex];
1492
+ nvUpdateSliceType(isAxialOnlyVolume(nextNii) ? 'axial' : sliceType);
1466
1493
  nv.opts.crosshairWidth = showCrosshair ? 1 : 0;
1467
1494
  return [3 /*break*/, 4];
1468
1495
  case 3:
@@ -1510,14 +1537,14 @@ export default function CloudMrNiivueViewer(props) {
1510
1537
  return [2 /*return*/];
1511
1538
  });
1512
1539
  }); };
1513
- var _38 = useState(''), selectedROI = _38[0], setSelectedDrawingLayer = _38[1];
1514
- var _39 = useState(false), saveDialogOpen = _39[0], setSaveDialogOpen = _39[1];
1515
- var _40 = useState(function () {
1516
- }), saveConfirmCallback = _40[0], setSaveConfirmCallback = _40[1];
1517
- var _41 = useState(false), confirmationOpen = _41[0], setConfirmationOpen = _41[1];
1518
- var _42 = useState(function () { }), warningConfirmationCallback = _42[0], setWarningConfirmationCallback = _42[1];
1519
- var _43 = useState(function () { }), warningCancelCallback = _43[0], setWarningCancelCallback = _43[1];
1520
- var _44 = useState(false), drawingChanged = _44[0], setDrawingChanged = _44[1];
1540
+ var _40 = useState(''), selectedROI = _40[0], setSelectedDrawingLayer = _40[1];
1541
+ var _41 = useState(false), saveDialogOpen = _41[0], setSaveDialogOpen = _41[1];
1542
+ var _42 = useState(function () {
1543
+ }), saveConfirmCallback = _42[0], setSaveConfirmCallback = _42[1];
1544
+ var _43 = useState(false), confirmationOpen = _43[0], setConfirmationOpen = _43[1];
1545
+ var _44 = useState(function () { }), warningConfirmationCallback = _44[0], setWarningConfirmationCallback = _44[1];
1546
+ var _45 = useState(function () { }), warningCancelCallback = _45[0], setWarningCancelCallback = _45[1];
1547
+ var _46 = useState(false), drawingChanged = _46[0], setDrawingChanged = _46[1];
1521
1548
  // When new drawing strokes are made on top of a saved ROI, reset the dropdown
1522
1549
  // so it shows the "ROI Layer" placeholder instead of the stale saved name.
1523
1550
  React.useEffect(function () {
@@ -1887,7 +1914,7 @@ export default function CloudMrNiivueViewer(props) {
1887
1914
  width: '100%',
1888
1915
  alignItems: 'center',
1889
1916
  justifyContent: 'center'
1890
- } }, { 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 () {
1917
+ } }, { 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, axialOnlyView: axialOnlyView, absoluteOnlyView: absoluteOnlyView, 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 () {
1891
1918
  },
1892
1919
  // suffix={'.zip'}
1893
1920
  defaultText: (props.rois[selectedROI] !== undefined ?
@@ -1906,3 +1933,34 @@ function niiToVolume(nii) {
1906
1933
  alias: nii.name
1907
1934
  };
1908
1935
  }
1936
+ /** 2D matrix results (noise covariance, noise coefficients, etc.) only support axial view. */
1937
+ function isAxialOnlyVolume(nii) {
1938
+ if (!nii)
1939
+ return false;
1940
+ if (nii.dim === 2)
1941
+ return true;
1942
+ var label = "".concat(nii.name || "", " ").concat(nii.type || "").toLowerCase();
1943
+ return (label.includes("noise covariance") ||
1944
+ label.includes("noise coefficients") ||
1945
+ label.includes("noise coefficient"));
1946
+ }
1947
+ /** SNR, g-factor maps, inverse g-factor, and noise coefficients are magnitude-only. */
1948
+ function isAbsoluteOnlyVolume(nii) {
1949
+ if (!nii)
1950
+ return false;
1951
+ var label = "".concat(nii.name || "", " ").concat(nii.type || "", " ").concat(nii.filename || "").toLowerCase();
1952
+ if (label.includes("noise coefficient"))
1953
+ return true;
1954
+ if (label.includes("inverse g factor") ||
1955
+ label.includes("inverse g-factor") ||
1956
+ label.includes("inverse gfactor") ||
1957
+ label.includes("inverse g"))
1958
+ return true;
1959
+ if (label.includes("g factor") ||
1960
+ label.includes("g-factor") ||
1961
+ /\bgfactor\b/.test(label))
1962
+ return true;
1963
+ if (label.includes("snr"))
1964
+ return true;
1965
+ return false;
1966
+ }
@@ -4,6 +4,10 @@ interface ToolbarProps {
4
4
  nv: any;
5
5
  nvUpdateSliceType: any;
6
6
  sliceType: string;
7
+ /** When true, only axial orientation is valid (e.g. noise covariance / coefficients). */
8
+ axialOnlyView?: boolean;
9
+ /** When true, only absolute display mode is valid (e.g. SNR, g-factor maps). */
10
+ absoluteOnlyView?: boolean;
7
11
  toggleLayers: React.MouseEventHandler<HTMLButtonElement> | undefined;
8
12
  toggleSettings: React.MouseEventHandler<HTMLButtonElement> | undefined;
9
13
  volumes: {
@@ -46,8 +46,8 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
46
46
  }
47
47
  };
48
48
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
49
- import { Fragment, useState } from 'react';
50
- import { Box, Button, CircularProgress, Stack, Switch, Tooltip, Typography } from "@mui/material";
49
+ import React, { Fragment, useState } from 'react';
50
+ import { Box, Button, CircularProgress, Stack, Switch, TextField, Tooltip, Typography } from "@mui/material";
51
51
  import { IconButton, FormControl, Select, MenuItem, InputLabel } from "@mui/material";
52
52
  import SettingsIcon from '@mui/icons-material/Settings';
53
53
  import HomeIcon from '@mui/icons-material/Home';
@@ -66,8 +66,9 @@ export default function Toolbar(props) {
66
66
  var viewerTheme = useNiivueViewerTheme();
67
67
  function handleSliceTypeChange(e) {
68
68
  var newSliceType = e.target.value;
69
- var nvUpdateSliceType = props.nvUpdateSliceType;
70
- nvUpdateSliceType(newSliceType);
69
+ if (props.axialOnlyView && newSliceType !== "axial")
70
+ return;
71
+ props.nvUpdateSliceType(newSliceType);
71
72
  }
72
73
  // let dragModes = ["Pan","Measurement","Contrast",'None'];
73
74
  var dragModes = [
@@ -76,9 +77,66 @@ export default function Toolbar(props) {
76
77
  { value: "contrast", label: "Slice and Contrast" },
77
78
  { value: "none", label: "Slice and None" }
78
79
  ];
79
- var _a = useState(false), roiDeleteOpen = _a[0], setRoiDeleteOpen = _a[1];
80
- var _b = useState(undefined), roiDeleteMsg = _b[0], setRoiDeleteMsg = _b[1];
81
- var _c = useState(function () { return function () { }; }), roiDeleteConfirm = _c[0], setRoiDeleteConfirm = _c[1];
80
+ var displayModes = ["absolute", "real", "imaginary", "phase"];
81
+ var _a = useState(''), zoomText = _a[0], setZoomText = _a[1];
82
+ var zoomEditingRef = React.useRef(false);
83
+ /** Compute the base pixels-per-mm at zoom=1.0, reading live canvas + volume state. */
84
+ var getBasePxPerMm = function () {
85
+ var _a;
86
+ var nv = props.nv;
87
+ var canvas = (_a = nv === null || nv === void 0 ? void 0 : nv.gl) === null || _a === void 0 ? void 0 : _a.canvas;
88
+ var back = nv === null || nv === void 0 ? void 0 : nv.back;
89
+ if (!canvas || !(back === null || back === void 0 ? void 0 : back.dims) || !(back === null || back === void 0 ? void 0 : back.pixDims))
90
+ return null;
91
+ var imgWmm = back.dims[1] * Math.abs(back.pixDims[1]);
92
+ var imgHmm = back.dims[2] * Math.abs(back.pixDims[2]);
93
+ if (imgWmm <= 0 || imgHmm <= 0)
94
+ return null;
95
+ var w = canvas.clientWidth || canvas.width;
96
+ var h = canvas.clientHeight || canvas.height;
97
+ return Math.min(w / imgWmm, h / imgHmm);
98
+ };
99
+ /** Returns the display value (number only, no unit suffix). */
100
+ var formatPxPerMm = function () {
101
+ var _a, _b, _c;
102
+ var zoom = (_c = (_b = (_a = props.nv) === null || _a === void 0 ? void 0 : _a.scene) === null || _b === void 0 ? void 0 : _b.pan2Dxyzmm) === null || _c === void 0 ? void 0 : _c[3];
103
+ if (zoom == null)
104
+ return '';
105
+ var base = getBasePxPerMm();
106
+ if (base != null && base > 0)
107
+ return (base * zoom).toFixed(2);
108
+ return (zoom * 100).toFixed(0);
109
+ };
110
+ React.useEffect(function () {
111
+ var rafId;
112
+ var sync = function () {
113
+ if (!zoomEditingRef.current)
114
+ setZoomText(formatPxPerMm());
115
+ rafId = requestAnimationFrame(sync);
116
+ };
117
+ rafId = requestAnimationFrame(sync);
118
+ return function () { return cancelAnimationFrame(rafId); };
119
+ }, [props.nv]);
120
+ var applyZoom = function (text) {
121
+ var num = parseFloat(text.trim());
122
+ if (isNaN(num) || num <= 0)
123
+ return;
124
+ var base = getBasePxPerMm();
125
+ var targetZoom = (base != null && base > 0) ? num / base : num / 100;
126
+ var scene = props.nv.scene;
127
+ var current = scene.pan2Dxyzmm[3];
128
+ var next = Math.max(0.01, targetZoom);
129
+ var delta = current - next;
130
+ scene.pan2Dxyzmm[3] = next;
131
+ var mm = props.nv.frac2mm(scene.crosshairPos);
132
+ scene.pan2Dxyzmm[0] += delta * mm[0];
133
+ scene.pan2Dxyzmm[1] += delta * mm[1];
134
+ scene.pan2Dxyzmm[2] += delta * mm[2];
135
+ props.nv.drawScene();
136
+ };
137
+ var _b = useState(false), roiDeleteOpen = _b[0], setRoiDeleteOpen = _b[1];
138
+ var _c = useState(undefined), roiDeleteMsg = _c[0], setRoiDeleteMsg = _c[1];
139
+ var _d = useState(function () { return function () { }; }), roiDeleteConfirm = _d[0], setRoiDeleteConfirm = _d[1];
82
140
  // const deleteROI= createAsyncThunk('DeleteROI', async (arg: { accessToken: string, jobId: string }) => {
83
141
  // // const data = { jobId: arg.jobId }; // No need to stringify, axios will handle it
84
142
  // const config = {
@@ -111,7 +169,7 @@ export default function Toolbar(props) {
111
169
  }) }))] })), _jsxs(FormControl, __assign({ size: 'small', sx: {
112
170
  m: 2,
113
171
  minWidth: 120
114
- } }, { children: [_jsx(InputLabel, __assign({ id: "slice-type-label" }, { children: "Orientation" })), _jsxs(Select, __assign({ labelId: "slice-type-label", id: "slice-type", value: props.sliceType, label: "Orientation", onChange: handleSliceTypeChange }, { children: [_jsx(MenuItem, __assign({ value: 'axial' }, { children: "Axial" })), _jsx(MenuItem, __assign({ value: 'coronal' }, { children: "Coronal" })), _jsx(MenuItem, __assign({ value: 'sagittal' }, { children: "Sagittal" })), _jsx(MenuItem, __assign({ value: 'multi' }, { children: "Multi" })), _jsx(MenuItem, __assign({ value: '3d' }, { children: "3D" }))] }))] })), _jsxs(FormControl, __assign({ size: 'small', sx: {
172
+ } }, { children: [_jsx(InputLabel, __assign({ id: "slice-type-label" }, { children: "Orientation" })), _jsxs(Select, __assign({ labelId: "slice-type-label", id: "slice-type", value: props.sliceType, label: "Orientation", onChange: handleSliceTypeChange }, { children: [_jsx(MenuItem, __assign({ value: 'axial' }, { children: "Axial" })), _jsx(MenuItem, __assign({ value: 'coronal', disabled: props.axialOnlyView }, { children: "Coronal" })), _jsx(MenuItem, __assign({ value: 'sagittal', disabled: props.axialOnlyView }, { children: "Sagittal" })), _jsx(MenuItem, __assign({ value: 'multi', disabled: props.axialOnlyView }, { children: "Multi" })), _jsx(MenuItem, __assign({ value: '3d', disabled: props.axialOnlyView }, { children: "3D" }))] }))] })), _jsxs(FormControl, __assign({ size: 'small', sx: {
115
173
  m: 2,
116
174
  minWidth: 180
117
175
  } }, { children: [_jsx(InputLabel, __assign({ id: "drag-mode-label" }, { children: "Scroll and Right Click Drag" })), _jsx(Select, __assign({ labelId: "drag-mode-label", id: "drag-mode", value: props.dragMode, label: "Scroll and Right Click Drag", onChange: function (e) {
@@ -120,8 +178,17 @@ export default function Toolbar(props) {
120
178
  } }, { children: dragModes.map(function (mode, index) { return (_jsx(MenuItem, __assign({ value: mode.value }, { children: mode.label }), index)); }) }))] })), _jsxs(FormControl, __assign({ size: 'small', sx: {
121
179
  m: 2,
122
180
  minWidth: 120
123
- } }, { children: [_jsx(InputLabel, __assign({ id: "slice-type-label" }, { children: "Display Mode" })), _jsx(Select, __assign({ labelId: "slice-type-label", id: "slice-type", value: props.complexMode, label: "Display Mode", onChange: function (e) { return props.setComplexMode(e.target.value); } }, { children: props.complexOptions.map(function (value) {
124
- return _jsx(MenuItem, __assign({ value: value }, { children: value.charAt(0).toUpperCase() + value.slice(1) }));
181
+ } }, { children: [_jsx(InputLabel, __assign({ id: "slice-type-label" }, { children: "Display Mode" })), _jsx(Select, __assign({ labelId: "slice-type-label", id: "slice-type", value: props.complexMode, label: "Display Mode", onChange: function (e) {
182
+ var next = e.target.value;
183
+ if (props.absoluteOnlyView && next !== "absolute")
184
+ return;
185
+ if (!props.complexOptions.includes(next))
186
+ return;
187
+ props.setComplexMode(next);
188
+ } }, { children: displayModes.map(function (value) {
189
+ var unavailable = !props.complexOptions.includes(value);
190
+ var absoluteOnlyLocked = props.absoluteOnlyView && value !== "absolute";
191
+ return (_jsx(MenuItem, __assign({ value: value, disabled: unavailable || absoluteOnlyLocked }, { children: value.charAt(0).toUpperCase() + value.slice(1) }), value));
125
192
  }) }))] })), _jsxs(FormControl, __assign({ size: "small", sx: { m: 2, minWidth: 160 } }, { children: [_jsx(InputLabel, __assign({ id: "roi-layer-label" }, { children: "ROI Layer" })), _jsx(Select, __assign({ labelId: "roi-layer-label", id: "roi-layer", value: props.selectedROI, label: "Opened ROIs",
126
193
  // Only text in the closed preview (no icon)
127
194
  renderValue: function (selected) {
@@ -259,7 +326,28 @@ export default function Toolbar(props) {
259
326
  border: '1px solid',
260
327
  borderColor: 'divider',
261
328
  borderRadius: 1
262
- } }, { children: _jsx(ZoomOutIcon, {}) })) })), _jsx(Tooltip, __assign({ title: 'Zoom In', placement: 'right' }, { children: _jsx(IconButton, __assign({ onClick: function () {
329
+ } }, { children: _jsx(ZoomOutIcon, {}) })) })), _jsx(TextField, { label: "px/mm", size: "small", value: zoomText, onChange: function (e) { return setZoomText(e.target.value); }, onFocus: function (e) {
330
+ zoomEditingRef.current = true;
331
+ requestAnimationFrame(function () { return e.target.select(); });
332
+ }, onBlur: function () {
333
+ zoomEditingRef.current = false;
334
+ applyZoom(zoomText);
335
+ }, onKeyDown: function (e) {
336
+ if (e.key === 'Enter') {
337
+ e.target.blur();
338
+ }
339
+ else if (e.key === 'Escape') {
340
+ zoomEditingRef.current = false;
341
+ setZoomText(formatPxPerMm());
342
+ e.target.blur();
343
+ }
344
+ }, sx: {
345
+ width: 100,
346
+ '& .MuiInputBase-input': {
347
+ textAlign: 'center',
348
+ fontSize: '0.8rem'
349
+ }
350
+ } }), _jsx(Tooltip, __assign({ title: 'Zoom In', placement: 'right' }, { children: _jsx(IconButton, __assign({ onClick: function () {
263
351
  var scene = props.nv.scene;
264
352
  var current = scene.pan2Dxyzmm[3];
265
353
  var next = current + 0.1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudmr-ux",
3
- "version": "4.8.8",
3
+ "version": "4.9.0",
4
4
  "author": "erosmontin@gmail.com",
5
5
  "license": "MIT",
6
6
  "repository": "erosmontin/cloudmr-ux",