@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.
Files changed (28) hide show
  1. package/es/aics-image-viewer/components/App/ChannelUpdater.js +11 -12
  2. package/es/aics-image-viewer/components/App/index.js +216 -466
  3. package/es/aics-image-viewer/components/AxisClipSliders/index.js +12 -12
  4. package/es/aics-image-viewer/components/BottomPanel/index.js +12 -28
  5. package/es/aics-image-viewer/components/CellViewerCanvasWrapper/index.js +3 -12
  6. package/es/aics-image-viewer/components/ChannelsWidgetRow/index.js +2 -2
  7. package/es/aics-image-viewer/components/ChannelsWidgetRow/styles.css +5 -1
  8. package/es/aics-image-viewer/components/ErrorAlert/index.js +1 -1
  9. package/es/aics-image-viewer/components/TfEditor/index.js +75 -35
  10. package/es/aics-image-viewer/components/TfEditor/styles.css +2 -3
  11. package/es/aics-image-viewer/components/Toolbar/ViewModeRadioButtons.js +1 -1
  12. package/es/aics-image-viewer/components/Toolbar/index.js +34 -30
  13. package/es/aics-image-viewer/components/ViewerStateProvider/index.js +11 -13
  14. package/es/aics-image-viewer/components/useVolume.js +327 -0
  15. package/es/aics-image-viewer/shared/constants.js +10 -1
  16. package/es/aics-image-viewer/shared/utils/hooks.js +3 -26
  17. package/es/aics-image-viewer/shared/utils/sliderValuesToImageValues.js +15 -9
  18. package/es/aics-image-viewer/shared/utils/viewerState.js +1 -1
  19. package/package.json +3 -2
  20. package/type-declarations/aics-image-viewer/components/BottomPanel/index.d.ts +2 -2
  21. package/type-declarations/aics-image-viewer/components/CellViewerCanvasWrapper/index.d.ts +2 -2
  22. package/type-declarations/aics-image-viewer/components/TfEditor/index.d.ts +1 -1
  23. package/type-declarations/aics-image-viewer/components/Toolbar/index.d.ts +1 -1
  24. package/type-declarations/aics-image-viewer/components/ViewerStateProvider/types.d.ts +1 -1
  25. package/type-declarations/aics-image-viewer/components/useVolume.d.ts +49 -0
  26. package/type-declarations/aics-image-viewer/shared/constants.d.ts +4 -1
  27. package/type-declarations/aics-image-viewer/shared/utils/hooks.d.ts +0 -2
  28. 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 { LoadSpec, PrefetchDirection, RENDERMODE_PATHTRACE, RENDERMODE_RAYMARCH, View3d, VolumeFileFormat, VolumeLoaderContext } from "@aics/vole-core";
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 { Box3, Vector3 } from "three";
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, useStateWithGetter } from "../../shared/utils/hooks";
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 { getDisplayName, makeChannelIndexGrouping } from "../../shared/utils/viewerChannelSettings";
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 axisToLoaderPriority = {
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, _scenes, _image$imageInfo$volu, _image$imageInfo$subr, _image$imageInfo$time, _props$transform2, _props$transform4;
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
- _showError = _useErrorAlert2[1];
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 hasRawImage = !!(props.rawData && props.rawDims);
176
- var numScenes = hasRawImage ? 1 : (_scenes$length = (_scenes = props.imageUrl.scenes) === null || _scenes === void 0 ? void 0 : _scenes.length) !== null && _scenes$length !== void 0 ? _scenes$length : 1;
177
- var numSlices = (_image$imageInfo$volu = image === null || image === void 0 ? void 0 : image.imageInfo.volumeSize) !== null && _image$imageInfo$volu !== void 0 ? _image$imageInfo$volu : {
178
- x: 0,
179
- y: 0,
180
- z: 0
181
- };
182
- var numSlicesLoaded = (_image$imageInfo$subr = image === null || image === void 0 ? void 0 : image.imageInfo.subregionSize) !== null && _image$imageInfo$subr !== void 0 ? _image$imageInfo$subr : {
183
- x: 0,
184
- y: 0,
185
- z: 0
186
- };
187
- 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;
188
-
189
- // State for image loading/reloading
190
-
191
- /** `true` when a channel's data has been loaded for the current image. */
192
- var hasChannelLoadedRef = useRef([]);
193
- // `true` when image data has been requested, but no data has been received yet
194
- var _useState3 = useState(false),
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
- setPlayingAxis(axis);
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
- * Updates a channel's ramp and control points after new data has been loaded.
276
- *
277
- * Also handles initializing the ramp/control points on initial load and resetting
278
- * them when the channel is reset.
279
- */
280
- var updateChannelTransferFunction = function updateChannelTransferFunction(aimg, thisChannelsSettings, channelIndex) {
281
- var thisChannel = aimg.getChannel(channelIndex);
282
-
283
- // If this is the first load of this image, auto-generate initial LUTs
284
- if (!hasChannelLoadedRef.current[channelIndex] || !thisChannelsSettings.controlPoints || !thisChannelsSettings.ramp || getChannelsAwaitingResetOnLoad().has(channelIndex)) {
285
- var viewerChannelSettings = getCurrentViewerChannelSettings();
286
- var _initializeLut = initializeLut(aimg, channelIndex, viewerChannelSettings),
287
- ramp = _initializeLut.ramp,
288
- 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;
289
197
  changeChannelSetting(channelIndex, {
290
- controlPoints: controlPoints,
291
- ramp: controlPointsToRamp(ramp),
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
- plotMax: DTYPE_RANGE[thisChannel.dtype].max
201
+ plotMin: DTYPE_RANGE[dtype].min,
202
+ plotMax: DTYPE_RANGE[dtype].max
294
203
  });
295
- onResetChannel(channelIndex);
296
204
  } else {
297
- // try not to update lut from here if we are in play mode
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 (thisChannelsSettings.useControlPoints) {
207
+ if (thisChannelSettings.useControlPoints) {
305
208
  // control points were just automatically remapped - update in state
306
- var rampControlPoints = rampToControlPoints(thisChannelsSettings.ramp);
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 _ramp = controlPointsToRamp(thisChannel.lut.controlPoints);
218
+ var _ramp2 = controlPointsToRamp(thisChannel.lut.controlPoints);
316
219
  // now manually remap control points using the channel's old range
317
- var _controlPoints = thisChannelsSettings.controlPoints;
318
- var remappedControlPoints = remapControlPointsForChannel(_controlPoints, oldRange, thisChannel);
220
+ var _controlPoints2 = thisChannelSettings.controlPoints;
221
+ var remappedControlPoints = remapControlPointsForChannel(_controlPoints2, oldRange, thisChannel);
319
222
  changeChannelSetting(channelIndex, {
320
223
  controlPoints: remappedControlPoints,
321
- ramp: _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(aimg);
334
- view3d.onVolumeData(aimg, [channelIndex]);
335
- view3d.setVolumeChannelEnabled(aimg, channelIndex, thisChannelsSettings.volumeEnabled);
336
- if (aimg.channelNames[channelIndex] === ((_getCurrentViewerChan = getCurrentViewerChannelSettings()) === null || _getCurrentViewerChan === void 0 ? void 0 : _getCurrentViewerChan.maskChannelName)) {
337
- view3d.setVolumeChannelAsMask(aimg, channelIndex);
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
- hasChannelLoadedRef.current[channelIndex] = true;
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 setChannelStateForNewImage = function setChannelStateForNewImage(channelNames) {
351
- var grouping = makeChannelIndexGrouping(channelNames, getCurrentViewerChannelSettings());
352
- setChannelGroupedByType(grouping);
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
- // compare each channel's new displayName to the old displayNames currently in state:
355
- // same number of channels, and each channel has same displayName
356
- var allNamesAreEqual = channelNames.every(function (name, idx) {
357
- var _channelSettings$idx;
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
- var newChannelSettings = channelNames.map(function (channel, index) {
371
- var color = getDefaultChannelColor(index);
372
- return initializeOneChannelSetting(channel, index, color, getCurrentViewerChannelSettings());
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: aimg.channelNames.map(function (name) {
384
- var ch = getOneChannelSetting(name, channelSetting);
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
- var mode3d = viewerSettings.viewMode === ViewMode.threeD;
398
- setIndicatorPositions(view3d, clippingPanelOpenRef.current, aimg.imageInfo.times > 1, numScenes > 1, mode3d);
399
- imageLoadHandlers.current.forEach(function (effect) {
400
- return effect(aimg);
401
- });
402
- playControls.stepAxis = function (axis) {
403
- if (axis === "t") {
404
- changeViewerSetting("time", (viewerState.current.time + 1) % aimg.imageInfo.times);
405
- } else {
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 openImage = /*#__PURE__*/function () {
419
- var _ref = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
420
- var _getCurrentViewerChan2;
421
- var _props, imageUrl, parentImageUrl, rawData, rawDims, scene, scenePaths, _scenes2, showParentImage, path, loadSpec, options, aimg, channelNames, newChannelSettings, requiredLoadSpec, requiredChannelsToLoad, maskChannelName, maskChannelIndex, slice;
422
- return _regeneratorRuntime().wrap(function _callee$(_context) {
423
- while (1) switch (_context.prev = _context.next) {
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
- // initiate loading only after setting up new channel settings,
520
- // in case the loader callback fires before the state is set
521
- loader.current.loadScene(scene, aimg, requiredLoadSpec)["catch"](function (e) {
522
- showError(e);
523
- throw e;
524
- });
525
- case 44:
526
- case "end":
527
- return _context.stop();
528
- }
529
- }, _callee, null, [[19, 26]]);
530
- }));
531
- return function openImage() {
532
- return _ref.apply(this, arguments);
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
- }, [props.metadata, props.metadataFormatter, image]);
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 props.onControlPanelToggle && props.onControlPanelToggle(controlPanelClosed);
630
- }, [controlPanelClosed, props.onControlPanelToggle]);
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 && imageLoaded) {
405
+ if (image && volume.imageLoadStatus === ImageLoadStatus.LOADED) {
642
406
  return effect(image);
643
407
  }
644
- }, [].concat(_toConsumableArray(deps), [image, imageLoaded]));
645
- };
646
- var imageLoadHandlers = useRef([]);
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
- useImageLoadEffect(function (currentImage) {
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
- useImageLoadEffect(function (_currentImage) {
706
- var isPathTracing = viewerSettings.renderMode === RenderMode.pathTrace;
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
- useImageLoadEffect(function (currentImage) {
711
- var isPathTracing = viewerSettings.renderMode === RenderMode.pathTrace;
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
- useImageLoadEffect(function (currentImage) {
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` is special: because syncing it requires a load, it cannot be dependent on `image`
477
+ // `time` and `scene` have their own special handlers via `volume`, since they both trigger loads
721
478
  useEffect(function () {
722
- if (image) {
723
- setSendingQueryRequest(true);
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
- if (image) {
730
- var _loader$current3;
731
- setSendingQueryRequest(true);
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
- useImageLoadEffect(function (currentImage) {
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
- useImageLoadEffect(function (currentImage) {
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, _ref2, slice) {
749
- var _ref3 = _slicedToArray(_ref2, 2),
750
- minval = _ref3[0],
751
- maxval = _ref3[1];
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 (viewerSettings.viewMode === ViewMode.threeD) {
508
+ if (viewMode === ViewMode.threeD) {
762
509
  axismin = minval;
763
510
  axismax = maxval;
764
511
  isOrthoAxis = false;
765
512
  } else {
766
- isOrthoAxis = activeAxisMap[viewerSettings.viewMode] === axis;
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" && viewerSettings.viewMode === ViewMode.xy) {
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(viewerSettings.viewMode);
780
- }, [minval, maxval, slice, viewerSettings.viewMode]);
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 === null || image === void 0 ? void 0 : image.imageInfo.physicalPixelSize]);
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: sendingQueryRequest,
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
- onClippingPanelVisibleChange: onClippingPanelVisibleChange,
860
- onClippingPanelVisibleChangeEnd: onClippingPanelVisibleChangeEnd
609
+ clippingPanelOpen: clippingPanelOpen,
610
+ onClippingPanelOpenChange: setClippingPanelOpen
861
611
  })))));
862
612
  };
863
613
  export default App;