@aics/vole-app 2.13.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/index.js +211 -479
- package/es/aics-image-viewer/components/AxisClipSliders/index.js +2 -2
- package/es/aics-image-viewer/components/useVolume.js +327 -0
- package/es/aics-image-viewer/shared/constants.js +9 -0
- package/es/aics-image-viewer/shared/utils/hooks.js +1 -24
- package/es/aics-image-viewer/shared/utils/sliderValuesToImageValues.js +15 -9
- package/package.json +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,6 @@ var defaultProps = {
|
|
|
81
74
|
canvasMargin: "0 0 0 0",
|
|
82
75
|
view3dRef: undefined
|
|
83
76
|
};
|
|
84
|
-
var axisToLoaderPriority = {
|
|
85
|
-
t: PrefetchDirection.T_PLUS,
|
|
86
|
-
z: PrefetchDirection.Z_PLUS,
|
|
87
|
-
y: PrefetchDirection.Y_PLUS,
|
|
88
|
-
x: PrefetchDirection.X_PLUS
|
|
89
|
-
};
|
|
90
77
|
var CLIPPING_PANEL_ANIMATION_DURATION_MS = 300;
|
|
91
78
|
var setIndicatorPositions = function setIndicatorPositions(view3d, panelOpen, hasTime, hasScenes, isMode3d) {
|
|
92
79
|
// The height of the clipping panel includes the button, but we're trying to put these elements next to the button
|
|
@@ -116,59 +103,41 @@ var setIndicatorPositions = function setIndicatorPositions(view3d, panelOpen, ha
|
|
|
116
103
|
view3d.setTimestepIndicatorPosition(SCALE_BAR_MARGIN_DEFAULT[0], scaleBarY);
|
|
117
104
|
view3d.setScaleBarPosition(scaleBarX, scaleBarY);
|
|
118
105
|
};
|
|
119
|
-
|
|
120
|
-
// TODO this component, specifically its volume loading behavior, is way out of compliance with react-hooks lints
|
|
121
|
-
// and will need a larger refactoring pass to fix that.
|
|
122
|
-
/* eslint-disable react-hooks/exhaustive-deps */
|
|
123
|
-
/* eslint-disable react-hooks/rules-of-hooks */
|
|
124
106
|
var App = function App(props) {
|
|
125
|
-
var _scenes$length,
|
|
107
|
+
var _getCurrentViewerChan, _scenes$length, _scenes2, _image$imageInfo$volu, _image$imageInfo$subr, _image$imageInfo$time, _props$transform2, _props$transform4;
|
|
126
108
|
props = _objectSpread(_objectSpread({}, defaultProps), props);
|
|
127
109
|
|
|
128
110
|
// State management /////////////////////////////////////////////////////////
|
|
129
111
|
var viewerState = useContext(ViewerStateContext).ref;
|
|
130
112
|
var _viewerState$current = viewerState.current,
|
|
113
|
+
imageType = _viewerState$current.imageType,
|
|
114
|
+
viewMode = _viewerState$current.viewMode,
|
|
131
115
|
channelSettings = _viewerState$current.channelSettings,
|
|
132
|
-
setChannelSettings = _viewerState$current.setChannelSettings,
|
|
133
|
-
changeViewerSetting = _viewerState$current.changeViewerSetting,
|
|
134
116
|
changeChannelSetting = _viewerState$current.changeChannelSetting,
|
|
135
117
|
applyColorPresets = _viewerState$current.applyColorPresets,
|
|
136
118
|
setSavedViewerChannelSettings = _viewerState$current.setSavedViewerChannelSettings,
|
|
137
119
|
getCurrentViewerChannelSettings = _viewerState$current.getCurrentViewerChannelSettings,
|
|
138
120
|
getChannelsAwaitingReset = _viewerState$current.getChannelsAwaitingReset,
|
|
139
|
-
getChannelsAwaitingResetOnLoad = _viewerState$current.getChannelsAwaitingResetOnLoad,
|
|
140
121
|
onResetChannel = _viewerState$current.onResetChannel;
|
|
122
|
+
var _props = props,
|
|
123
|
+
onControlPanelToggle = _props.onControlPanelToggle,
|
|
124
|
+
metadata = _props.metadata,
|
|
125
|
+
metadataFormatter = _props.metadataFormatter;
|
|
141
126
|
useMemo(function () {
|
|
142
127
|
if (props.viewerChannelSettings) {
|
|
143
128
|
setSavedViewerChannelSettings(props.viewerChannelSettings);
|
|
144
129
|
}
|
|
145
|
-
}, [props.viewerChannelSettings]);
|
|
130
|
+
}, [props.viewerChannelSettings, setSavedViewerChannelSettings]);
|
|
146
131
|
var view3d = useConstructor(function () {
|
|
147
132
|
return new View3d();
|
|
148
133
|
});
|
|
149
134
|
if (props.view3dRef !== undefined) {
|
|
150
135
|
props.view3dRef.current = view3d;
|
|
151
136
|
}
|
|
152
|
-
var loadContext = useConstructor(function () {
|
|
153
|
-
return new VolumeLoaderContext(CACHE_MAX_SIZE, QUEUE_MAX_SIZE, QUEUE_MAX_LOW_PRIORITY_SIZE);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
// install loadContext into view3d
|
|
157
|
-
view3d.loaderContext = loadContext;
|
|
158
|
-
var loader = useRef();
|
|
159
|
-
var _useState = useState(null),
|
|
160
|
-
_useState2 = _slicedToArray(_useState, 2),
|
|
161
|
-
image = _useState2[0],
|
|
162
|
-
setImage = _useState2[1];
|
|
163
|
-
var imageUrlRef = useRef("");
|
|
164
137
|
var _useErrorAlert = useErrorAlert(),
|
|
165
138
|
_useErrorAlert2 = _slicedToArray(_useErrorAlert, 2),
|
|
166
139
|
errorAlert = _useErrorAlert2[0],
|
|
167
|
-
|
|
168
|
-
var showError = function showError(error) {
|
|
169
|
-
_showError(error);
|
|
170
|
-
setSendingQueryRequest(false);
|
|
171
|
-
};
|
|
140
|
+
showError = _useErrorAlert2[1];
|
|
172
141
|
useEffect(function () {
|
|
173
142
|
// Get notifications of loading errors which occur after the initial load, e.g. on time change or new channel load
|
|
174
143
|
view3d.setLoadErrorHandler(function (_vol, e) {
|
|
@@ -177,142 +146,67 @@ var App = function App(props) {
|
|
|
177
146
|
return function () {
|
|
178
147
|
return view3d.setLoadErrorHandler(undefined);
|
|
179
148
|
};
|
|
180
|
-
}, [view3d]);
|
|
181
|
-
var
|
|
182
|
-
var
|
|
183
|
-
var
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
var
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
_useState4 = _slicedToArray(_useState3, 2),
|
|
202
|
-
sendingQueryRequest = _useState4[0],
|
|
203
|
-
setSendingQueryRequest = _useState4[1];
|
|
204
|
-
// `true` when all channels of the current image are loaded
|
|
205
|
-
var _useState5 = useState(false),
|
|
206
|
-
_useState6 = _slicedToArray(_useState5, 2),
|
|
207
|
-
imageLoaded = _useState6[0],
|
|
208
|
-
setImageLoaded = _useState6[1];
|
|
209
|
-
// tracks which channels have been loaded
|
|
210
|
-
var _useStateWithGetter = useStateWithGetter([]),
|
|
211
|
-
_useStateWithGetter2 = _slicedToArray(_useStateWithGetter, 3),
|
|
212
|
-
channelVersions = _useStateWithGetter2[0],
|
|
213
|
-
setChannelVersions = _useStateWithGetter2[1],
|
|
214
|
-
getChannelVersions = _useStateWithGetter2[2];
|
|
215
|
-
// we need to keep track of channel ranges for remapping
|
|
216
|
-
var channelRangesRef = useRef([]);
|
|
217
|
-
var _useState7 = useState({}),
|
|
218
|
-
_useState8 = _slicedToArray(_useState7, 2),
|
|
219
|
-
channelGroupedByType = _useState8[0],
|
|
220
|
-
setChannelGroupedByType = _useState8[1];
|
|
221
|
-
var _useState9 = useState(function () {
|
|
222
|
-
return window.innerWidth < CONTROL_PANEL_CLOSE_WIDTH;
|
|
223
|
-
}),
|
|
224
|
-
_useState10 = _slicedToArray(_useState9, 2),
|
|
225
|
-
controlPanelClosed = _useState10[0],
|
|
226
|
-
setControlPanelClosed = _useState10[1];
|
|
227
|
-
// Only allow auto-close once while the screen is too narrow.
|
|
228
|
-
var _useState11 = useState(false),
|
|
229
|
-
_useState12 = _slicedToArray(_useState11, 2),
|
|
230
|
-
hasAutoClosedControlPanel = _useState12[0],
|
|
231
|
-
setHasAutoClosedControlPanel = _useState12[1];
|
|
232
|
-
var _useState13 = useState(true),
|
|
233
|
-
_useState14 = _slicedToArray(_useState13, 2),
|
|
234
|
-
clippingPanelOpen = _useState14[0],
|
|
235
|
-
setClippingPanelOpen = _useState14[1];
|
|
236
|
-
var clippingPanelOpenTimeout = useRef(0);
|
|
237
|
-
|
|
238
|
-
// `PlayControls` manages playing through time and spatial axes, which isn't practical with "pure" React
|
|
239
|
-
// Playback state goes here, but the play/pause buttons that mainly control this class are down in `AxisClipSliders`
|
|
240
|
-
var playControls = useConstructor(function () {
|
|
241
|
-
return new PlayControls();
|
|
242
|
-
});
|
|
243
|
-
var _useState15 = useState(null),
|
|
244
|
-
_useState16 = _slicedToArray(_useState15, 2),
|
|
245
|
-
playingAxis = _useState16[0],
|
|
246
|
-
setPlayingAxis = _useState16[1];
|
|
247
|
-
playControls.onPlayingAxisChanged = function (axis) {
|
|
248
|
-
var _loader$current, _loader$current2;
|
|
249
|
-
(_loader$current = loader.current) === null || _loader$current === void 0 || _loader$current.setPrefetchPriority(axis ? [axisToLoaderPriority[axis]] : []);
|
|
250
|
-
(_loader$current2 = loader.current) === null || _loader$current2 === void 0 || _loader$current2.syncMultichannelLoading(axis ? true : false);
|
|
251
|
-
if (image) {
|
|
252
|
-
if (axis === null) {
|
|
253
|
-
// Playback has stopped - reset scale level bias
|
|
254
|
-
view3d.setScaleLevelBias(image, 0);
|
|
255
|
-
} else {
|
|
256
|
-
// Playback has started - unless entire axis is in memory (typical in X and Y), downlevel to speed things up
|
|
257
|
-
var shouldDownlevel = axis === "t" || numSlices[axis] !== numSlicesLoaded[axis];
|
|
258
|
-
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;
|
|
259
170
|
}
|
|
171
|
+
imageUrlRef.current = path;
|
|
172
|
+
var result = (_scenes = path.scenes) !== null && _scenes !== void 0 ? _scenes : [path];
|
|
173
|
+
scenesRef.current = result;
|
|
174
|
+
return result;
|
|
260
175
|
}
|
|
261
|
-
|
|
262
|
-
|
|
176
|
+
}, [imageUrl, parentImageUrl, rawData, rawDims, imageType]);
|
|
177
|
+
var maskChannelName = (_getCurrentViewerChan = getCurrentViewerChannelSettings()) === null || _getCurrentViewerChan === void 0 ? void 0 : _getCurrentViewerChan.maskChannelName;
|
|
263
178
|
|
|
264
|
-
//
|
|
265
|
-
|
|
266
|
-
var
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
var
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Updates a channel's ramp and control points after new data has been loaded.
|
|
284
|
-
*
|
|
285
|
-
* Also handles initializing the ramp/control points on initial load and resetting
|
|
286
|
-
* them when the channel is reset.
|
|
287
|
-
*/
|
|
288
|
-
var updateChannelTransferFunction = function updateChannelTransferFunction(aimg, thisChannelsSettings, channelIndex) {
|
|
289
|
-
var thisChannel = aimg.getChannel(channelIndex);
|
|
290
|
-
|
|
291
|
-
// If this is the first load of this image, auto-generate initial LUTs
|
|
292
|
-
if (!hasChannelLoadedRef.current[channelIndex] || !thisChannelsSettings.controlPoints || !thisChannelsSettings.ramp || getChannelsAwaitingResetOnLoad().has(channelIndex)) {
|
|
293
|
-
var viewerChannelSettings = getCurrentViewerChannelSettings();
|
|
294
|
-
var _initializeLut = initializeLut(aimg, channelIndex, viewerChannelSettings),
|
|
295
|
-
ramp = _initializeLut.ramp,
|
|
296
|
-
controlPoints = _initializeLut.controlPoints;
|
|
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;
|
|
297
197
|
changeChannelSetting(channelIndex, {
|
|
298
|
-
controlPoints:
|
|
299
|
-
ramp: controlPointsToRamp(
|
|
198
|
+
controlPoints: _controlPoints,
|
|
199
|
+
ramp: controlPointsToRamp(_ramp),
|
|
300
200
|
// set the default range of the transfer function editor to cover the full range of the data type
|
|
301
|
-
plotMin: DTYPE_RANGE[
|
|
302
|
-
plotMax: DTYPE_RANGE[
|
|
201
|
+
plotMin: DTYPE_RANGE[dtype].min,
|
|
202
|
+
plotMax: DTYPE_RANGE[dtype].max
|
|
303
203
|
});
|
|
304
|
-
onResetChannel(channelIndex);
|
|
305
204
|
} else {
|
|
306
|
-
//
|
|
307
|
-
// if (playingAxis !== null) {
|
|
308
|
-
// do nothing here?
|
|
309
|
-
// tell gui that we have updated control pts?
|
|
310
|
-
//changeChannelSetting(channelIndex, "controlPoints", aimg.getChannel(channelIndex).lut.controlPoints);
|
|
311
|
-
// }
|
|
205
|
+
// This channel has already been initialized, but its LUT was just remapped and we need to update some things
|
|
312
206
|
var oldRange = channelRangesRef.current[channelIndex];
|
|
313
|
-
if (
|
|
207
|
+
if (thisChannelSettings.useControlPoints) {
|
|
314
208
|
// control points were just automatically remapped - update in state
|
|
315
|
-
var rampControlPoints = rampToControlPoints(
|
|
209
|
+
var rampControlPoints = rampToControlPoints(thisChannelSettings.ramp);
|
|
316
210
|
// now manually remap ramp using the channel's old range
|
|
317
211
|
var remappedRampControlPoints = remapControlPointsForChannel(rampControlPoints, oldRange, thisChannel);
|
|
318
212
|
changeChannelSetting(channelIndex, {
|
|
@@ -321,76 +215,52 @@ var App = function App(props) {
|
|
|
321
215
|
});
|
|
322
216
|
} else {
|
|
323
217
|
// ramp was just automatically remapped - update in state
|
|
324
|
-
var
|
|
218
|
+
var _ramp2 = controlPointsToRamp(thisChannel.lut.controlPoints);
|
|
325
219
|
// now manually remap control points using the channel's old range
|
|
326
|
-
var
|
|
327
|
-
var remappedControlPoints = remapControlPointsForChannel(
|
|
220
|
+
var _controlPoints2 = thisChannelSettings.controlPoints;
|
|
221
|
+
var remappedControlPoints = remapControlPointsForChannel(_controlPoints2, oldRange, thisChannel);
|
|
328
222
|
changeChannelSetting(channelIndex, {
|
|
329
223
|
controlPoints: remappedControlPoints,
|
|
330
|
-
ramp:
|
|
224
|
+
ramp: _ramp2
|
|
331
225
|
});
|
|
332
226
|
}
|
|
333
227
|
}
|
|
334
|
-
};
|
|
335
|
-
var onChannelDataLoaded = function onChannelDataLoaded(aimg, thisChannelsSettings, channelIndex) {
|
|
336
|
-
var _getCurrentViewerChan;
|
|
337
|
-
var thisChannel = aimg.getChannel(channelIndex);
|
|
338
|
-
updateChannelTransferFunction(aimg, thisChannelsSettings, channelIndex);
|
|
339
228
|
|
|
340
229
|
// save the channel's new range for remapping next time
|
|
341
230
|
channelRangesRef.current[channelIndex] = [thisChannel.rawMin, thisChannel.rawMax];
|
|
342
|
-
view3d.updateLuts(
|
|
343
|
-
view3d.onVolumeData(
|
|
344
|
-
view3d.setVolumeChannelEnabled(
|
|
345
|
-
if (
|
|
346
|
-
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);
|
|
347
236
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
// when any channel data has arrived:
|
|
351
|
-
setSendingQueryRequest(false);
|
|
352
|
-
setOneChannelLoaded(channelIndex);
|
|
353
|
-
if (aimg.isLoaded()) {
|
|
354
|
-
view3d.updateActiveChannels(aimg);
|
|
355
|
-
setImageLoaded(true);
|
|
356
|
-
playControls.onImageLoaded();
|
|
237
|
+
if (image.isLoaded()) {
|
|
238
|
+
view3d.updateActiveChannels(image);
|
|
357
239
|
}
|
|
358
|
-
};
|
|
359
|
-
var
|
|
360
|
-
|
|
361
|
-
|
|
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;
|
|
362
249
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
var displayName = getDisplayName(name, idx, getCurrentViewerChannelSettings());
|
|
368
|
-
return displayName === ((_channelSettings$idx = channelSettings[idx]) === null || _channelSettings$idx === void 0 ? void 0 : _channelSettings$idx.displayName);
|
|
369
|
-
});
|
|
370
|
-
if (allNamesAreEqual) {
|
|
371
|
-
var _newChannelSettings = channelNames.map(function (channel, index) {
|
|
372
|
-
return _objectSpread(_objectSpread({}, channelSettings[index]), {}, {
|
|
373
|
-
name: channel
|
|
374
|
-
});
|
|
375
|
-
});
|
|
376
|
-
setChannelSettings(_newChannelSettings);
|
|
377
|
-
return _newChannelSettings;
|
|
250
|
+
// add the image to the viewer on load
|
|
251
|
+
useEffect(function () {
|
|
252
|
+
if (image === null) {
|
|
253
|
+
return;
|
|
378
254
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
});
|
|
383
|
-
setChannelSettings(newChannelSettings);
|
|
384
|
-
return newChannelSettings;
|
|
385
|
-
};
|
|
386
|
-
var placeImageInViewer = function placeImageInViewer(aimg, newChannelSettings) {
|
|
387
|
-
setImage(aimg);
|
|
388
|
-
var channelSetting = newChannelSettings || channelSettings;
|
|
389
|
-
view3d.removeAllVolumes();
|
|
390
|
-
view3d.addVolume(aimg, {
|
|
255
|
+
channelRangesRef.current = new Array(image.channelNames.length).fill(undefined);
|
|
256
|
+
var channelSettings = viewerState.current.channelSettings;
|
|
257
|
+
view3d.addVolume(image, {
|
|
391
258
|
// Immediately passing down channel parameters isn't strictly necessary, but keeps things looking consistent on load
|
|
392
|
-
channels:
|
|
393
|
-
|
|
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
|
+
});
|
|
394
264
|
if (!ch) {
|
|
395
265
|
return {};
|
|
396
266
|
}
|
|
@@ -403,151 +273,47 @@ var App = function App(props) {
|
|
|
403
273
|
};
|
|
404
274
|
})
|
|
405
275
|
});
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
var max = aimg.imageInfo.volumeSize[axis];
|
|
416
|
-
var current = viewerState.current.slice[axis] * max;
|
|
417
|
-
changeViewerSetting("slice", _objectSpread(_objectSpread({}, viewerState.current.slice), {}, _defineProperty({}, axis, (current + 1) % max / max)));
|
|
418
|
-
}
|
|
419
|
-
};
|
|
420
|
-
playControls.getVolumeIsLoaded = function () {
|
|
421
|
-
return aimg.isLoaded();
|
|
422
|
-
};
|
|
423
|
-
view3d.updateActiveChannels(aimg);
|
|
424
|
-
// make sure we pick up whether the image needs to be in single-slice mode
|
|
425
|
-
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
|
|
426
285
|
};
|
|
427
|
-
var
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
case 0:
|
|
434
|
-
_props = props, imageUrl = _props.imageUrl, parentImageUrl = _props.parentImageUrl, rawData = _props.rawData, rawDims = _props.rawDims;
|
|
435
|
-
scene = viewerState.current.scene;
|
|
436
|
-
if (!(rawData && rawDims)) {
|
|
437
|
-
_context.next = 6;
|
|
438
|
-
break;
|
|
439
|
-
}
|
|
440
|
-
scenePaths = [{
|
|
441
|
-
data: rawData,
|
|
442
|
-
metadata: rawDims
|
|
443
|
-
}];
|
|
444
|
-
_context.next = 12;
|
|
445
|
-
break;
|
|
446
|
-
case 6:
|
|
447
|
-
showParentImage = viewerState.current.imageType === ImageType.fullField && parentImageUrl !== undefined;
|
|
448
|
-
path = showParentImage ? parentImageUrl : imageUrl; // Don't reload if we're already looking at this image
|
|
449
|
-
if (!isEqual(path, imageUrlRef.current)) {
|
|
450
|
-
_context.next = 10;
|
|
451
|
-
break;
|
|
452
|
-
}
|
|
453
|
-
return _context.abrupt("return");
|
|
454
|
-
case 10:
|
|
455
|
-
imageUrlRef.current = path;
|
|
456
|
-
scenePaths = (_scenes2 = path.scenes) !== null && _scenes2 !== void 0 ? _scenes2 : [path];
|
|
457
|
-
case 12:
|
|
458
|
-
setSendingQueryRequest(true);
|
|
459
|
-
setImageLoaded(false);
|
|
460
|
-
hasChannelLoadedRef.current = [];
|
|
461
|
-
loadSpec = new LoadSpec();
|
|
462
|
-
loadSpec.time = viewerState.current.time;
|
|
463
|
-
options = {};
|
|
464
|
-
if (rawData && rawDims) {
|
|
465
|
-
options.fileType = VolumeFileFormat.DATA;
|
|
466
|
-
options.rawArrayOptions = {
|
|
467
|
-
data: rawData,
|
|
468
|
-
metadata: rawDims
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
_context.prev = 19;
|
|
472
|
-
loader.current = new SceneStore(loadContext, scenePaths);
|
|
473
|
-
_context.next = 23;
|
|
474
|
-
return loader.current.createVolume(scene, loadSpec, function (v, channelIndex) {
|
|
475
|
-
// NOTE: this callback runs *after* `onNewVolumeCreated` below, for every loaded channel
|
|
476
|
-
// TODO is this search by name necessary or will the `channelIndex` passed to the callback always match state?
|
|
477
|
-
var thisChannelSettings = viewerState.current.channelSettings[channelIndex];
|
|
478
|
-
onChannelDataLoaded(v, thisChannelSettings, channelIndex);
|
|
479
|
-
});
|
|
480
|
-
case 23:
|
|
481
|
-
aimg = _context.sent;
|
|
482
|
-
_context.next = 30;
|
|
483
|
-
break;
|
|
484
|
-
case 26:
|
|
485
|
-
_context.prev = 26;
|
|
486
|
-
_context.t0 = _context["catch"](19);
|
|
487
|
-
showError(_context.t0);
|
|
488
|
-
throw _context.t0;
|
|
489
|
-
case 30:
|
|
490
|
-
channelNames = aimg.imageInfo.channelNames;
|
|
491
|
-
newChannelSettings = setChannelStateForNewImage(channelNames); // order is important:
|
|
492
|
-
// we need to remove the old volume before triggering channels unloaded,
|
|
493
|
-
// which may cause calls on View3d to the old volume.
|
|
494
|
-
view3d.removeAllVolumes();
|
|
495
|
-
setAllChannelsUnloaded(channelNames.length);
|
|
496
|
-
placeImageInViewer(aimg, newChannelSettings);
|
|
497
|
-
channelRangesRef.current = new Array(channelNames.length).fill(undefined);
|
|
498
|
-
requiredLoadSpec = new LoadSpec();
|
|
499
|
-
requiredLoadSpec.time = viewerState.current.time;
|
|
500
|
-
|
|
501
|
-
// make the currently enabled channels "required":
|
|
502
|
-
// find all enabled indices in newChannelSettings:
|
|
503
|
-
requiredChannelsToLoad = newChannelSettings ? newChannelSettings.map(function (channel, index) {
|
|
504
|
-
return channel.volumeEnabled ? index : -1;
|
|
505
|
-
}).filter(function (index) {
|
|
506
|
-
return index >= 0;
|
|
507
|
-
}) : []; // add mask channel to required channels, if specified
|
|
508
|
-
maskChannelName = (_getCurrentViewerChan2 = getCurrentViewerChannelSettings()) === null || _getCurrentViewerChan2 === void 0 ? void 0 : _getCurrentViewerChan2.maskChannelName;
|
|
509
|
-
if (maskChannelName) {
|
|
510
|
-
maskChannelIndex = channelNames.indexOf(maskChannelName);
|
|
511
|
-
if (maskChannelIndex >= 0 && !requiredChannelsToLoad.includes(maskChannelIndex)) {
|
|
512
|
-
requiredChannelsToLoad.push(maskChannelIndex);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
requiredLoadSpec.channels = requiredChannelsToLoad;
|
|
516
|
-
|
|
517
|
-
// When in 2D Z-axis view mode, we restrict the subregion to only the current slice. This is
|
|
518
|
-
// to match an optimization that volume viewer does by loading Z-slices at a higher resolution,
|
|
519
|
-
// and ensures the very first volume that is loaded is the same as the one that
|
|
520
|
-
// will be shown whenever we switch back to the same viewer settings (2D Z-axis view mode).
|
|
521
|
-
// (We don't do this for ZX and YZ modes because we assume that the data won't be chunked along the
|
|
522
|
-
// X or Y axes in ways that would improve loading resolution, and we load the full 3D volume instead.)
|
|
523
|
-
if (viewerSettings.viewMode === ViewMode.xy) {
|
|
524
|
-
slice = viewerSettings.slice;
|
|
525
|
-
requiredLoadSpec.subregion = new Box3(new Vector3(0, 0, slice.z), new Vector3(1, 1, slice.z));
|
|
526
|
-
}
|
|
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;
|
|
527
292
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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);
|
|
544
310
|
|
|
545
311
|
// Imperative callbacks /////////////////////////////////////////////////////
|
|
546
312
|
|
|
547
313
|
var viewerSettings = viewerState.current;
|
|
548
314
|
var saveIsosurface = useCallback(function (channelIndex, type) {
|
|
549
315
|
if (image) view3d.saveChannelIsosurface(image, channelIndex, type);
|
|
550
|
-
}, [image]);
|
|
316
|
+
}, [image, view3d]);
|
|
551
317
|
var saveScreenshot = useCallback(function () {
|
|
552
318
|
view3d.capture(function (dataUrl) {
|
|
553
319
|
var anchor = document.createElement("a");
|
|
@@ -555,36 +321,8 @@ var App = function App(props) {
|
|
|
555
321
|
anchor.download = "screenshot.png";
|
|
556
322
|
anchor.click();
|
|
557
323
|
});
|
|
558
|
-
}, []);
|
|
559
|
-
var onClippingPanelOpenChange = useCallback(function (panelOpen) {
|
|
560
|
-
if (panelOpen === clippingPanelOpen) {
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
var hasTime = numTimesteps > 1;
|
|
564
|
-
var hasScenes = numScenes > 1;
|
|
565
|
-
var mode3d = viewerSettings.viewMode === ViewMode.threeD;
|
|
566
|
-
setClippingPanelOpen(panelOpen);
|
|
567
|
-
setIndicatorPositions(view3d, panelOpen, hasTime, hasScenes, mode3d);
|
|
568
|
-
|
|
569
|
-
// Hide indicators while clipping panel is in motion - otherwise they pop to the right place prematurely
|
|
570
|
-
if (panelOpen) {
|
|
571
|
-
view3d.setShowScaleBar(false);
|
|
572
|
-
view3d.setShowTimestepIndicator(false);
|
|
573
|
-
view3d.setShowAxis(false);
|
|
574
|
-
window.clearTimeout(clippingPanelOpenTimeout.current);
|
|
575
|
-
clippingPanelOpenTimeout.current = window.setTimeout(function () {
|
|
576
|
-
view3d.setShowScaleBar(true);
|
|
577
|
-
view3d.setShowTimestepIndicator(true);
|
|
578
|
-
if (viewerSettings.showAxes) {
|
|
579
|
-
view3d.setShowAxis(true);
|
|
580
|
-
}
|
|
581
|
-
}, CLIPPING_PANEL_ANIMATION_DURATION_MS);
|
|
582
|
-
}
|
|
583
|
-
}, [view3d, numTimesteps, numScenes, viewerSettings.viewMode, viewerSettings.showAxes, clippingPanelOpen]);
|
|
324
|
+
}, [view3d]);
|
|
584
325
|
var getMetadata = useCallback(function () {
|
|
585
|
-
var _props2 = props,
|
|
586
|
-
metadata = _props2.metadata,
|
|
587
|
-
metadataFormatter = _props2.metadataFormatter;
|
|
588
326
|
var imageMetadata = image === null || image === void 0 ? void 0 : image.imageMetadata;
|
|
589
327
|
if (imageMetadata && metadataFormatter) {
|
|
590
328
|
imageMetadata = metadataFormatter(imageMetadata);
|
|
@@ -608,7 +346,28 @@ var App = function App(props) {
|
|
|
608
346
|
var _sceneMeta;
|
|
609
347
|
return (_sceneMeta = sceneMeta) !== null && _sceneMeta !== void 0 ? _sceneMeta : {};
|
|
610
348
|
}
|
|
611
|
-
}, [
|
|
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]);
|
|
612
371
|
|
|
613
372
|
// Effects //////////////////////////////////////////////////////////////////
|
|
614
373
|
|
|
@@ -630,19 +389,9 @@ var App = function App(props) {
|
|
|
630
389
|
return window.removeEventListener("resize", onResizeDebounced);
|
|
631
390
|
};
|
|
632
391
|
}, [hasAutoClosedControlPanel]);
|
|
633
|
-
|
|
634
|
-
// one-time init after view3d exists and before we start loading images
|
|
635
|
-
useEffect(function () {
|
|
636
|
-
view3d.setCameraMode(viewerSettings.viewMode);
|
|
637
|
-
}, []);
|
|
638
|
-
|
|
639
|
-
// Hook to trigger image load: on mount, when image source props/state change (`cellId`, `imageType`, `rawData`, etc)
|
|
640
|
-
useEffect(function () {
|
|
641
|
-
openImage();
|
|
642
|
-
}, [props.imageUrl, props.cellId, viewerSettings.imageType, props.rawDims, props.rawData]);
|
|
643
392
|
useEffect(function () {
|
|
644
|
-
return
|
|
645
|
-
}, [controlPanelClosed,
|
|
393
|
+
return onControlPanelToggle && onControlPanelToggle(controlPanelClosed);
|
|
394
|
+
}, [controlPanelClosed, onControlPanelToggle]);
|
|
646
395
|
useEffect(function () {
|
|
647
396
|
// delayed for the animation to finish
|
|
648
397
|
window.setTimeout(function () {
|
|
@@ -653,44 +402,39 @@ var App = function App(props) {
|
|
|
653
402
|
/** Custom effect hook for viewer updates that depend on `image`, so we don't have to repeatedly null-check it */
|
|
654
403
|
var useImageEffect = function useImageEffect(effect, deps) {
|
|
655
404
|
useEffect(function () {
|
|
656
|
-
if (image &&
|
|
405
|
+
if (image && volume.imageLoadStatus === ImageLoadStatus.LOADED) {
|
|
657
406
|
return effect(image);
|
|
658
407
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
imageLoadHandlers.current = [];
|
|
663
|
-
/** `ImageEffect`s that also run right on image creation, so the image doesn't first render with default settings */
|
|
664
|
-
var useImageLoadEffect = function useImageLoadEffect(effect, deps) {
|
|
665
|
-
useImageEffect(effect, deps);
|
|
666
|
-
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]));
|
|
667
411
|
};
|
|
668
412
|
|
|
669
413
|
// Effects to imperatively sync `viewerSettings` to `view3d`
|
|
670
414
|
useImageEffect(function (_currentImage) {
|
|
671
415
|
view3d.setCameraMode(viewerSettings.viewMode);
|
|
672
416
|
view3d.resize(null);
|
|
673
|
-
}, [viewerSettings.viewMode]);
|
|
417
|
+
}, [viewerSettings.viewMode, view3d]);
|
|
674
418
|
useImageEffect(function (_currentImage) {
|
|
675
419
|
if (viewerSettings.cameraState) {
|
|
676
420
|
view3d.setCameraState(viewerSettings.cameraState);
|
|
677
421
|
}
|
|
678
|
-
}, [viewerSettings.cameraState]);
|
|
422
|
+
}, [viewerSettings.cameraState, view3d]);
|
|
679
423
|
useImageEffect(function (_currentImage) {
|
|
680
424
|
return view3d.setAutoRotate(viewerSettings.autorotate);
|
|
681
|
-
}, [viewerSettings.autorotate]);
|
|
425
|
+
}, [viewerSettings.autorotate, view3d]);
|
|
682
426
|
useImageEffect(function (_currentImage) {
|
|
683
427
|
return view3d.setShowAxis(viewerSettings.showAxes);
|
|
684
|
-
}, [viewerSettings.showAxes]);
|
|
428
|
+
}, [viewerSettings.showAxes, view3d]);
|
|
685
429
|
useImageEffect(function (_currentImage) {
|
|
686
430
|
return view3d.setBackgroundColor(colorArrayToFloats(viewerSettings.backgroundColor));
|
|
687
|
-
}, [viewerSettings.backgroundColor]);
|
|
431
|
+
}, [viewerSettings.backgroundColor, view3d]);
|
|
688
432
|
useImageEffect(function (currentImage) {
|
|
689
433
|
return view3d.setBoundingBoxColor(currentImage, colorArrayToFloats(viewerSettings.boundingBoxColor));
|
|
690
|
-
}, [viewerSettings.boundingBoxColor]);
|
|
434
|
+
}, [viewerSettings.boundingBoxColor, view3d]);
|
|
691
435
|
useImageEffect(function (currentImage) {
|
|
692
436
|
return view3d.setShowBoundingBox(currentImage, viewerSettings.showBoundingBox);
|
|
693
|
-
}, [viewerSettings.showBoundingBox]);
|
|
437
|
+
}, [viewerSettings.showBoundingBox, view3d]);
|
|
694
438
|
useImageEffect(function (image) {
|
|
695
439
|
// Check whether any channels are marked to be reset and apply it.
|
|
696
440
|
var channelsAwaitingReset = getChannelsAwaitingReset();
|
|
@@ -706,64 +450,52 @@ var App = function App(props) {
|
|
|
706
450
|
onResetChannel(i);
|
|
707
451
|
}
|
|
708
452
|
}
|
|
709
|
-
}, [channelSettings]);
|
|
710
|
-
|
|
453
|
+
}, [changeChannelSetting, channelSettings, getChannelsAwaitingReset, getCurrentViewerChannelSettings, onResetChannel]);
|
|
454
|
+
useImageEffect(function (currentImage) {
|
|
711
455
|
var renderMode = viewerSettings.renderMode;
|
|
712
456
|
view3d.setMaxProjectMode(currentImage, renderMode === RenderMode.maxProject);
|
|
713
457
|
view3d.setVolumeRenderMode(renderMode === RenderMode.pathTrace ? RENDERMODE_PATHTRACE : RENDERMODE_RAYMARCH);
|
|
714
458
|
view3d.updateActiveChannels(currentImage);
|
|
715
|
-
}, [viewerSettings.renderMode]);
|
|
459
|
+
}, [viewerSettings.renderMode, view3d]);
|
|
716
460
|
useImageEffect(function (currentImage) {
|
|
717
461
|
view3d.updateMaskAlpha(currentImage, alphaSliderToImageValue(viewerSettings.maskAlpha));
|
|
718
462
|
view3d.updateActiveChannels(currentImage);
|
|
719
|
-
}, [viewerSettings.maskAlpha]);
|
|
720
|
-
|
|
721
|
-
var
|
|
722
|
-
var brightness = brightnessSliderToImageValue(viewerSettings.brightness, isPathTracing);
|
|
463
|
+
}, [viewerSettings.maskAlpha, view3d]);
|
|
464
|
+
useImageEffect(function (_currentImage) {
|
|
465
|
+
var brightness = brightnessSliderToImageValue(viewerSettings.brightness);
|
|
723
466
|
view3d.updateExposure(brightness);
|
|
724
|
-
}, [viewerSettings.brightness]);
|
|
725
|
-
|
|
726
|
-
var
|
|
727
|
-
var density = densitySliderToImageValue(viewerSettings.density, isPathTracing);
|
|
467
|
+
}, [viewerSettings.brightness, view3d]);
|
|
468
|
+
useImageEffect(function (currentImage) {
|
|
469
|
+
var density = densitySliderToImageValue(viewerSettings.density);
|
|
728
470
|
view3d.updateDensity(currentImage, density);
|
|
729
|
-
}, [viewerSettings.density]);
|
|
730
|
-
|
|
471
|
+
}, [viewerSettings.density, view3d]);
|
|
472
|
+
useImageEffect(function (currentImage) {
|
|
731
473
|
var imageValues = gammaSliderToImageValues(viewerSettings.levels);
|
|
732
474
|
view3d.setGamma(currentImage, imageValues.min, imageValues.scale, imageValues.max);
|
|
733
|
-
}, [viewerSettings.levels]);
|
|
475
|
+
}, [viewerSettings.levels, view3d]);
|
|
734
476
|
|
|
735
|
-
// `time`
|
|
477
|
+
// `time` and `scene` have their own special handlers via `volume`, since they both trigger loads
|
|
736
478
|
useEffect(function () {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
setAllChannelsUnloaded(image.numChannels);
|
|
740
|
-
view3d.setTime(image, viewerSettings.time);
|
|
741
|
-
}
|
|
742
|
-
}, [viewerSettings.time]);
|
|
479
|
+
return setTime(view3d, viewerSettings.time);
|
|
480
|
+
}, [view3d, viewerSettings.time, setTime]);
|
|
743
481
|
useEffect(function () {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
(_loader$current3 = loader.current) === null || _loader$current3 === void 0 || _loader$current3.loadScene(viewerSettings.scene, image)["catch"](function (e) {
|
|
748
|
-
return showError(e);
|
|
749
|
-
});
|
|
750
|
-
}
|
|
751
|
-
}, [viewerSettings.scene]);
|
|
752
|
-
useImageLoadEffect(function (currentImage) {
|
|
482
|
+
return setScene(viewerSettings.scene);
|
|
483
|
+
}, [viewerSettings.scene, setScene]);
|
|
484
|
+
useImageEffect(function (currentImage) {
|
|
753
485
|
return view3d.setInterpolationEnabled(currentImage, viewerSettings.interpolationEnabled);
|
|
754
|
-
}, [viewerSettings.interpolationEnabled]);
|
|
755
|
-
|
|
486
|
+
}, [viewerSettings.interpolationEnabled, view3d]);
|
|
487
|
+
useImageEffect(function (currentImage) {
|
|
756
488
|
var _props$transform;
|
|
757
489
|
return view3d.setVolumeTranslation(currentImage, ((_props$transform = props.transform) === null || _props$transform === void 0 ? void 0 : _props$transform.translation) || [0, 0, 0]);
|
|
758
|
-
}, [(_props$transform2 = props.transform) === null || _props$transform2 === void 0 ? void 0 : _props$transform2.translation]);
|
|
759
|
-
|
|
490
|
+
}, [(_props$transform2 = props.transform) === null || _props$transform2 === void 0 ? void 0 : _props$transform2.translation, view3d]);
|
|
491
|
+
useImageEffect(function (currentImage) {
|
|
760
492
|
var _props$transform3;
|
|
761
493
|
return view3d.setVolumeRotation(currentImage, ((_props$transform3 = props.transform) === null || _props$transform3 === void 0 ? void 0 : _props$transform3.rotation) || [0, 0, 0]);
|
|
762
|
-
}, [(_props$transform4 = props.transform) === null || _props$transform4 === void 0 ? void 0 : _props$transform4.rotation]);
|
|
763
|
-
var usePerAxisClippingUpdater = function usePerAxisClippingUpdater(axis,
|
|
764
|
-
var
|
|
765
|
-
minval =
|
|
766
|
-
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];
|
|
767
499
|
useImageEffect(
|
|
768
500
|
// Logic to determine axis clipping range, for each of x,y,z,3d slider:
|
|
769
501
|
// if slider was same as active axis view mode: [viewerSettings.slice[axis], viewerSettings.slice[axis] + 1.0/volumeSize[axis]]
|
|
@@ -773,30 +505,30 @@ var App = function App(props) {
|
|
|
773
505
|
var isOrthoAxis = false;
|
|
774
506
|
var axismin = 0.0;
|
|
775
507
|
var axismax = 1.0;
|
|
776
|
-
if (
|
|
508
|
+
if (viewMode === ViewMode.threeD) {
|
|
777
509
|
axismin = minval;
|
|
778
510
|
axismax = maxval;
|
|
779
511
|
isOrthoAxis = false;
|
|
780
512
|
} else {
|
|
781
|
-
isOrthoAxis = activeAxisMap[
|
|
513
|
+
isOrthoAxis = activeAxisMap[viewMode] === axis;
|
|
782
514
|
var oneSlice = 1 / currentImage.imageInfo.volumeSize[axis];
|
|
783
515
|
axismin = isOrthoAxis ? slice : 0.0;
|
|
784
516
|
axismax = isOrthoAxis ? slice + oneSlice : 1.0;
|
|
785
|
-
if (axis === "z" &&
|
|
517
|
+
if (axis === "z" && viewMode === ViewMode.xy) {
|
|
786
518
|
view3d.setZSlice(currentImage, Math.floor(slice * currentImage.imageInfo.volumeSize.z));
|
|
787
|
-
if (!currentImage.isLoaded()) {
|
|
788
|
-
setImageLoaded(false);
|
|
789
|
-
}
|
|
790
519
|
}
|
|
791
520
|
}
|
|
792
521
|
// view3d wants the coordinates in the -0.5 to 0.5 range
|
|
793
522
|
view3d.setAxisClip(currentImage, axis, axismin - 0.5, axismax - 0.5, isOrthoAxis);
|
|
794
|
-
view3d.setCameraMode(
|
|
795
|
-
|
|
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]);
|
|
796
528
|
};
|
|
797
|
-
usePerAxisClippingUpdater("x", viewerSettings.region.x, viewerSettings.slice.x);
|
|
798
|
-
usePerAxisClippingUpdater("y", viewerSettings.region.y, viewerSettings.slice.y);
|
|
799
|
-
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);
|
|
800
532
|
|
|
801
533
|
// Rendering ////////////////////////////////////////////////////////////////
|
|
802
534
|
|
|
@@ -805,7 +537,7 @@ var App = function App(props) {
|
|
|
805
537
|
}, [props.visibleControls]);
|
|
806
538
|
var pixelSize = useMemo(function () {
|
|
807
539
|
return image ? image.imageInfo.physicalPixelSize.toArray() : [1, 1, 1];
|
|
808
|
-
}, [image
|
|
540
|
+
}, [image]);
|
|
809
541
|
var resetCamera = useMemo(function () {
|
|
810
542
|
return view3d.resetCamera.bind(view3d);
|
|
811
543
|
}, [view3d]);
|
|
@@ -821,7 +553,7 @@ var App = function App(props) {
|
|
|
821
553
|
index: index,
|
|
822
554
|
view3d: view3d,
|
|
823
555
|
image: image,
|
|
824
|
-
version: channelVersions[index]
|
|
556
|
+
version: volume.channelVersions[index]
|
|
825
557
|
});
|
|
826
558
|
}), /*#__PURE__*/React.createElement(Sider, {
|
|
827
559
|
className: "control-panel-holder",
|
|
@@ -840,7 +572,7 @@ var App = function App(props) {
|
|
|
840
572
|
hasImage: !!image,
|
|
841
573
|
pixelSize: pixelSize,
|
|
842
574
|
channelDataChannels: image === null || image === void 0 ? void 0 : image.channels,
|
|
843
|
-
channelGroupedByType: channelGroupedByType
|
|
575
|
+
channelGroupedByType: volume.channelGroupedByType
|
|
844
576
|
// functions
|
|
845
577
|
,
|
|
846
578
|
setCollapsed: setControlPanelClosed,
|
|
@@ -865,17 +597,17 @@ var App = function App(props) {
|
|
|
865
597
|
}), /*#__PURE__*/React.createElement(CellViewerCanvasWrapper, {
|
|
866
598
|
view3d: view3d,
|
|
867
599
|
image: image,
|
|
868
|
-
loadingImage:
|
|
600
|
+
loadingImage: volume.imageLoadStatus === ImageLoadStatus.REQUESTED,
|
|
869
601
|
numSlices: numSlices,
|
|
870
602
|
numSlicesLoaded: numSlicesLoaded,
|
|
871
603
|
numTimesteps: numTimesteps,
|
|
872
604
|
numScenes: numScenes,
|
|
873
|
-
playControls: playControls,
|
|
874
|
-
playingAxis: playingAxis,
|
|
605
|
+
playControls: volume.playControls,
|
|
606
|
+
playingAxis: volume.playingAxis,
|
|
875
607
|
appHeight: props.appHeight,
|
|
876
608
|
visibleControls: visibleControls,
|
|
877
609
|
clippingPanelOpen: clippingPanelOpen,
|
|
878
|
-
onClippingPanelOpenChange:
|
|
610
|
+
onClippingPanelOpenChange: setClippingPanelOpen
|
|
879
611
|
})))));
|
|
880
612
|
};
|
|
881
613
|
export default App;
|