@aics/vole-app 2.12.0 → 2.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/aics-image-viewer/components/App/ChannelUpdater.js +11 -12
- package/es/aics-image-viewer/components/App/index.js +216 -466
- package/es/aics-image-viewer/components/AxisClipSliders/index.js +12 -12
- package/es/aics-image-viewer/components/BottomPanel/index.js +12 -28
- package/es/aics-image-viewer/components/CellViewerCanvasWrapper/index.js +3 -12
- package/es/aics-image-viewer/components/ChannelsWidgetRow/index.js +2 -2
- package/es/aics-image-viewer/components/ChannelsWidgetRow/styles.css +5 -1
- package/es/aics-image-viewer/components/ErrorAlert/index.js +1 -1
- package/es/aics-image-viewer/components/TfEditor/index.js +75 -35
- package/es/aics-image-viewer/components/TfEditor/styles.css +2 -3
- package/es/aics-image-viewer/components/Toolbar/ViewModeRadioButtons.js +1 -1
- package/es/aics-image-viewer/components/Toolbar/index.js +34 -30
- package/es/aics-image-viewer/components/ViewerStateProvider/index.js +11 -13
- package/es/aics-image-viewer/components/useVolume.js +327 -0
- package/es/aics-image-viewer/shared/constants.js +10 -1
- package/es/aics-image-viewer/shared/utils/hooks.js +3 -26
- package/es/aics-image-viewer/shared/utils/sliderValuesToImageValues.js +15 -9
- package/es/aics-image-viewer/shared/utils/viewerState.js +1 -1
- package/package.json +3 -2
- package/type-declarations/aics-image-viewer/components/BottomPanel/index.d.ts +2 -2
- package/type-declarations/aics-image-viewer/components/CellViewerCanvasWrapper/index.d.ts +2 -2
- package/type-declarations/aics-image-viewer/components/TfEditor/index.d.ts +1 -1
- package/type-declarations/aics-image-viewer/components/Toolbar/index.d.ts +1 -1
- package/type-declarations/aics-image-viewer/components/ViewerStateProvider/types.d.ts +1 -1
- package/type-declarations/aics-image-viewer/components/useVolume.d.ts +49 -0
- package/type-declarations/aics-image-viewer/shared/constants.d.ts +4 -1
- package/type-declarations/aics-image-viewer/shared/utils/hooks.d.ts +0 -2
- package/type-declarations/aics-image-viewer/shared/utils/sliderValuesToImageValues.d.ts +6 -3
|
@@ -3,9 +3,6 @@ function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArra
|
|
|
3
3
|
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
4
4
|
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
|
|
5
5
|
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
|
|
6
|
-
function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, "catch": function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
|
|
7
|
-
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
8
|
-
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
9
6
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
10
7
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
11
8
|
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
@@ -18,22 +15,18 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
|
|
|
18
15
|
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
|
19
16
|
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
20
17
|
// 3rd Party Imports
|
|
21
|
-
import {
|
|
18
|
+
import { RENDERMODE_PATHTRACE, RENDERMODE_RAYMARCH, View3d } from "@aics/vole-core";
|
|
22
19
|
import { Layout } from "antd";
|
|
23
20
|
import { debounce, isEqual } from "lodash";
|
|
24
21
|
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
25
|
-
import {
|
|
26
|
-
import { AXIS_MARGIN_DEFAULT, CACHE_MAX_SIZE, CLIPPING_PANEL_HEIGHT_DEFAULT, CLIPPING_PANEL_HEIGHT_TALL, CONTROL_PANEL_CLOSE_WIDTH, DTYPE_RANGE, getDefaultChannelColor, getDefaultViewerState, QUEUE_MAX_LOW_PRIORITY_SIZE, QUEUE_MAX_SIZE, SCALE_BAR_MARGIN_DEFAULT } from "../../shared/constants";
|
|
22
|
+
import { AXIS_MARGIN_DEFAULT, CLIPPING_PANEL_HEIGHT_DEFAULT, CLIPPING_PANEL_HEIGHT_TALL, CONTROL_PANEL_CLOSE_WIDTH, DTYPE_RANGE, getDefaultViewerState, SCALE_BAR_MARGIN_DEFAULT } from "../../shared/constants";
|
|
27
23
|
import { ImageType, RenderMode, ViewMode } from "../../shared/enums";
|
|
28
24
|
import { activeAxisMap } from "../../shared/types";
|
|
29
25
|
import { colorArrayToFloats } from "../../shared/utils/colorRepresentations";
|
|
30
26
|
import { controlPointsToRamp, initializeLut, rampToControlPoints, remapControlPointsForChannel } from "../../shared/utils/controlPointsToLut";
|
|
31
|
-
import { useConstructor
|
|
32
|
-
import PlayControls from "../../shared/utils/playControls";
|
|
33
|
-
import SceneStore from "../../shared/utils/sceneStore";
|
|
27
|
+
import { useConstructor } from "../../shared/utils/hooks";
|
|
34
28
|
import { alphaSliderToImageValue, brightnessSliderToImageValue, densitySliderToImageValue, gammaSliderToImageValues } from "../../shared/utils/sliderValuesToImageValues";
|
|
35
|
-
import {
|
|
36
|
-
import { initializeOneChannelSetting } from "../../shared/utils/viewerState";
|
|
29
|
+
import useVolume, { ImageLoadStatus } from "../useVolume";
|
|
37
30
|
import CellViewerCanvasWrapper from "../CellViewerCanvasWrapper";
|
|
38
31
|
import ControlPanel from "../ControlPanel";
|
|
39
32
|
import { useErrorAlert } from "../ErrorAlert";
|
|
@@ -81,12 +74,7 @@ var defaultProps = {
|
|
|
81
74
|
canvasMargin: "0 0 0 0",
|
|
82
75
|
view3dRef: undefined
|
|
83
76
|
};
|
|
84
|
-
var
|
|
85
|
-
t: PrefetchDirection.T_PLUS,
|
|
86
|
-
z: PrefetchDirection.Z_PLUS,
|
|
87
|
-
y: PrefetchDirection.Y_PLUS,
|
|
88
|
-
x: PrefetchDirection.X_PLUS
|
|
89
|
-
};
|
|
77
|
+
var CLIPPING_PANEL_ANIMATION_DURATION_MS = 300;
|
|
90
78
|
var setIndicatorPositions = function setIndicatorPositions(view3d, panelOpen, hasTime, hasScenes, isMode3d) {
|
|
91
79
|
// The height of the clipping panel includes the button, but we're trying to put these elements next to the button
|
|
92
80
|
var CLIPPING_PANEL_BUTTON_HEIGHT = 40;
|
|
@@ -116,53 +104,40 @@ var setIndicatorPositions = function setIndicatorPositions(view3d, panelOpen, ha
|
|
|
116
104
|
view3d.setScaleBarPosition(scaleBarX, scaleBarY);
|
|
117
105
|
};
|
|
118
106
|
var App = function App(props) {
|
|
119
|
-
var _scenes$length,
|
|
107
|
+
var _getCurrentViewerChan, _scenes$length, _scenes2, _image$imageInfo$volu, _image$imageInfo$subr, _image$imageInfo$time, _props$transform2, _props$transform4;
|
|
120
108
|
props = _objectSpread(_objectSpread({}, defaultProps), props);
|
|
121
109
|
|
|
122
110
|
// State management /////////////////////////////////////////////////////////
|
|
123
111
|
var viewerState = useContext(ViewerStateContext).ref;
|
|
124
112
|
var _viewerState$current = viewerState.current,
|
|
113
|
+
imageType = _viewerState$current.imageType,
|
|
114
|
+
viewMode = _viewerState$current.viewMode,
|
|
125
115
|
channelSettings = _viewerState$current.channelSettings,
|
|
126
|
-
setChannelSettings = _viewerState$current.setChannelSettings,
|
|
127
|
-
changeViewerSetting = _viewerState$current.changeViewerSetting,
|
|
128
116
|
changeChannelSetting = _viewerState$current.changeChannelSetting,
|
|
129
117
|
applyColorPresets = _viewerState$current.applyColorPresets,
|
|
130
118
|
setSavedViewerChannelSettings = _viewerState$current.setSavedViewerChannelSettings,
|
|
131
119
|
getCurrentViewerChannelSettings = _viewerState$current.getCurrentViewerChannelSettings,
|
|
132
120
|
getChannelsAwaitingReset = _viewerState$current.getChannelsAwaitingReset,
|
|
133
|
-
getChannelsAwaitingResetOnLoad = _viewerState$current.getChannelsAwaitingResetOnLoad,
|
|
134
121
|
onResetChannel = _viewerState$current.onResetChannel;
|
|
122
|
+
var _props = props,
|
|
123
|
+
onControlPanelToggle = _props.onControlPanelToggle,
|
|
124
|
+
metadata = _props.metadata,
|
|
125
|
+
metadataFormatter = _props.metadataFormatter;
|
|
135
126
|
useMemo(function () {
|
|
136
127
|
if (props.viewerChannelSettings) {
|
|
137
128
|
setSavedViewerChannelSettings(props.viewerChannelSettings);
|
|
138
129
|
}
|
|
139
|
-
}, [props.viewerChannelSettings]);
|
|
130
|
+
}, [props.viewerChannelSettings, setSavedViewerChannelSettings]);
|
|
140
131
|
var view3d = useConstructor(function () {
|
|
141
132
|
return new View3d();
|
|
142
133
|
});
|
|
143
134
|
if (props.view3dRef !== undefined) {
|
|
144
135
|
props.view3dRef.current = view3d;
|
|
145
136
|
}
|
|
146
|
-
var loadContext = useConstructor(function () {
|
|
147
|
-
return new VolumeLoaderContext(CACHE_MAX_SIZE, QUEUE_MAX_SIZE, QUEUE_MAX_LOW_PRIORITY_SIZE);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// install loadContext into view3d
|
|
151
|
-
view3d.loaderContext = loadContext;
|
|
152
|
-
var loader = useRef();
|
|
153
|
-
var _useState = useState(null),
|
|
154
|
-
_useState2 = _slicedToArray(_useState, 2),
|
|
155
|
-
image = _useState2[0],
|
|
156
|
-
setImage = _useState2[1];
|
|
157
|
-
var imageUrlRef = useRef("");
|
|
158
137
|
var _useErrorAlert = useErrorAlert(),
|
|
159
138
|
_useErrorAlert2 = _slicedToArray(_useErrorAlert, 2),
|
|
160
139
|
errorAlert = _useErrorAlert2[0],
|
|
161
|
-
|
|
162
|
-
var showError = function showError(error) {
|
|
163
|
-
_showError(error);
|
|
164
|
-
setSendingQueryRequest(false);
|
|
165
|
-
};
|
|
140
|
+
showError = _useErrorAlert2[1];
|
|
166
141
|
useEffect(function () {
|
|
167
142
|
// Get notifications of loading errors which occur after the initial load, e.g. on time change or new channel load
|
|
168
143
|
view3d.setLoadErrorHandler(function (_vol, e) {
|
|
@@ -171,139 +146,67 @@ var App = function App(props) {
|
|
|
171
146
|
return function () {
|
|
172
147
|
return view3d.setLoadErrorHandler(undefined);
|
|
173
148
|
};
|
|
174
|
-
}, [view3d]);
|
|
175
|
-
var
|
|
176
|
-
var
|
|
177
|
-
var
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
var
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
_useState4 = _slicedToArray(_useState3, 2),
|
|
196
|
-
sendingQueryRequest = _useState4[0],
|
|
197
|
-
setSendingQueryRequest = _useState4[1];
|
|
198
|
-
// `true` when all channels of the current image are loaded
|
|
199
|
-
var _useState5 = useState(false),
|
|
200
|
-
_useState6 = _slicedToArray(_useState5, 2),
|
|
201
|
-
imageLoaded = _useState6[0],
|
|
202
|
-
setImageLoaded = _useState6[1];
|
|
203
|
-
// tracks which channels have been loaded
|
|
204
|
-
var _useStateWithGetter = useStateWithGetter([]),
|
|
205
|
-
_useStateWithGetter2 = _slicedToArray(_useStateWithGetter, 3),
|
|
206
|
-
channelVersions = _useStateWithGetter2[0],
|
|
207
|
-
setChannelVersions = _useStateWithGetter2[1],
|
|
208
|
-
getChannelVersions = _useStateWithGetter2[2];
|
|
209
|
-
// we need to keep track of channel ranges for remapping
|
|
210
|
-
var channelRangesRef = useRef([]);
|
|
211
|
-
var _useState7 = useState({}),
|
|
212
|
-
_useState8 = _slicedToArray(_useState7, 2),
|
|
213
|
-
channelGroupedByType = _useState8[0],
|
|
214
|
-
setChannelGroupedByType = _useState8[1];
|
|
215
|
-
var _useState9 = useState(function () {
|
|
216
|
-
return window.innerWidth < CONTROL_PANEL_CLOSE_WIDTH;
|
|
217
|
-
}),
|
|
218
|
-
_useState10 = _slicedToArray(_useState9, 2),
|
|
219
|
-
controlPanelClosed = _useState10[0],
|
|
220
|
-
setControlPanelClosed = _useState10[1];
|
|
221
|
-
// Only allow auto-close once while the screen is too narrow.
|
|
222
|
-
var _useState11 = useState(false),
|
|
223
|
-
_useState12 = _slicedToArray(_useState11, 2),
|
|
224
|
-
hasAutoClosedControlPanel = _useState12[0],
|
|
225
|
-
setHasAutoClosedControlPanel = _useState12[1];
|
|
226
|
-
|
|
227
|
-
// Clipping panel state doesn't need to trigger renders on change, so it can go in a ref
|
|
228
|
-
var clippingPanelOpenRef = useRef(true);
|
|
229
|
-
|
|
230
|
-
// `PlayControls` manages playing through time and spatial axes, which isn't practical with "pure" React
|
|
231
|
-
// Playback state goes here, but the play/pause buttons that mainly control this class are down in `AxisClipSliders`
|
|
232
|
-
var playControls = useConstructor(function () {
|
|
233
|
-
return new PlayControls();
|
|
234
|
-
});
|
|
235
|
-
var _useState13 = useState(null),
|
|
236
|
-
_useState14 = _slicedToArray(_useState13, 2),
|
|
237
|
-
playingAxis = _useState14[0],
|
|
238
|
-
setPlayingAxis = _useState14[1];
|
|
239
|
-
playControls.onPlayingAxisChanged = function (axis) {
|
|
240
|
-
var _loader$current, _loader$current2;
|
|
241
|
-
(_loader$current = loader.current) === null || _loader$current === void 0 || _loader$current.setPrefetchPriority(axis ? [axisToLoaderPriority[axis]] : []);
|
|
242
|
-
(_loader$current2 = loader.current) === null || _loader$current2 === void 0 || _loader$current2.syncMultichannelLoading(axis ? true : false);
|
|
243
|
-
if (image) {
|
|
244
|
-
if (axis === null) {
|
|
245
|
-
// Playback has stopped - reset scale level bias
|
|
246
|
-
view3d.setScaleLevelBias(image, 0);
|
|
247
|
-
} else {
|
|
248
|
-
// Playback has started - unless entire axis is in memory (typical in X and Y), downlevel to speed things up
|
|
249
|
-
var shouldDownlevel = axis === "t" || numSlices[axis] !== numSlicesLoaded[axis];
|
|
250
|
-
view3d.setScaleLevelBias(image, shouldDownlevel ? 1 : 0);
|
|
149
|
+
}, [view3d, showError]);
|
|
150
|
+
var imageUrlRef = useRef("");
|
|
151
|
+
var scenesRef = useRef([]);
|
|
152
|
+
var _props2 = props,
|
|
153
|
+
imageUrl = _props2.imageUrl,
|
|
154
|
+
parentImageUrl = _props2.parentImageUrl,
|
|
155
|
+
rawData = _props2.rawData,
|
|
156
|
+
rawDims = _props2.rawDims;
|
|
157
|
+
var scenes = useMemo(function () {
|
|
158
|
+
if (rawData && rawDims) {
|
|
159
|
+
return [{
|
|
160
|
+
data: rawData,
|
|
161
|
+
metadata: rawDims
|
|
162
|
+
}];
|
|
163
|
+
} else {
|
|
164
|
+
var _scenes;
|
|
165
|
+
var showParentImage = imageType === ImageType.fullField && parentImageUrl !== undefined;
|
|
166
|
+
var path = showParentImage ? parentImageUrl : imageUrl;
|
|
167
|
+
// Don't reload if we're already looking at this image
|
|
168
|
+
if (isEqual(path, imageUrlRef.current)) {
|
|
169
|
+
return scenesRef.current;
|
|
251
170
|
}
|
|
171
|
+
imageUrlRef.current = path;
|
|
172
|
+
var result = (_scenes = path.scenes) !== null && _scenes !== void 0 ? _scenes : [path];
|
|
173
|
+
scenesRef.current = result;
|
|
174
|
+
return result;
|
|
252
175
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
// These last state functions are only ever used within this component - no need for a `useCallback`
|
|
257
|
-
|
|
258
|
-
var getOneChannelSetting = function getOneChannelSetting(channelName, settings) {
|
|
259
|
-
return (settings || viewerState.current.channelSettings).find(function (channel) {
|
|
260
|
-
return channel.name === channelName;
|
|
261
|
-
});
|
|
262
|
-
};
|
|
263
|
-
var setAllChannelsUnloaded = function setAllChannelsUnloaded(numberOfChannels) {
|
|
264
|
-
setChannelVersions(new Array(numberOfChannels).fill(0));
|
|
265
|
-
};
|
|
266
|
-
var setOneChannelLoaded = function setOneChannelLoaded(index) {
|
|
267
|
-
var newVersions = getChannelVersions().slice();
|
|
268
|
-
newVersions[index]++;
|
|
269
|
-
setChannelVersions(newVersions);
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
// Image loading/initialization functions ///////////////////////////////////
|
|
176
|
+
}, [imageUrl, parentImageUrl, rawData, rawDims, imageType]);
|
|
177
|
+
var maskChannelName = (_getCurrentViewerChan = getCurrentViewerChannelSettings()) === null || _getCurrentViewerChan === void 0 ? void 0 : _getCurrentViewerChan.maskChannelName;
|
|
273
178
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
179
|
+
// we need to keep track of channel ranges for remapping control points
|
|
180
|
+
var channelRangesRef = useRef([]);
|
|
181
|
+
var onChannelLoaded = useCallback(function (image, channelIndex, isInitialLoad) {
|
|
182
|
+
// TODO this was once a search by name - is that still necessary or will the index always be correct?
|
|
183
|
+
var thisChannelSettings = channelSettings[channelIndex];
|
|
184
|
+
var _viewerState$current2 = viewerState.current,
|
|
185
|
+
getChannelsAwaitingResetOnLoad = _viewerState$current2.getChannelsAwaitingResetOnLoad,
|
|
186
|
+
getCurrentViewerChannelSettings = _viewerState$current2.getCurrentViewerChannelSettings,
|
|
187
|
+
changeChannelSetting = _viewerState$current2.changeChannelSetting;
|
|
188
|
+
var ramp = thisChannelSettings.ramp,
|
|
189
|
+
controlPoints = thisChannelSettings.controlPoints;
|
|
190
|
+
var thisChannel = image.getChannel(channelIndex);
|
|
191
|
+
if (isInitialLoad || !controlPoints || !ramp || getChannelsAwaitingResetOnLoad().has(channelIndex)) {
|
|
192
|
+
// This channel needs its LUT initialized
|
|
193
|
+
var _initializeLut = initializeLut(image, channelIndex, getCurrentViewerChannelSettings()),
|
|
194
|
+
_ramp = _initializeLut.ramp,
|
|
195
|
+
_controlPoints = _initializeLut.controlPoints;
|
|
196
|
+
var dtype = thisChannel.dtype;
|
|
289
197
|
changeChannelSetting(channelIndex, {
|
|
290
|
-
controlPoints:
|
|
291
|
-
ramp: controlPointsToRamp(
|
|
198
|
+
controlPoints: _controlPoints,
|
|
199
|
+
ramp: controlPointsToRamp(_ramp),
|
|
292
200
|
// set the default range of the transfer function editor to cover the full range of the data type
|
|
293
|
-
|
|
201
|
+
plotMin: DTYPE_RANGE[dtype].min,
|
|
202
|
+
plotMax: DTYPE_RANGE[dtype].max
|
|
294
203
|
});
|
|
295
|
-
onResetChannel(channelIndex);
|
|
296
204
|
} else {
|
|
297
|
-
//
|
|
298
|
-
// if (playingAxis !== null) {
|
|
299
|
-
// do nothing here?
|
|
300
|
-
// tell gui that we have updated control pts?
|
|
301
|
-
//changeChannelSetting(channelIndex, "controlPoints", aimg.getChannel(channelIndex).lut.controlPoints);
|
|
302
|
-
// }
|
|
205
|
+
// This channel has already been initialized, but its LUT was just remapped and we need to update some things
|
|
303
206
|
var oldRange = channelRangesRef.current[channelIndex];
|
|
304
|
-
if (
|
|
207
|
+
if (thisChannelSettings.useControlPoints) {
|
|
305
208
|
// control points were just automatically remapped - update in state
|
|
306
|
-
var rampControlPoints = rampToControlPoints(
|
|
209
|
+
var rampControlPoints = rampToControlPoints(thisChannelSettings.ramp);
|
|
307
210
|
// now manually remap ramp using the channel's old range
|
|
308
211
|
var remappedRampControlPoints = remapControlPointsForChannel(rampControlPoints, oldRange, thisChannel);
|
|
309
212
|
changeChannelSetting(channelIndex, {
|
|
@@ -312,76 +215,52 @@ var App = function App(props) {
|
|
|
312
215
|
});
|
|
313
216
|
} else {
|
|
314
217
|
// ramp was just automatically remapped - update in state
|
|
315
|
-
var
|
|
218
|
+
var _ramp2 = controlPointsToRamp(thisChannel.lut.controlPoints);
|
|
316
219
|
// now manually remap control points using the channel's old range
|
|
317
|
-
var
|
|
318
|
-
var remappedControlPoints = remapControlPointsForChannel(
|
|
220
|
+
var _controlPoints2 = thisChannelSettings.controlPoints;
|
|
221
|
+
var remappedControlPoints = remapControlPointsForChannel(_controlPoints2, oldRange, thisChannel);
|
|
319
222
|
changeChannelSetting(channelIndex, {
|
|
320
223
|
controlPoints: remappedControlPoints,
|
|
321
|
-
ramp:
|
|
224
|
+
ramp: _ramp2
|
|
322
225
|
});
|
|
323
226
|
}
|
|
324
227
|
}
|
|
325
|
-
};
|
|
326
|
-
var onChannelDataLoaded = function onChannelDataLoaded(aimg, thisChannelsSettings, channelIndex) {
|
|
327
|
-
var _getCurrentViewerChan;
|
|
328
|
-
var thisChannel = aimg.getChannel(channelIndex);
|
|
329
|
-
updateChannelTransferFunction(aimg, thisChannelsSettings, channelIndex);
|
|
330
228
|
|
|
331
229
|
// save the channel's new range for remapping next time
|
|
332
230
|
channelRangesRef.current[channelIndex] = [thisChannel.rawMin, thisChannel.rawMax];
|
|
333
|
-
view3d.updateLuts(
|
|
334
|
-
view3d.onVolumeData(
|
|
335
|
-
view3d.setVolumeChannelEnabled(
|
|
336
|
-
if (
|
|
337
|
-
view3d.setVolumeChannelAsMask(
|
|
231
|
+
view3d.updateLuts(image);
|
|
232
|
+
view3d.onVolumeData(image, [channelIndex]);
|
|
233
|
+
view3d.setVolumeChannelEnabled(image, channelIndex, thisChannelSettings.volumeEnabled);
|
|
234
|
+
if (image.channelNames[channelIndex] === maskChannelName) {
|
|
235
|
+
view3d.setVolumeChannelAsMask(image, channelIndex);
|
|
338
236
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
// when any channel data has arrived:
|
|
342
|
-
setSendingQueryRequest(false);
|
|
343
|
-
setOneChannelLoaded(channelIndex);
|
|
344
|
-
if (aimg.isLoaded()) {
|
|
345
|
-
view3d.updateActiveChannels(aimg);
|
|
346
|
-
setImageLoaded(true);
|
|
347
|
-
playControls.onImageLoaded();
|
|
237
|
+
if (image.isLoaded()) {
|
|
238
|
+
view3d.updateActiveChannels(image);
|
|
348
239
|
}
|
|
349
|
-
};
|
|
350
|
-
var
|
|
351
|
-
|
|
352
|
-
|
|
240
|
+
}, [view3d, channelSettings, maskChannelName, viewerState]);
|
|
241
|
+
var volume = useVolume(scenes, {
|
|
242
|
+
onChannelLoaded: onChannelLoaded,
|
|
243
|
+
onError: showError,
|
|
244
|
+
maskChannelName: maskChannelName
|
|
245
|
+
});
|
|
246
|
+
var image = volume.image,
|
|
247
|
+
setTime = volume.setTime,
|
|
248
|
+
setScene = volume.setScene;
|
|
353
249
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
var displayName = getDisplayName(name, idx, getCurrentViewerChannelSettings());
|
|
359
|
-
return displayName === ((_channelSettings$idx = channelSettings[idx]) === null || _channelSettings$idx === void 0 ? void 0 : _channelSettings$idx.displayName);
|
|
360
|
-
});
|
|
361
|
-
if (allNamesAreEqual) {
|
|
362
|
-
var _newChannelSettings = channelNames.map(function (channel, index) {
|
|
363
|
-
return _objectSpread(_objectSpread({}, channelSettings[index]), {}, {
|
|
364
|
-
name: channel
|
|
365
|
-
});
|
|
366
|
-
});
|
|
367
|
-
setChannelSettings(_newChannelSettings);
|
|
368
|
-
return _newChannelSettings;
|
|
250
|
+
// add the image to the viewer on load
|
|
251
|
+
useEffect(function () {
|
|
252
|
+
if (image === null) {
|
|
253
|
+
return;
|
|
369
254
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
});
|
|
374
|
-
setChannelSettings(newChannelSettings);
|
|
375
|
-
return newChannelSettings;
|
|
376
|
-
};
|
|
377
|
-
var placeImageInViewer = function placeImageInViewer(aimg, newChannelSettings) {
|
|
378
|
-
setImage(aimg);
|
|
379
|
-
var channelSetting = newChannelSettings || channelSettings;
|
|
380
|
-
view3d.removeAllVolumes();
|
|
381
|
-
view3d.addVolume(aimg, {
|
|
255
|
+
channelRangesRef.current = new Array(image.channelNames.length).fill(undefined);
|
|
256
|
+
var channelSettings = viewerState.current.channelSettings;
|
|
257
|
+
view3d.addVolume(image, {
|
|
382
258
|
// Immediately passing down channel parameters isn't strictly necessary, but keeps things looking consistent on load
|
|
383
|
-
channels:
|
|
384
|
-
|
|
259
|
+
channels: image.channelNames.map(function (name) {
|
|
260
|
+
// TODO do we really need to be searching by name here?
|
|
261
|
+
var ch = channelSettings.find(function (channel) {
|
|
262
|
+
return channel.name === name;
|
|
263
|
+
});
|
|
385
264
|
if (!ch) {
|
|
386
265
|
return {};
|
|
387
266
|
}
|
|
@@ -394,151 +273,47 @@ var App = function App(props) {
|
|
|
394
273
|
};
|
|
395
274
|
})
|
|
396
275
|
});
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
var max = aimg.imageInfo.volumeSize[axis];
|
|
407
|
-
var current = viewerState.current.slice[axis] * max;
|
|
408
|
-
changeViewerSetting("slice", _objectSpread(_objectSpread({}, viewerState.current.slice), {}, _defineProperty({}, axis, (current + 1) % max / max)));
|
|
409
|
-
}
|
|
410
|
-
};
|
|
411
|
-
playControls.getVolumeIsLoaded = function () {
|
|
412
|
-
return aimg.isLoaded();
|
|
413
|
-
};
|
|
414
|
-
view3d.updateActiveChannels(aimg);
|
|
415
|
-
// make sure we pick up whether the image needs to be in single-slice mode
|
|
416
|
-
view3d.setCameraMode(viewerSettings.viewMode);
|
|
276
|
+
view3d.updateActiveChannels(image);
|
|
277
|
+
return view3d.removeAllVolumes.bind(view3d);
|
|
278
|
+
}, [image, view3d, viewerState]);
|
|
279
|
+
var hasRawImage = !!(props.rawData && props.rawDims);
|
|
280
|
+
var numScenes = hasRawImage ? 1 : (_scenes$length = (_scenes2 = props.imageUrl.scenes) === null || _scenes2 === void 0 ? void 0 : _scenes2.length) !== null && _scenes$length !== void 0 ? _scenes$length : 1;
|
|
281
|
+
var numSlices = (_image$imageInfo$volu = image === null || image === void 0 ? void 0 : image.imageInfo.volumeSize) !== null && _image$imageInfo$volu !== void 0 ? _image$imageInfo$volu : {
|
|
282
|
+
x: 1,
|
|
283
|
+
y: 1,
|
|
284
|
+
z: 1
|
|
417
285
|
};
|
|
418
|
-
var
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
case 0:
|
|
425
|
-
_props = props, imageUrl = _props.imageUrl, parentImageUrl = _props.parentImageUrl, rawData = _props.rawData, rawDims = _props.rawDims;
|
|
426
|
-
scene = viewerState.current.scene;
|
|
427
|
-
if (!(rawData && rawDims)) {
|
|
428
|
-
_context.next = 6;
|
|
429
|
-
break;
|
|
430
|
-
}
|
|
431
|
-
scenePaths = [{
|
|
432
|
-
data: rawData,
|
|
433
|
-
metadata: rawDims
|
|
434
|
-
}];
|
|
435
|
-
_context.next = 12;
|
|
436
|
-
break;
|
|
437
|
-
case 6:
|
|
438
|
-
showParentImage = viewerState.current.imageType === ImageType.fullField && parentImageUrl !== undefined;
|
|
439
|
-
path = showParentImage ? parentImageUrl : imageUrl; // Don't reload if we're already looking at this image
|
|
440
|
-
if (!isEqual(path, imageUrlRef.current)) {
|
|
441
|
-
_context.next = 10;
|
|
442
|
-
break;
|
|
443
|
-
}
|
|
444
|
-
return _context.abrupt("return");
|
|
445
|
-
case 10:
|
|
446
|
-
imageUrlRef.current = path;
|
|
447
|
-
scenePaths = (_scenes2 = path.scenes) !== null && _scenes2 !== void 0 ? _scenes2 : [path];
|
|
448
|
-
case 12:
|
|
449
|
-
setSendingQueryRequest(true);
|
|
450
|
-
setImageLoaded(false);
|
|
451
|
-
hasChannelLoadedRef.current = [];
|
|
452
|
-
loadSpec = new LoadSpec();
|
|
453
|
-
loadSpec.time = viewerState.current.time;
|
|
454
|
-
options = {};
|
|
455
|
-
if (rawData && rawDims) {
|
|
456
|
-
options.fileType = VolumeFileFormat.DATA;
|
|
457
|
-
options.rawArrayOptions = {
|
|
458
|
-
data: rawData,
|
|
459
|
-
metadata: rawDims
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
_context.prev = 19;
|
|
463
|
-
loader.current = new SceneStore(loadContext, scenePaths);
|
|
464
|
-
_context.next = 23;
|
|
465
|
-
return loader.current.createVolume(scene, loadSpec, function (v, channelIndex) {
|
|
466
|
-
// NOTE: this callback runs *after* `onNewVolumeCreated` below, for every loaded channel
|
|
467
|
-
// TODO is this search by name necessary or will the `channelIndex` passed to the callback always match state?
|
|
468
|
-
var thisChannelSettings = viewerState.current.channelSettings[channelIndex];
|
|
469
|
-
onChannelDataLoaded(v, thisChannelSettings, channelIndex);
|
|
470
|
-
});
|
|
471
|
-
case 23:
|
|
472
|
-
aimg = _context.sent;
|
|
473
|
-
_context.next = 30;
|
|
474
|
-
break;
|
|
475
|
-
case 26:
|
|
476
|
-
_context.prev = 26;
|
|
477
|
-
_context.t0 = _context["catch"](19);
|
|
478
|
-
showError(_context.t0);
|
|
479
|
-
throw _context.t0;
|
|
480
|
-
case 30:
|
|
481
|
-
channelNames = aimg.imageInfo.channelNames;
|
|
482
|
-
newChannelSettings = setChannelStateForNewImage(channelNames); // order is important:
|
|
483
|
-
// we need to remove the old volume before triggering channels unloaded,
|
|
484
|
-
// which may cause calls on View3d to the old volume.
|
|
485
|
-
view3d.removeAllVolumes();
|
|
486
|
-
setAllChannelsUnloaded(channelNames.length);
|
|
487
|
-
placeImageInViewer(aimg, newChannelSettings);
|
|
488
|
-
channelRangesRef.current = new Array(channelNames.length).fill(undefined);
|
|
489
|
-
requiredLoadSpec = new LoadSpec();
|
|
490
|
-
requiredLoadSpec.time = viewerState.current.time;
|
|
491
|
-
|
|
492
|
-
// make the currently enabled channels "required":
|
|
493
|
-
// find all enabled indices in newChannelSettings:
|
|
494
|
-
requiredChannelsToLoad = newChannelSettings ? newChannelSettings.map(function (channel, index) {
|
|
495
|
-
return channel.volumeEnabled ? index : -1;
|
|
496
|
-
}).filter(function (index) {
|
|
497
|
-
return index >= 0;
|
|
498
|
-
}) : []; // add mask channel to required channels, if specified
|
|
499
|
-
maskChannelName = (_getCurrentViewerChan2 = getCurrentViewerChannelSettings()) === null || _getCurrentViewerChan2 === void 0 ? void 0 : _getCurrentViewerChan2.maskChannelName;
|
|
500
|
-
if (maskChannelName) {
|
|
501
|
-
maskChannelIndex = channelNames.indexOf(maskChannelName);
|
|
502
|
-
if (maskChannelIndex >= 0 && !requiredChannelsToLoad.includes(maskChannelIndex)) {
|
|
503
|
-
requiredChannelsToLoad.push(maskChannelIndex);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
requiredLoadSpec.channels = requiredChannelsToLoad;
|
|
507
|
-
|
|
508
|
-
// When in 2D Z-axis view mode, we restrict the subregion to only the current slice. This is
|
|
509
|
-
// to match an optimization that volume viewer does by loading Z-slices at a higher resolution,
|
|
510
|
-
// and ensures the very first volume that is loaded is the same as the one that
|
|
511
|
-
// will be shown whenever we switch back to the same viewer settings (2D Z-axis view mode).
|
|
512
|
-
// (We don't do this for ZX and YZ modes because we assume that the data won't be chunked along the
|
|
513
|
-
// X or Y axes in ways that would improve loading resolution, and we load the full 3D volume instead.)
|
|
514
|
-
if (viewerSettings.viewMode === ViewMode.xy) {
|
|
515
|
-
slice = viewerSettings.slice;
|
|
516
|
-
requiredLoadSpec.subregion = new Box3(new Vector3(0, 0, slice.z), new Vector3(1, 1, slice.z));
|
|
517
|
-
}
|
|
286
|
+
var numSlicesLoaded = (_image$imageInfo$subr = image === null || image === void 0 ? void 0 : image.imageInfo.subregionSize) !== null && _image$imageInfo$subr !== void 0 ? _image$imageInfo$subr : {
|
|
287
|
+
x: 0,
|
|
288
|
+
y: 0,
|
|
289
|
+
z: 0
|
|
290
|
+
};
|
|
291
|
+
var numTimesteps = (_image$imageInfo$time = image === null || image === void 0 ? void 0 : image.imageInfo.times) !== null && _image$imageInfo$time !== void 0 ? _image$imageInfo$time : 1;
|
|
518
292
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
293
|
+
// const [channelGroupedByType, setChannelGroupedByType] = useState<ChannelGrouping>({});
|
|
294
|
+
var _useState = useState(function () {
|
|
295
|
+
return window.innerWidth < CONTROL_PANEL_CLOSE_WIDTH;
|
|
296
|
+
}),
|
|
297
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
298
|
+
controlPanelClosed = _useState2[0],
|
|
299
|
+
setControlPanelClosed = _useState2[1];
|
|
300
|
+
// Only allow auto-close once while the screen is too narrow.
|
|
301
|
+
var _useState3 = useState(false),
|
|
302
|
+
_useState4 = _slicedToArray(_useState3, 2),
|
|
303
|
+
hasAutoClosedControlPanel = _useState4[0],
|
|
304
|
+
setHasAutoClosedControlPanel = _useState4[1];
|
|
305
|
+
var _useState5 = useState(true),
|
|
306
|
+
_useState6 = _slicedToArray(_useState5, 2),
|
|
307
|
+
clippingPanelOpen = _useState6[0],
|
|
308
|
+
setClippingPanelOpen = _useState6[1];
|
|
309
|
+
var clippingPanelOpenTimeout = useRef(0);
|
|
535
310
|
|
|
536
311
|
// Imperative callbacks /////////////////////////////////////////////////////
|
|
537
312
|
|
|
538
313
|
var viewerSettings = viewerState.current;
|
|
539
314
|
var saveIsosurface = useCallback(function (channelIndex, type) {
|
|
540
315
|
if (image) view3d.saveChannelIsosurface(image, channelIndex, type);
|
|
541
|
-
}, [image]);
|
|
316
|
+
}, [image, view3d]);
|
|
542
317
|
var saveScreenshot = useCallback(function () {
|
|
543
318
|
view3d.capture(function (dataUrl) {
|
|
544
319
|
var anchor = document.createElement("a");
|
|
@@ -546,30 +321,8 @@ var App = function App(props) {
|
|
|
546
321
|
anchor.download = "screenshot.png";
|
|
547
322
|
anchor.click();
|
|
548
323
|
});
|
|
549
|
-
}, []);
|
|
550
|
-
var resetCamera = useCallback(function () {
|
|
551
|
-
return view3d.resetCamera();
|
|
552
|
-
}, []);
|
|
553
|
-
var onClippingPanelVisibleChange = useCallback(function (panelOpen, hasTime, hasScenes, mode3d) {
|
|
554
|
-
clippingPanelOpenRef.current = panelOpen;
|
|
555
|
-
setIndicatorPositions(view3d, panelOpen, hasTime, hasScenes, mode3d);
|
|
556
|
-
|
|
557
|
-
// Hide indicators while clipping panel is in motion - otherwise they pop to the right place prematurely
|
|
558
|
-
view3d.setShowScaleBar(false);
|
|
559
|
-
view3d.setShowTimestepIndicator(false);
|
|
560
|
-
view3d.setShowAxis(false);
|
|
561
|
-
}, [viewerSettings.showAxes]);
|
|
562
|
-
var onClippingPanelVisibleChangeEnd = useCallback(function () {
|
|
563
|
-
view3d.setShowScaleBar(true);
|
|
564
|
-
view3d.setShowTimestepIndicator(true);
|
|
565
|
-
if (viewerSettings.showAxes) {
|
|
566
|
-
view3d.setShowAxis(true);
|
|
567
|
-
}
|
|
568
|
-
}, [viewerSettings.showAxes]);
|
|
324
|
+
}, [view3d]);
|
|
569
325
|
var getMetadata = useCallback(function () {
|
|
570
|
-
var _props2 = props,
|
|
571
|
-
metadata = _props2.metadata,
|
|
572
|
-
metadataFormatter = _props2.metadataFormatter;
|
|
573
326
|
var imageMetadata = image === null || image === void 0 ? void 0 : image.imageMetadata;
|
|
574
327
|
if (imageMetadata && metadataFormatter) {
|
|
575
328
|
imageMetadata = metadataFormatter(imageMetadata);
|
|
@@ -593,7 +346,28 @@ var App = function App(props) {
|
|
|
593
346
|
var _sceneMeta;
|
|
594
347
|
return (_sceneMeta = sceneMeta) !== null && _sceneMeta !== void 0 ? _sceneMeta : {};
|
|
595
348
|
}
|
|
596
|
-
}, [
|
|
349
|
+
}, [metadata, metadataFormatter, image, numScenes, viewerState]);
|
|
350
|
+
useEffect(function () {
|
|
351
|
+
var hasTime = numTimesteps > 1;
|
|
352
|
+
var hasScenes = numScenes > 1;
|
|
353
|
+
var mode3d = viewerSettings.viewMode === ViewMode.threeD;
|
|
354
|
+
setIndicatorPositions(view3d, clippingPanelOpen, hasTime, hasScenes, mode3d);
|
|
355
|
+
|
|
356
|
+
// Hide indicators while clipping panel is in motion - otherwise they pop to the right place prematurely
|
|
357
|
+
if (clippingPanelOpen) {
|
|
358
|
+
view3d.setShowScaleBar(false);
|
|
359
|
+
view3d.setShowTimestepIndicator(false);
|
|
360
|
+
view3d.setShowAxis(false);
|
|
361
|
+
window.clearTimeout(clippingPanelOpenTimeout.current);
|
|
362
|
+
clippingPanelOpenTimeout.current = window.setTimeout(function () {
|
|
363
|
+
view3d.setShowScaleBar(true);
|
|
364
|
+
view3d.setShowTimestepIndicator(true);
|
|
365
|
+
if (viewerSettings.showAxes) {
|
|
366
|
+
view3d.setShowAxis(true);
|
|
367
|
+
}
|
|
368
|
+
}, CLIPPING_PANEL_ANIMATION_DURATION_MS);
|
|
369
|
+
}
|
|
370
|
+
}, [view3d, numTimesteps, numScenes, viewerSettings.viewMode, viewerSettings.showAxes, clippingPanelOpen]);
|
|
597
371
|
|
|
598
372
|
// Effects //////////////////////////////////////////////////////////////////
|
|
599
373
|
|
|
@@ -615,19 +389,9 @@ var App = function App(props) {
|
|
|
615
389
|
return window.removeEventListener("resize", onResizeDebounced);
|
|
616
390
|
};
|
|
617
391
|
}, [hasAutoClosedControlPanel]);
|
|
618
|
-
|
|
619
|
-
// one-time init after view3d exists and before we start loading images
|
|
620
|
-
useEffect(function () {
|
|
621
|
-
view3d.setCameraMode(viewerSettings.viewMode);
|
|
622
|
-
}, []);
|
|
623
|
-
|
|
624
|
-
// Hook to trigger image load: on mount, when image source props/state change (`cellId`, `imageType`, `rawData`, etc)
|
|
625
|
-
useEffect(function () {
|
|
626
|
-
openImage();
|
|
627
|
-
}, [props.imageUrl, props.cellId, viewerSettings.imageType, props.rawDims, props.rawData]);
|
|
628
392
|
useEffect(function () {
|
|
629
|
-
return
|
|
630
|
-
}, [controlPanelClosed,
|
|
393
|
+
return onControlPanelToggle && onControlPanelToggle(controlPanelClosed);
|
|
394
|
+
}, [controlPanelClosed, onControlPanelToggle]);
|
|
631
395
|
useEffect(function () {
|
|
632
396
|
// delayed for the animation to finish
|
|
633
397
|
window.setTimeout(function () {
|
|
@@ -638,44 +402,39 @@ var App = function App(props) {
|
|
|
638
402
|
/** Custom effect hook for viewer updates that depend on `image`, so we don't have to repeatedly null-check it */
|
|
639
403
|
var useImageEffect = function useImageEffect(effect, deps) {
|
|
640
404
|
useEffect(function () {
|
|
641
|
-
if (image &&
|
|
405
|
+
if (image && volume.imageLoadStatus === ImageLoadStatus.LOADED) {
|
|
642
406
|
return effect(image);
|
|
643
407
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
imageLoadHandlers.current = [];
|
|
648
|
-
/** `ImageEffect`s that also run right on image creation, so the image doesn't first render with default settings */
|
|
649
|
-
var useImageLoadEffect = function useImageLoadEffect(effect, deps) {
|
|
650
|
-
useImageEffect(effect, deps);
|
|
651
|
-
imageLoadHandlers.current.push(effect);
|
|
408
|
+
// react-hooks will check that `deps` match `effect`'s dependencies, so we can safely exclude `effect` here
|
|
409
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
410
|
+
}, [].concat(_toConsumableArray(deps), [image, volume.imageLoadStatus]));
|
|
652
411
|
};
|
|
653
412
|
|
|
654
413
|
// Effects to imperatively sync `viewerSettings` to `view3d`
|
|
655
414
|
useImageEffect(function (_currentImage) {
|
|
656
415
|
view3d.setCameraMode(viewerSettings.viewMode);
|
|
657
416
|
view3d.resize(null);
|
|
658
|
-
}, [viewerSettings.viewMode]);
|
|
417
|
+
}, [viewerSettings.viewMode, view3d]);
|
|
659
418
|
useImageEffect(function (_currentImage) {
|
|
660
419
|
if (viewerSettings.cameraState) {
|
|
661
420
|
view3d.setCameraState(viewerSettings.cameraState);
|
|
662
421
|
}
|
|
663
|
-
}, [viewerSettings.cameraState]);
|
|
422
|
+
}, [viewerSettings.cameraState, view3d]);
|
|
664
423
|
useImageEffect(function (_currentImage) {
|
|
665
424
|
return view3d.setAutoRotate(viewerSettings.autorotate);
|
|
666
|
-
}, [viewerSettings.autorotate]);
|
|
425
|
+
}, [viewerSettings.autorotate, view3d]);
|
|
667
426
|
useImageEffect(function (_currentImage) {
|
|
668
427
|
return view3d.setShowAxis(viewerSettings.showAxes);
|
|
669
|
-
}, [viewerSettings.showAxes]);
|
|
428
|
+
}, [viewerSettings.showAxes, view3d]);
|
|
670
429
|
useImageEffect(function (_currentImage) {
|
|
671
430
|
return view3d.setBackgroundColor(colorArrayToFloats(viewerSettings.backgroundColor));
|
|
672
|
-
}, [viewerSettings.backgroundColor]);
|
|
431
|
+
}, [viewerSettings.backgroundColor, view3d]);
|
|
673
432
|
useImageEffect(function (currentImage) {
|
|
674
433
|
return view3d.setBoundingBoxColor(currentImage, colorArrayToFloats(viewerSettings.boundingBoxColor));
|
|
675
|
-
}, [viewerSettings.boundingBoxColor]);
|
|
434
|
+
}, [viewerSettings.boundingBoxColor, view3d]);
|
|
676
435
|
useImageEffect(function (currentImage) {
|
|
677
436
|
return view3d.setShowBoundingBox(currentImage, viewerSettings.showBoundingBox);
|
|
678
|
-
}, [viewerSettings.showBoundingBox]);
|
|
437
|
+
}, [viewerSettings.showBoundingBox, view3d]);
|
|
679
438
|
useImageEffect(function (image) {
|
|
680
439
|
// Check whether any channels are marked to be reset and apply it.
|
|
681
440
|
var channelsAwaitingReset = getChannelsAwaitingReset();
|
|
@@ -691,64 +450,52 @@ var App = function App(props) {
|
|
|
691
450
|
onResetChannel(i);
|
|
692
451
|
}
|
|
693
452
|
}
|
|
694
|
-
}, [channelSettings]);
|
|
695
|
-
|
|
453
|
+
}, [changeChannelSetting, channelSettings, getChannelsAwaitingReset, getCurrentViewerChannelSettings, onResetChannel]);
|
|
454
|
+
useImageEffect(function (currentImage) {
|
|
696
455
|
var renderMode = viewerSettings.renderMode;
|
|
697
456
|
view3d.setMaxProjectMode(currentImage, renderMode === RenderMode.maxProject);
|
|
698
457
|
view3d.setVolumeRenderMode(renderMode === RenderMode.pathTrace ? RENDERMODE_PATHTRACE : RENDERMODE_RAYMARCH);
|
|
699
458
|
view3d.updateActiveChannels(currentImage);
|
|
700
|
-
}, [viewerSettings.renderMode]);
|
|
459
|
+
}, [viewerSettings.renderMode, view3d]);
|
|
701
460
|
useImageEffect(function (currentImage) {
|
|
702
461
|
view3d.updateMaskAlpha(currentImage, alphaSliderToImageValue(viewerSettings.maskAlpha));
|
|
703
462
|
view3d.updateActiveChannels(currentImage);
|
|
704
|
-
}, [viewerSettings.maskAlpha]);
|
|
705
|
-
|
|
706
|
-
var
|
|
707
|
-
var brightness = brightnessSliderToImageValue(viewerSettings.brightness, isPathTracing);
|
|
463
|
+
}, [viewerSettings.maskAlpha, view3d]);
|
|
464
|
+
useImageEffect(function (_currentImage) {
|
|
465
|
+
var brightness = brightnessSliderToImageValue(viewerSettings.brightness);
|
|
708
466
|
view3d.updateExposure(brightness);
|
|
709
|
-
}, [viewerSettings.brightness]);
|
|
710
|
-
|
|
711
|
-
var
|
|
712
|
-
var density = densitySliderToImageValue(viewerSettings.density, isPathTracing);
|
|
467
|
+
}, [viewerSettings.brightness, view3d]);
|
|
468
|
+
useImageEffect(function (currentImage) {
|
|
469
|
+
var density = densitySliderToImageValue(viewerSettings.density);
|
|
713
470
|
view3d.updateDensity(currentImage, density);
|
|
714
|
-
}, [viewerSettings.density]);
|
|
715
|
-
|
|
471
|
+
}, [viewerSettings.density, view3d]);
|
|
472
|
+
useImageEffect(function (currentImage) {
|
|
716
473
|
var imageValues = gammaSliderToImageValues(viewerSettings.levels);
|
|
717
474
|
view3d.setGamma(currentImage, imageValues.min, imageValues.scale, imageValues.max);
|
|
718
|
-
}, [viewerSettings.levels]);
|
|
475
|
+
}, [viewerSettings.levels, view3d]);
|
|
719
476
|
|
|
720
|
-
// `time`
|
|
477
|
+
// `time` and `scene` have their own special handlers via `volume`, since they both trigger loads
|
|
721
478
|
useEffect(function () {
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
setAllChannelsUnloaded(image.numChannels);
|
|
725
|
-
view3d.setTime(image, viewerSettings.time);
|
|
726
|
-
}
|
|
727
|
-
}, [viewerSettings.time]);
|
|
479
|
+
return setTime(view3d, viewerSettings.time);
|
|
480
|
+
}, [view3d, viewerSettings.time, setTime]);
|
|
728
481
|
useEffect(function () {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
(_loader$current3 = loader.current) === null || _loader$current3 === void 0 || _loader$current3.loadScene(viewerSettings.scene, image)["catch"](function (e) {
|
|
733
|
-
return showError(e);
|
|
734
|
-
});
|
|
735
|
-
}
|
|
736
|
-
}, [viewerSettings.scene]);
|
|
737
|
-
useImageLoadEffect(function (currentImage) {
|
|
482
|
+
return setScene(viewerSettings.scene);
|
|
483
|
+
}, [viewerSettings.scene, setScene]);
|
|
484
|
+
useImageEffect(function (currentImage) {
|
|
738
485
|
return view3d.setInterpolationEnabled(currentImage, viewerSettings.interpolationEnabled);
|
|
739
|
-
}, [viewerSettings.interpolationEnabled]);
|
|
740
|
-
|
|
486
|
+
}, [viewerSettings.interpolationEnabled, view3d]);
|
|
487
|
+
useImageEffect(function (currentImage) {
|
|
741
488
|
var _props$transform;
|
|
742
489
|
return view3d.setVolumeTranslation(currentImage, ((_props$transform = props.transform) === null || _props$transform === void 0 ? void 0 : _props$transform.translation) || [0, 0, 0]);
|
|
743
|
-
}, [(_props$transform2 = props.transform) === null || _props$transform2 === void 0 ? void 0 : _props$transform2.translation]);
|
|
744
|
-
|
|
490
|
+
}, [(_props$transform2 = props.transform) === null || _props$transform2 === void 0 ? void 0 : _props$transform2.translation, view3d]);
|
|
491
|
+
useImageEffect(function (currentImage) {
|
|
745
492
|
var _props$transform3;
|
|
746
493
|
return view3d.setVolumeRotation(currentImage, ((_props$transform3 = props.transform) === null || _props$transform3 === void 0 ? void 0 : _props$transform3.rotation) || [0, 0, 0]);
|
|
747
|
-
}, [(_props$transform4 = props.transform) === null || _props$transform4 === void 0 ? void 0 : _props$transform4.rotation]);
|
|
748
|
-
var usePerAxisClippingUpdater = function usePerAxisClippingUpdater(axis,
|
|
749
|
-
var
|
|
750
|
-
minval =
|
|
751
|
-
maxval =
|
|
494
|
+
}, [(_props$transform4 = props.transform) === null || _props$transform4 === void 0 ? void 0 : _props$transform4.rotation, view3d]);
|
|
495
|
+
var usePerAxisClippingUpdater = function usePerAxisClippingUpdater(axis, _ref, slice, viewMode) {
|
|
496
|
+
var _ref2 = _slicedToArray(_ref, 2),
|
|
497
|
+
minval = _ref2[0],
|
|
498
|
+
maxval = _ref2[1];
|
|
752
499
|
useImageEffect(
|
|
753
500
|
// Logic to determine axis clipping range, for each of x,y,z,3d slider:
|
|
754
501
|
// if slider was same as active axis view mode: [viewerSettings.slice[axis], viewerSettings.slice[axis] + 1.0/volumeSize[axis]]
|
|
@@ -758,30 +505,30 @@ var App = function App(props) {
|
|
|
758
505
|
var isOrthoAxis = false;
|
|
759
506
|
var axismin = 0.0;
|
|
760
507
|
var axismax = 1.0;
|
|
761
|
-
if (
|
|
508
|
+
if (viewMode === ViewMode.threeD) {
|
|
762
509
|
axismin = minval;
|
|
763
510
|
axismax = maxval;
|
|
764
511
|
isOrthoAxis = false;
|
|
765
512
|
} else {
|
|
766
|
-
isOrthoAxis = activeAxisMap[
|
|
513
|
+
isOrthoAxis = activeAxisMap[viewMode] === axis;
|
|
767
514
|
var oneSlice = 1 / currentImage.imageInfo.volumeSize[axis];
|
|
768
515
|
axismin = isOrthoAxis ? slice : 0.0;
|
|
769
516
|
axismax = isOrthoAxis ? slice + oneSlice : 1.0;
|
|
770
|
-
if (axis === "z" &&
|
|
517
|
+
if (axis === "z" && viewMode === ViewMode.xy) {
|
|
771
518
|
view3d.setZSlice(currentImage, Math.floor(slice * currentImage.imageInfo.volumeSize.z));
|
|
772
|
-
if (!currentImage.isLoaded()) {
|
|
773
|
-
setImageLoaded(false);
|
|
774
|
-
}
|
|
775
519
|
}
|
|
776
520
|
}
|
|
777
521
|
// view3d wants the coordinates in the -0.5 to 0.5 range
|
|
778
522
|
view3d.setAxisClip(currentImage, axis, axismin - 0.5, axismax - 0.5, isOrthoAxis);
|
|
779
|
-
view3d.setCameraMode(
|
|
780
|
-
|
|
523
|
+
view3d.setCameraMode(viewMode);
|
|
524
|
+
// TODO under some circumstances, this effect will trigger a load. Ideally, this would be reflected in the load
|
|
525
|
+
// state managed by `useVolume`. This is complicated by the fact that the relevant methods (`setAxisClip` and
|
|
526
|
+
// `setZSlice`) don't provide a channel load callback like other load-triggering methods (e.g. `setTime`).
|
|
527
|
+
}, [axis, minval, maxval, slice, viewMode]);
|
|
781
528
|
};
|
|
782
|
-
usePerAxisClippingUpdater("x", viewerSettings.region.x, viewerSettings.slice.x);
|
|
783
|
-
usePerAxisClippingUpdater("y", viewerSettings.region.y, viewerSettings.slice.y);
|
|
784
|
-
usePerAxisClippingUpdater("z", viewerSettings.region.z, viewerSettings.slice.z);
|
|
529
|
+
usePerAxisClippingUpdater("x", viewerSettings.region.x, viewerSettings.slice.x, viewMode);
|
|
530
|
+
usePerAxisClippingUpdater("y", viewerSettings.region.y, viewerSettings.slice.y, viewMode);
|
|
531
|
+
usePerAxisClippingUpdater("z", viewerSettings.region.z, viewerSettings.slice.z, viewMode);
|
|
785
532
|
|
|
786
533
|
// Rendering ////////////////////////////////////////////////////////////////
|
|
787
534
|
|
|
@@ -790,7 +537,10 @@ var App = function App(props) {
|
|
|
790
537
|
}, [props.visibleControls]);
|
|
791
538
|
var pixelSize = useMemo(function () {
|
|
792
539
|
return image ? image.imageInfo.physicalPixelSize.toArray() : [1, 1, 1];
|
|
793
|
-
}, [image
|
|
540
|
+
}, [image]);
|
|
541
|
+
var resetCamera = useMemo(function () {
|
|
542
|
+
return view3d.resetCamera.bind(view3d);
|
|
543
|
+
}, [view3d]);
|
|
794
544
|
return /*#__PURE__*/React.createElement(StyleProvider, null, errorAlert, /*#__PURE__*/React.createElement(Layout, {
|
|
795
545
|
className: "cell-viewer-app",
|
|
796
546
|
style: {
|
|
@@ -803,7 +553,7 @@ var App = function App(props) {
|
|
|
803
553
|
index: index,
|
|
804
554
|
view3d: view3d,
|
|
805
555
|
image: image,
|
|
806
|
-
version: channelVersions[index]
|
|
556
|
+
version: volume.channelVersions[index]
|
|
807
557
|
});
|
|
808
558
|
}), /*#__PURE__*/React.createElement(Sider, {
|
|
809
559
|
className: "control-panel-holder",
|
|
@@ -822,7 +572,7 @@ var App = function App(props) {
|
|
|
822
572
|
hasImage: !!image,
|
|
823
573
|
pixelSize: pixelSize,
|
|
824
574
|
channelDataChannels: image === null || image === void 0 ? void 0 : image.channels,
|
|
825
|
-
channelGroupedByType: channelGroupedByType
|
|
575
|
+
channelGroupedByType: volume.channelGroupedByType
|
|
826
576
|
// functions
|
|
827
577
|
,
|
|
828
578
|
setCollapsed: setControlPanelClosed,
|
|
@@ -847,17 +597,17 @@ var App = function App(props) {
|
|
|
847
597
|
}), /*#__PURE__*/React.createElement(CellViewerCanvasWrapper, {
|
|
848
598
|
view3d: view3d,
|
|
849
599
|
image: image,
|
|
850
|
-
loadingImage:
|
|
600
|
+
loadingImage: volume.imageLoadStatus === ImageLoadStatus.REQUESTED,
|
|
851
601
|
numSlices: numSlices,
|
|
852
602
|
numSlicesLoaded: numSlicesLoaded,
|
|
853
603
|
numTimesteps: numTimesteps,
|
|
854
604
|
numScenes: numScenes,
|
|
855
|
-
playControls: playControls,
|
|
856
|
-
playingAxis: playingAxis,
|
|
605
|
+
playControls: volume.playControls,
|
|
606
|
+
playingAxis: volume.playingAxis,
|
|
857
607
|
appHeight: props.appHeight,
|
|
858
608
|
visibleControls: visibleControls,
|
|
859
|
-
|
|
860
|
-
|
|
609
|
+
clippingPanelOpen: clippingPanelOpen,
|
|
610
|
+
onClippingPanelOpenChange: setClippingPanelOpen
|
|
861
611
|
})))));
|
|
862
612
|
};
|
|
863
613
|
export default App;
|