@aics/vole-app 2.14.0 → 3.1.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 (81) hide show
  1. package/es/Globals.d.js +0 -0
  2. package/es/aics-image-viewer/components/App/ChannelUpdater.js +21 -3
  3. package/es/aics-image-viewer/components/App/index.js +184 -198
  4. package/es/aics-image-viewer/components/CellViewerCanvasWrapper/index.js +10 -9
  5. package/es/aics-image-viewer/components/ChannelsWidget.js +35 -22
  6. package/es/aics-image-viewer/components/ChannelsWidgetRow/index.js +26 -52
  7. package/es/aics-image-viewer/components/ChannelsWidgetRow/styles.css +0 -6
  8. package/es/aics-image-viewer/components/ColorPicker.js +1 -1
  9. package/es/aics-image-viewer/components/ControlPanel/index.js +5 -4
  10. package/es/aics-image-viewer/components/CustomizeWidget.js +12 -8
  11. package/es/aics-image-viewer/components/ErrorAlert/index.js +4 -4
  12. package/es/aics-image-viewer/components/GlobalVolumeControls.js +15 -12
  13. package/es/aics-image-viewer/components/StyleProvider/index.js +4 -2
  14. package/es/aics-image-viewer/components/TfEditor/index.js +304 -154
  15. package/es/aics-image-viewer/components/TfEditor/styles.css +53 -31
  16. package/es/aics-image-viewer/components/Toolbar/DownloadButton.js +1 -1
  17. package/es/aics-image-viewer/components/Toolbar/index.js +18 -16
  18. package/es/aics-image-viewer/components/shared/SharedCheckBox.js +1 -1
  19. package/es/aics-image-viewer/components/shared/SliderRow/index.js +8 -3
  20. package/es/aics-image-viewer/components/shared/SliderRow/styles.css +7 -0
  21. package/es/aics-image-viewer/components/shared/SmarterSlider.js +1 -1
  22. package/es/aics-image-viewer/components/shared/ViewerIcon.js +1 -1
  23. package/es/aics-image-viewer/components/useVolume.js +59 -58
  24. package/es/aics-image-viewer/shared/constants.js +2 -2
  25. package/es/aics-image-viewer/shared/utils/controlPointsToLut.js +57 -24
  26. package/es/aics-image-viewer/shared/utils/sceneStore.js +5 -3
  27. package/es/aics-image-viewer/shared/utils/storage.js +238 -0
  28. package/es/aics-image-viewer/shared/utils/test/storage.test.js +264 -0
  29. package/es/aics-image-viewer/shared/utils/test/urlParsing.test.js +240 -108
  30. package/es/aics-image-viewer/shared/utils/test/viewerChannelSettings.test.js +1 -1
  31. package/es/aics-image-viewer/shared/utils/urlParsing.js +278 -153
  32. package/es/aics-image-viewer/shared/utils/viewerState.js +4 -24
  33. package/es/aics-image-viewer/state/reset.js +111 -0
  34. package/es/aics-image-viewer/state/store.js +90 -0
  35. package/es/aics-image-viewer/state/subscribers.js +153 -0
  36. package/es/aics-image-viewer/state/test/reset.test.js +247 -0
  37. package/es/aics-image-viewer/state/util.js +71 -0
  38. package/es/index.js +3 -3
  39. package/package.json +18 -11
  40. package/type-declarations/aics-image-viewer/components/App/ChannelUpdater.d.ts +2 -4
  41. package/type-declarations/aics-image-viewer/components/App/types.d.ts +3 -3
  42. package/type-declarations/aics-image-viewer/components/AxisClipSliders/index.d.ts +6 -6
  43. package/type-declarations/aics-image-viewer/components/CellViewerCanvasWrapper/index.d.ts +5 -14
  44. package/type-declarations/aics-image-viewer/components/ChannelsWidget.d.ts +3 -6
  45. package/type-declarations/aics-image-viewer/components/ChannelsWidgetRow/index.d.ts +3 -6
  46. package/type-declarations/aics-image-viewer/components/ColorPicker.d.ts +1 -1
  47. package/type-declarations/aics-image-viewer/components/ControlPanel/index.d.ts +6 -7
  48. package/type-declarations/aics-image-viewer/components/CustomizeWidget.d.ts +2 -8
  49. package/type-declarations/aics-image-viewer/components/ErrorAlert/index.d.ts +4 -0
  50. package/type-declarations/aics-image-viewer/components/GlobalVolumeControls.d.ts +2 -9
  51. package/type-declarations/aics-image-viewer/components/MetadataViewer/index.d.ts +1 -1
  52. package/type-declarations/aics-image-viewer/components/StyleProvider/index.d.ts +1 -1
  53. package/type-declarations/aics-image-viewer/components/TfEditor/index.d.ts +10 -3
  54. package/type-declarations/aics-image-viewer/components/Toolbar/index.d.ts +3 -12
  55. package/type-declarations/aics-image-viewer/components/shared/SliderRow/index.d.ts +2 -1
  56. package/type-declarations/aics-image-viewer/components/shared/SmarterSlider.d.ts +1 -1
  57. package/type-declarations/aics-image-viewer/components/shared/ViewerIcon.d.ts +1 -1
  58. package/type-declarations/aics-image-viewer/components/useVolume.d.ts +7 -4
  59. package/type-declarations/aics-image-viewer/shared/constants.d.ts +5 -5
  60. package/type-declarations/aics-image-viewer/shared/types.d.ts +1 -1
  61. package/type-declarations/aics-image-viewer/shared/utils/controlPointsToLut.d.ts +9 -5
  62. package/type-declarations/aics-image-viewer/shared/utils/firebase/index.d.ts +1 -1
  63. package/type-declarations/aics-image-viewer/shared/utils/hooks.d.ts +1 -1
  64. package/type-declarations/aics-image-viewer/shared/utils/playControls.d.ts +1 -1
  65. package/type-declarations/aics-image-viewer/shared/utils/sceneStore.d.ts +8 -4
  66. package/type-declarations/aics-image-viewer/shared/utils/storage.d.ts +15 -0
  67. package/type-declarations/aics-image-viewer/shared/utils/urlParsing.d.ts +94 -31
  68. package/type-declarations/aics-image-viewer/shared/utils/viewerChannelSettings.d.ts +39 -1
  69. package/type-declarations/aics-image-viewer/shared/utils/viewerState.d.ts +3 -6
  70. package/type-declarations/aics-image-viewer/state/reset.d.ts +29 -0
  71. package/type-declarations/aics-image-viewer/state/store.d.ts +30 -0
  72. package/type-declarations/aics-image-viewer/state/subscribers.d.ts +4 -0
  73. package/type-declarations/aics-image-viewer/state/types.d.ts +46 -0
  74. package/type-declarations/aics-image-viewer/state/util.d.ts +8 -0
  75. package/type-declarations/index.d.ts +4 -4
  76. package/es/aics-image-viewer/components/ViewerStateProvider/ResetStateProvider.js +0 -163
  77. package/es/aics-image-viewer/components/ViewerStateProvider/index.js +0 -259
  78. package/type-declarations/aics-image-viewer/components/ViewerStateProvider/ResetStateProvider.d.ts +0 -34
  79. package/type-declarations/aics-image-viewer/components/ViewerStateProvider/index.d.ts +0 -33
  80. package/type-declarations/aics-image-viewer/components/ViewerStateProvider/types.d.ts +0 -107
  81. /package/es/aics-image-viewer/{components/ViewerStateProvider → state}/types.js +0 -0
File without changes
@@ -1,3 +1,9 @@
1
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ 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; }
3
+ 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; }
4
+ 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; }
5
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
6
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
1
7
  function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
2
8
  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."); }
3
9
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
@@ -6,17 +12,22 @@ function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(
6
12
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
7
13
  import { Lut } from "@aics/vole-core";
8
14
  import { useEffect } from "react";
9
- import { controlPointsToLut, rampToControlPoints } from "../../shared/utils/controlPointsToLut";
15
+ import { useShallow } from "zustand/shallow";
16
+ import { binIndexedControlPointsToLut, rampToControlPoints } from "../../shared/utils/controlPointsToLut";
17
+ import { useViewerState } from "../../state/store";
10
18
  /**
11
19
  * A component that doesn't render anything, but reacts to the provided `ChannelState`
12
20
  * and keeps it in sync with the viewer.
13
21
  */
14
22
  var ChannelUpdater = function ChannelUpdater(_ref) {
15
23
  var index = _ref.index,
16
- channelState = _ref.channelState,
17
24
  view3d = _ref.view3d,
18
25
  image = _ref.image,
19
26
  version = _ref.version;
27
+ var channelStateSelector = useShallow(function (state) {
28
+ return state.channelSettings[index];
29
+ });
30
+ var channelState = useViewerState(channelStateSelector);
20
31
  var volumeEnabled = channelState.volumeEnabled,
21
32
  isosurfaceEnabled = channelState.isosurfaceEnabled,
22
33
  isovalue = channelState.isovalue,
@@ -73,8 +84,15 @@ var ChannelUpdater = function ChannelUpdater(_ref) {
73
84
  if (useControlPoints && controlPoints.length < 2) {
74
85
  return;
75
86
  }
87
+ var histogram = currentImage.getHistogram(index);
76
88
  var controlPointsToUse = useControlPoints ? controlPoints : rampToControlPoints(ramp);
77
- var gradient = controlPointsToLut(controlPointsToUse);
89
+ // Convert control points from raw intensity values to histogram bin indices
90
+ var binIndexedControlPoints = controlPointsToUse.map(function (cp) {
91
+ return _objectSpread(_objectSpread({}, cp), {}, {
92
+ x: histogram.findFractionalBinOfValue(cp.x)
93
+ });
94
+ });
95
+ var gradient = binIndexedControlPointsToLut(binIndexedControlPoints);
78
96
  currentImage.setLut(index, gradient);
79
97
  view3d.updateLuts(currentImage);
80
98
  }, [controlPoints, ramp, useControlPoints, index, view3d]);
@@ -15,24 +15,23 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
15
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; } }
16
16
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
17
17
  // 3rd Party Imports
18
- import { RENDERMODE_PATHTRACE, RENDERMODE_RAYMARCH, View3d } from "@aics/vole-core";
18
+ import { View3d } from "@aics/vole-core";
19
19
  import { Layout } from "antd";
20
20
  import { debounce, isEqual } from "lodash";
21
- import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
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";
23
- import { ImageType, RenderMode, ViewMode } from "../../shared/enums";
24
- import { activeAxisMap } from "../../shared/types";
25
- import { colorArrayToFloats } from "../../shared/utils/colorRepresentations";
26
- import { controlPointsToRamp, initializeLut, rampToControlPoints, remapControlPointsForChannel } from "../../shared/utils/controlPointsToLut";
21
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
22
+ import { AXIS_MARGIN_DEFAULT, CLIPPING_PANEL_HEIGHT_DEFAULT, CLIPPING_PANEL_HEIGHT_TALL, CONTROL_PANEL_CLOSE_WIDTH, getDefaultViewerChannelSettings, getDefaultViewerState, SCALE_BAR_MARGIN_DEFAULT } from "../../shared/constants";
23
+ import { ImageType, ViewMode } from "../../shared/enums";
24
+ import { controlPointsToRamp, initializeLut } from "../../shared/utils/controlPointsToLut";
27
25
  import { useConstructor } from "../../shared/utils/hooks";
28
- import { alphaSliderToImageValue, brightnessSliderToImageValue, densitySliderToImageValue, gammaSliderToImageValues } from "../../shared/utils/sliderValuesToImageValues";
26
+ import { findFirstChannelMatch } from "../../shared/utils/viewerChannelSettings";
27
+ import { select, useViewerState } from "../../state/store";
28
+ import { subscribeImageToState, subscribeViewToState } from "../../state/subscribers";
29
29
  import useVolume, { ImageLoadStatus } from "../useVolume";
30
30
  import CellViewerCanvasWrapper from "../CellViewerCanvasWrapper";
31
31
  import ControlPanel from "../ControlPanel";
32
32
  import { useErrorAlert } from "../ErrorAlert";
33
33
  import StyleProvider from "../StyleProvider";
34
34
  import Toolbar from "../Toolbar";
35
- import { ViewerStateContext } from "../ViewerStateProvider";
36
35
  import ChannelUpdater from "./ChannelUpdater";
37
36
  import "../../assets/styles/globals.css";
38
37
  import "./styles.css";
@@ -105,31 +104,37 @@ var setIndicatorPositions = function setIndicatorPositions(view3d, panelOpen, ha
105
104
  view3d.setScaleBarPosition(scaleBarX, scaleBarY);
106
105
  };
107
106
  var App = function App(props) {
108
- var _props$showError, _getCurrentViewerChan, _scenes$length, _scenes2, _image$imageInfo$volu, _image$imageInfo$subr, _image$imageInfo$time, _props$transform2, _props$transform4;
107
+ var _props$showError, _props$viewerChannelS, _scenes$length, _scenes2, _image$imageInfo$volu, _image$imageInfo$subr, _image$imageInfo$time, _props$transform2, _props$transform4;
109
108
  props = _objectSpread(_objectSpread({}, defaultProps), props);
110
109
 
111
110
  // State management /////////////////////////////////////////////////////////
112
- var viewerState = useContext(ViewerStateContext).ref;
113
- var _viewerState$current = viewerState.current,
114
- imageType = _viewerState$current.imageType,
115
- viewMode = _viewerState$current.viewMode,
116
- channelSettings = _viewerState$current.channelSettings,
117
- changeChannelSetting = _viewerState$current.changeChannelSetting,
118
- applyColorPresets = _viewerState$current.applyColorPresets,
119
- setSavedViewerChannelSettings = _viewerState$current.setSavedViewerChannelSettings,
120
- getCurrentViewerChannelSettings = _viewerState$current.getCurrentViewerChannelSettings,
121
- getChannelsAwaitingReset = _viewerState$current.getChannelsAwaitingReset,
122
- onResetChannel = _viewerState$current.onResetChannel;
123
- var _props = props,
124
- onControlPanelToggle = _props.onControlPanelToggle,
125
- onImageTitleChange = _props.onImageTitleChange,
126
- metadata = _props.metadata,
127
- metadataFormatter = _props.metadataFormatter;
128
- useMemo(function () {
129
- if (props.viewerChannelSettings) {
130
- setSavedViewerChannelSettings(props.viewerChannelSettings);
111
+ var imageType = useViewerState(select("imageType"));
112
+ var viewMode = useViewerState(select("viewMode"));
113
+ var scene = useViewerState(select("scene"));
114
+ var time = useViewerState(select("time"));
115
+ var showAxes = useViewerState(select("showAxes"));
116
+ var channelSettings = useViewerState(select("channelSettings"));
117
+ var changeChannelSetting = useViewerState(select("changeChannelSetting"));
118
+ var applyColorPresets = useViewerState(select("applyColorPresets"));
119
+ var resetToSavedState = useViewerState(select("resetToSavedViewerState"));
120
+ var resetToSavedViewerState = useCallback(function () {
121
+ return resetToSavedState(props.viewerSettings, props.viewerChannelSettings);
122
+ }, [resetToSavedState, props.viewerSettings, props.viewerChannelSettings]);
123
+
124
+ // Apply viewer settings to state that have changed since the last prop
125
+ var prevViewerSettingsPropsRef = useRef();
126
+ if (props.viewerSettings && !isEqual(props.viewerSettings, prevViewerSettingsPropsRef.current)) {
127
+ for (var _i = 0, _arr = Object.keys(props.viewerSettings); _i < _arr.length; _i++) {
128
+ var _prevViewerSettingsPr, _prevViewerSettingsPr2;
129
+ var key = _arr[_i];
130
+ var value = props.viewerSettings[key];
131
+ var lastValue = (_prevViewerSettingsPr = (_prevViewerSettingsPr2 = prevViewerSettingsPropsRef.current) === null || _prevViewerSettingsPr2 === void 0 ? void 0 : _prevViewerSettingsPr2[key]) !== null && _prevViewerSettingsPr !== void 0 ? _prevViewerSettingsPr : undefined;
132
+ if (value !== undefined && !isEqual(value, lastValue)) {
133
+ useViewerState.getState().changeViewerSetting(key, value);
134
+ }
131
135
  }
132
- }, [props.viewerChannelSettings, setSavedViewerChannelSettings]);
136
+ prevViewerSettingsPropsRef.current = props.viewerSettings;
137
+ }
133
138
  var view3d = useConstructor(function () {
134
139
  return new View3d();
135
140
  });
@@ -155,11 +160,11 @@ var App = function App(props) {
155
160
  }, [view3d, showError]);
156
161
  var imageUrlRef = useRef("");
157
162
  var scenesRef = useRef([]);
158
- var _props2 = props,
159
- imageUrl = _props2.imageUrl,
160
- parentImageUrl = _props2.parentImageUrl,
161
- rawData = _props2.rawData,
162
- rawDims = _props2.rawDims;
163
+ var _props = props,
164
+ imageUrl = _props.imageUrl,
165
+ parentImageUrl = _props.parentImageUrl,
166
+ rawData = _props.rawData,
167
+ rawDims = _props.rawDims;
163
168
  var scenes = useMemo(function () {
164
169
  if (rawData && rawDims) {
165
170
  return [{
@@ -180,21 +185,58 @@ var App = function App(props) {
180
185
  return result;
181
186
  }
182
187
  }, [imageUrl, parentImageUrl, rawData, rawDims, imageType]);
183
- var maskChannelName = (_getCurrentViewerChan = getCurrentViewerChannelSettings()) === null || _getCurrentViewerChan === void 0 ? void 0 : _getCurrentViewerChan.maskChannelName;
188
+ var maskChannelName = (_props$viewerChannelS = props.viewerChannelSettings) === null || _props$viewerChannelS === void 0 ? void 0 : _props$viewerChannelS.maskChannelName;
184
189
 
185
190
  // we need to keep track of channel ranges for remapping control points
186
191
  var channelRangesRef = useRef([]);
192
+ var removePreviousImage = useRef(undefined);
193
+ var _props2 = props,
194
+ onImageTitleChange = _props2.onImageTitleChange;
187
195
  var onCreateImage = useCallback(function (newImage) {
196
+ var _removePreviousImage$, _newImage$imageInfo$c;
197
+ (_removePreviousImage$ = removePreviousImage.current) === null || _removePreviousImage$ === void 0 || _removePreviousImage$.call(removePreviousImage);
188
198
  if (newImage === null) {
189
199
  return;
190
200
  }
191
- channelRangesRef.current = new Array(newImage.channelNames.length).fill(undefined);
192
- var channelSettings = viewerState.current.channelSettings;
201
+ var _useViewerState$getSt = useViewerState.getState(),
202
+ channelState = _useViewerState$getSt.channelSettings;
203
+ var channelNames = newImage.channelNames;
204
+ channelRangesRef.current = channelNames.map(function (_, i) {
205
+ var _channelState$i;
206
+ return (_channelState$i = channelState[i]) !== null && _channelState$i !== void 0 && _channelState$i.keepIntensityRange ? channelRangesRef.current[i] : undefined;
207
+ });
208
+ var _useViewerState$getSt2 = useViewerState.getState(),
209
+ useDefaultViewerChannelSettings = _useViewerState$getSt2.useDefaultViewerChannelSettings;
210
+ var viewerChannelSettings = useDefaultViewerChannelSettings ? getDefaultViewerChannelSettings() : props.viewerChannelSettings;
211
+
212
+ // If the image has channel color metadata, apply those colors now
213
+ var channelColorMeta = (_newImage$imageInfo$c = newImage.imageInfo.channelColors) === null || _newImage$imageInfo$c === void 0 ? void 0 : _newImage$imageInfo$c.map(function (color, index) {
214
+ // Filter out channels that have colors in `viewerChannelSettings`
215
+ if (viewerChannelSettings === undefined) {
216
+ return color;
217
+ }
218
+ var settings = findFirstChannelMatch(channelNames[index], index, viewerChannelSettings);
219
+ if ((settings === null || settings === void 0 ? void 0 : settings.color) !== undefined) {
220
+ return undefined;
221
+ } else {
222
+ return color;
223
+ }
224
+ });
225
+ if (Array.isArray(channelColorMeta)) {
226
+ channelColorMeta.forEach(function (color, index) {
227
+ if (Array.isArray(color) && index < channelNames.length) {
228
+ changeChannelSetting(index, {
229
+ color: color
230
+ });
231
+ }
232
+ });
233
+ }
193
234
  view3d.addVolume(newImage, {
194
235
  // Immediately passing down channel parameters isn't strictly necessary, but keeps things looking consistent on load
195
- channels: newImage.channelNames.map(function (name) {
236
+ channels: newImage.channelNames.map(function (name, index) {
237
+ var _channelColorMeta$ind;
196
238
  // TODO do we really need to be searching by name here?
197
- var ch = channelSettings.find(function (channel) {
239
+ var ch = channelState.find(function (channel) {
198
240
  return channel.name === name;
199
241
  });
200
242
  if (!ch) {
@@ -205,58 +247,76 @@ var App = function App(props) {
205
247
  isosurfaceEnabled: ch.isosurfaceEnabled,
206
248
  isovalue: ch.isovalue,
207
249
  isosurfaceOpacity: ch.opacity,
208
- color: ch.color
250
+ color: (_channelColorMeta$ind = channelColorMeta === null || channelColorMeta === void 0 ? void 0 : channelColorMeta[index]) !== null && _channelColorMeta$ind !== void 0 ? _channelColorMeta$ind : ch.color
209
251
  };
210
252
  })
211
253
  });
212
- onImageTitleChange === null || onImageTitleChange === void 0 || onImageTitleChange(newImage.imageInfo.imageInfo.name);
213
254
  view3d.updateActiveChannels(newImage);
214
- }, [view3d, viewerState, onImageTitleChange]);
255
+ var unsubscribeView = subscribeViewToState(useViewerState, view3d);
256
+ var unsubscribeImage = subscribeImageToState(useViewerState, view3d, newImage);
257
+ removePreviousImage.current = function () {
258
+ unsubscribeView();
259
+ unsubscribeImage();
260
+ view3d.removeAllVolumes();
261
+ removePreviousImage.current = undefined;
262
+ };
263
+ }, [props.viewerChannelSettings, view3d, changeChannelSetting]);
264
+ var _useState = useState(undefined),
265
+ _useState2 = _slicedToArray(_useState, 2),
266
+ loadedScene = _useState2[0],
267
+ setLoadedScene = _useState2[1];
268
+ var onChangeScene = useCallback(function (image, sceneIndex) {
269
+ onImageTitleChange === null || onImageTitleChange === void 0 || onImageTitleChange(image.imageInfo.imageInfo.name);
270
+ setLoadedScene(sceneIndex);
271
+ }, [onImageTitleChange]);
215
272
  var onChannelLoaded = useCallback(function (image, channelIndex, isInitialLoad) {
216
- // TODO this was once a search by name - is that still necessary or will the index always be correct?
217
- var thisChannelSettings = channelSettings[channelIndex];
218
- var _viewerState$current2 = viewerState.current,
219
- getChannelsAwaitingResetOnLoad = _viewerState$current2.getChannelsAwaitingResetOnLoad,
220
- getCurrentViewerChannelSettings = _viewerState$current2.getCurrentViewerChannelSettings,
221
- changeChannelSetting = _viewerState$current2.changeChannelSetting;
273
+ // TODO this was once a search by name - is that still necessary or will
274
+ // the index always be correct?
275
+ var _useViewerState$getSt3 = useViewerState.getState(),
276
+ channelsToResetOnLoad = _useViewerState$getSt3.channelsToResetOnLoad,
277
+ useDefaultViewerChannelSettings = _useViewerState$getSt3.useDefaultViewerChannelSettings,
278
+ channelSettings = _useViewerState$getSt3.channelSettings;
279
+ var channelState = channelSettings[channelIndex];
280
+ var viewerChannelSettings = useDefaultViewerChannelSettings ? getDefaultViewerChannelSettings() : props.viewerChannelSettings;
222
281
  var thisChannel = image.getChannel(channelIndex);
223
- var noLut = !thisChannelSettings || !thisChannelSettings.controlPoints || !thisChannelSettings.ramp;
224
- if (isInitialLoad || noLut || getChannelsAwaitingResetOnLoad().has(channelIndex)) {
282
+ var noLut = !channelState || !channelState.controlPoints || !channelState.ramp;
283
+ var hasOldRange = channelRangesRef.current[channelIndex] !== undefined;
284
+ var canInitializeToExistingRange = (channelState === null || channelState === void 0 ? void 0 : channelState.keepIntensityRange) && hasOldRange;
285
+ // True if we are loading new data and the old range cannot or should not
286
+ // be kept (no saved range or keeping intensities is disabled).
287
+ var needsInitializeToDefaults = isInitialLoad && !canInitializeToExistingRange;
288
+ if (needsInitializeToDefaults || noLut || channelsToResetOnLoad.includes(channelIndex)) {
289
+ var _initSettings$isovalu;
225
290
  // This channel needs its LUT initialized
226
- var _initializeLut = initializeLut(image, channelIndex, getCurrentViewerChannelSettings()),
291
+ var _initializeLut = initializeLut(image, channelIndex, viewerChannelSettings),
227
292
  ramp = _initializeLut.ramp,
228
293
  controlPoints = _initializeLut.controlPoints;
229
- var dtype = thisChannel.dtype;
294
+
295
+ // Initialize isovalue to channel settings or default to midpoint of data range
296
+ var name = image.channelNames[channelIndex];
297
+ var initSettings = viewerChannelSettings && findFirstChannelMatch(name, channelIndex, viewerChannelSettings);
298
+ var defaultIsovalue = thisChannel.rawMin + (thisChannel.rawMax - thisChannel.rawMin) / 2;
299
+ var isovalue = (_initSettings$isovalu = initSettings === null || initSettings === void 0 ? void 0 : initSettings.isovalue) !== null && _initSettings$isovalu !== void 0 ? _initSettings$isovalu : defaultIsovalue;
230
300
  changeChannelSetting(channelIndex, {
231
301
  controlPoints: controlPoints,
232
302
  ramp: controlPointsToRamp(ramp),
233
303
  // set the default range of the transfer function editor to cover the full range of the data type
234
- plotMin: DTYPE_RANGE[dtype].min,
235
- plotMax: DTYPE_RANGE[dtype].max
304
+ plotMin: thisChannel.rawMin,
305
+ plotMax: thisChannel.rawMax,
306
+ isovalue: isovalue
236
307
  });
237
308
  } else {
238
- // This channel has already been initialized, but its LUT was just remapped and we need to update some things
239
- var oldRange = channelRangesRef.current[channelIndex];
240
- if (thisChannelSettings.useControlPoints) {
241
- // control points were just automatically remapped - update in state
242
- var rampControlPoints = rampToControlPoints(thisChannelSettings.ramp);
243
- // now manually remap ramp using the channel's old range
244
- var remappedRampControlPoints = remapControlPointsForChannel(rampControlPoints, oldRange, thisChannel);
245
- changeChannelSetting(channelIndex, {
246
- ramp: controlPointsToRamp(remappedRampControlPoints),
247
- controlPoints: thisChannel.lut.controlPoints
248
- });
249
- } else {
250
- // ramp was just automatically remapped - update in state
251
- var _ramp = controlPointsToRamp(thisChannel.lut.controlPoints);
252
- // now manually remap control points using the channel's old range
253
- var _controlPoints = thisChannelSettings.controlPoints;
254
- var remappedControlPoints = remapControlPointsForChannel(_controlPoints, oldRange, thisChannel);
255
- changeChannelSetting(channelIndex, {
256
- controlPoints: remappedControlPoints,
257
- ramp: _ramp
258
- });
259
- }
309
+ // Expand the plot min and max to include the current data range as
310
+ // needed. This keeps the domain visually consistent when replaying
311
+ // through time or Z slices.
312
+ //
313
+ // NOTE: This must use the most up-to-date channel plot range, because
314
+ // onChannelLoaded can be called multiple times per channel during
315
+ // loading.
316
+ changeChannelSetting(channelIndex, {
317
+ plotMin: Math.min(thisChannel.rawMin, channelState.plotMin),
318
+ plotMax: Math.max(thisChannel.rawMax, channelState.plotMax)
319
+ });
260
320
  }
261
321
 
262
322
  // save the channel's new range for remapping next time
@@ -269,14 +329,16 @@ var App = function App(props) {
269
329
  if (image.isLoaded()) {
270
330
  view3d.updateActiveChannels(image);
271
331
  }
272
- }, [view3d, channelSettings, maskChannelName, viewerState]);
332
+ }, [view3d, changeChannelSetting, maskChannelName, props.viewerChannelSettings]);
273
333
  var onError = useCallback(function (error) {
274
334
  showError(error);
275
335
  onImageTitleChange === null || onImageTitleChange === void 0 || onImageTitleChange(undefined);
276
336
  }, [showError, onImageTitleChange]);
277
337
  var volume = useVolume(scenes, {
338
+ viewerChannelSettings: props.viewerChannelSettings,
278
339
  onCreateImage: onCreateImage,
279
340
  onChannelLoaded: onChannelLoaded,
341
+ onChangeScene: onChangeScene,
280
342
  onError: onError,
281
343
  maskChannelName: maskChannelName
282
344
  });
@@ -298,26 +360,25 @@ var App = function App(props) {
298
360
  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;
299
361
 
300
362
  // const [channelGroupedByType, setChannelGroupedByType] = useState<ChannelGrouping>({});
301
- var _useState = useState(function () {
363
+ var _useState3 = useState(function () {
302
364
  return window.innerWidth < CONTROL_PANEL_CLOSE_WIDTH;
303
365
  }),
304
- _useState2 = _slicedToArray(_useState, 2),
305
- controlPanelClosed = _useState2[0],
306
- setControlPanelClosed = _useState2[1];
307
- // Only allow auto-close once while the screen is too narrow.
308
- var _useState3 = useState(false),
309
366
  _useState4 = _slicedToArray(_useState3, 2),
310
- hasAutoClosedControlPanel = _useState4[0],
311
- setHasAutoClosedControlPanel = _useState4[1];
312
- var _useState5 = useState(true),
367
+ controlPanelClosed = _useState4[0],
368
+ setControlPanelClosed = _useState4[1];
369
+ // Only allow auto-close once while the screen is too narrow.
370
+ var _useState5 = useState(false),
313
371
  _useState6 = _slicedToArray(_useState5, 2),
314
- clippingPanelOpen = _useState6[0],
315
- setClippingPanelOpen = _useState6[1];
372
+ hasAutoClosedControlPanel = _useState6[0],
373
+ setHasAutoClosedControlPanel = _useState6[1];
374
+ var _useState7 = useState(true),
375
+ _useState8 = _slicedToArray(_useState7, 2),
376
+ clippingPanelOpen = _useState8[0],
377
+ setClippingPanelOpen = _useState8[1];
316
378
  var clippingPanelOpenTimeout = useRef(0);
317
379
 
318
380
  // Imperative callbacks /////////////////////////////////////////////////////
319
381
 
320
- var viewerSettings = viewerState.current;
321
382
  var saveIsosurface = useCallback(function (channelIndex, type) {
322
383
  if (image) view3d.saveChannelIsosurface(image, channelIndex, type);
323
384
  }, [image, view3d]);
@@ -329,21 +390,24 @@ var App = function App(props) {
329
390
  anchor.click();
330
391
  });
331
392
  }, [view3d]);
332
- var getMetadata = useCallback(function () {
393
+ var _props3 = props,
394
+ metadataFormatter = _props3.metadataFormatter;
395
+ var metadata = useMemo(function () {
333
396
  var imageMetadata = image === null || image === void 0 ? void 0 : image.imageMetadata;
334
397
  if (imageMetadata && metadataFormatter) {
335
398
  imageMetadata = metadataFormatter(imageMetadata);
336
399
  }
400
+ var propsMetadata = props.metadata;
337
401
  var sceneMeta;
338
- if (Array.isArray(metadata)) {
402
+ if (Array.isArray(propsMetadata)) {
339
403
  // If metadata is an array, try to index it by scene
340
- if (metadata.length >= numScenes) {
341
- sceneMeta = metadata[viewerState.current.scene];
404
+ if (propsMetadata.length >= numScenes && loadedScene !== undefined) {
405
+ sceneMeta = propsMetadata[loadedScene];
342
406
  } else {
343
- sceneMeta = metadata[0];
407
+ sceneMeta = propsMetadata[0];
344
408
  }
345
409
  } else {
346
- sceneMeta = metadata;
410
+ sceneMeta = propsMetadata;
347
411
  }
348
412
  if (imageMetadata && Object.keys(imageMetadata).length > 0) {
349
413
  return _objectSpread({
@@ -353,11 +417,11 @@ var App = function App(props) {
353
417
  var _sceneMeta;
354
418
  return (_sceneMeta = sceneMeta) !== null && _sceneMeta !== void 0 ? _sceneMeta : {};
355
419
  }
356
- }, [metadata, metadataFormatter, image, numScenes, viewerState]);
420
+ }, [props.metadata, metadataFormatter, image, loadedScene, numScenes]);
357
421
  useEffect(function () {
358
422
  var hasTime = numTimesteps > 1;
359
423
  var hasScenes = numScenes > 1;
360
- var mode3d = viewerSettings.viewMode === ViewMode.threeD;
424
+ var mode3d = viewMode === ViewMode.threeD;
361
425
  setIndicatorPositions(view3d, clippingPanelOpen, hasTime, hasScenes, mode3d);
362
426
 
363
427
  // Hide indicators while clipping panel is in motion - otherwise they pop to the right place prematurely
@@ -369,12 +433,12 @@ var App = function App(props) {
369
433
  clippingPanelOpenTimeout.current = window.setTimeout(function () {
370
434
  view3d.setShowScaleBar(true);
371
435
  view3d.setShowTimestepIndicator(true);
372
- if (viewerSettings.showAxes) {
436
+ if (showAxes) {
373
437
  view3d.setShowAxis(true);
374
438
  }
375
439
  }, CLIPPING_PANEL_ANIMATION_DURATION_MS);
376
440
  }
377
- }, [view3d, numTimesteps, numScenes, viewerSettings.viewMode, viewerSettings.showAxes, clippingPanelOpen]);
441
+ }, [view3d, numTimesteps, numScenes, viewMode, showAxes, clippingPanelOpen]);
378
442
 
379
443
  // Effects //////////////////////////////////////////////////////////////////
380
444
 
@@ -396,6 +460,8 @@ var App = function App(props) {
396
460
  return window.removeEventListener("resize", onResizeDebounced);
397
461
  };
398
462
  }, [hasAutoClosedControlPanel]);
463
+ var _props4 = props,
464
+ onControlPanelToggle = _props4.onControlPanelToggle;
399
465
  useEffect(function () {
400
466
  return onControlPanelToggle && onControlPanelToggle(controlPanelClosed);
401
467
  }, [controlPanelClosed, onControlPanelToggle]);
@@ -418,36 +484,17 @@ var App = function App(props) {
418
484
  };
419
485
 
420
486
  // Effects to imperatively sync `viewerSettings` to `view3d`
421
- useImageEffect(function (_currentImage) {
422
- view3d.setCameraMode(viewerSettings.viewMode);
423
- view3d.resize(null);
424
- }, [viewerSettings.viewMode, view3d]);
425
- useImageEffect(function (_currentImage) {
426
- if (viewerSettings.cameraState) {
427
- view3d.setCameraState(viewerSettings.cameraState);
428
- }
429
- }, [viewerSettings.cameraState, view3d]);
430
- useImageEffect(function (_currentImage) {
431
- return view3d.setAutoRotate(viewerSettings.autorotate);
432
- }, [viewerSettings.autorotate, view3d]);
433
- useImageEffect(function (_currentImage) {
434
- return view3d.setShowAxis(viewerSettings.showAxes);
435
- }, [viewerSettings.showAxes, view3d]);
436
- useImageEffect(function (_currentImage) {
437
- return view3d.setBackgroundColor(colorArrayToFloats(viewerSettings.backgroundColor));
438
- }, [viewerSettings.backgroundColor, view3d]);
439
- useImageEffect(function (currentImage) {
440
- return view3d.setBoundingBoxColor(currentImage, colorArrayToFloats(viewerSettings.boundingBoxColor));
441
- }, [viewerSettings.boundingBoxColor, view3d]);
442
- useImageEffect(function (currentImage) {
443
- return view3d.setShowBoundingBox(currentImage, viewerSettings.showBoundingBox);
444
- }, [viewerSettings.showBoundingBox, view3d]);
487
+
445
488
  useImageEffect(function (image) {
446
489
  // Check whether any channels are marked to be reset and apply it.
447
- var channelsAwaitingReset = getChannelsAwaitingReset();
490
+ var viewerState = useViewerState.getState();
491
+ var channelsToReset = viewerState.channelsToReset,
492
+ onResetChannel = viewerState.onResetChannel,
493
+ useDefaultViewerChannelSettings = viewerState.useDefaultViewerChannelSettings;
494
+ var viewerChannelSettings = useDefaultViewerChannelSettings ? getDefaultViewerChannelSettings() : props.viewerChannelSettings;
448
495
  for (var i = 0; i < channelSettings.length; i++) {
449
- if (channelsAwaitingReset.has(i)) {
450
- var _initializeLut2 = initializeLut(image, i, getCurrentViewerChannelSettings()),
496
+ if (channelsToReset.includes(i)) {
497
+ var _initializeLut2 = initializeLut(image, i, viewerChannelSettings),
451
498
  ramp = _initializeLut2.ramp,
452
499
  controlPoints = _initializeLut2.controlPoints;
453
500
  changeChannelSetting(i, {
@@ -457,40 +504,15 @@ var App = function App(props) {
457
504
  onResetChannel(i);
458
505
  }
459
506
  }
460
- }, [changeChannelSetting, channelSettings, getChannelsAwaitingReset, getCurrentViewerChannelSettings, onResetChannel]);
461
- useImageEffect(function (currentImage) {
462
- var renderMode = viewerSettings.renderMode;
463
- view3d.setMaxProjectMode(currentImage, renderMode === RenderMode.maxProject);
464
- view3d.setVolumeRenderMode(renderMode === RenderMode.pathTrace ? RENDERMODE_PATHTRACE : RENDERMODE_RAYMARCH);
465
- view3d.updateActiveChannels(currentImage);
466
- }, [viewerSettings.renderMode, view3d]);
467
- useImageEffect(function (currentImage) {
468
- view3d.updateMaskAlpha(currentImage, alphaSliderToImageValue(viewerSettings.maskAlpha));
469
- view3d.updateActiveChannels(currentImage);
470
- }, [viewerSettings.maskAlpha, view3d]);
471
- useImageEffect(function (_currentImage) {
472
- var brightness = brightnessSliderToImageValue(viewerSettings.brightness);
473
- view3d.updateExposure(brightness);
474
- }, [viewerSettings.brightness, view3d]);
475
- useImageEffect(function (currentImage) {
476
- var density = densitySliderToImageValue(viewerSettings.density);
477
- view3d.updateDensity(currentImage, density);
478
- }, [viewerSettings.density, view3d]);
479
- useImageEffect(function (currentImage) {
480
- var imageValues = gammaSliderToImageValues(viewerSettings.levels);
481
- view3d.setGamma(currentImage, imageValues.min, imageValues.scale, imageValues.max);
482
- }, [viewerSettings.levels, view3d]);
507
+ }, [changeChannelSetting, channelSettings, props.viewerChannelSettings]);
483
508
 
484
509
  // `time` and `scene` have their own special handlers via `volume`, since they both trigger loads
485
510
  useEffect(function () {
486
- return setTime(view3d, viewerSettings.time);
487
- }, [view3d, viewerSettings.time, setTime]);
511
+ return setTime(view3d, time);
512
+ }, [view3d, time, setTime]);
488
513
  useEffect(function () {
489
- return setScene(viewerSettings.scene);
490
- }, [viewerSettings.scene, setScene]);
491
- useImageEffect(function (currentImage) {
492
- return view3d.setInterpolationEnabled(currentImage, viewerSettings.interpolationEnabled);
493
- }, [viewerSettings.interpolationEnabled, view3d]);
514
+ return setScene(scene);
515
+ }, [scene, setScene]);
494
516
  useImageEffect(function (currentImage) {
495
517
  var _props$transform;
496
518
  return view3d.setVolumeTranslation(currentImage, ((_props$transform = props.transform) === null || _props$transform === void 0 ? void 0 : _props$transform.translation) || [0, 0, 0]);
@@ -499,43 +521,6 @@ var App = function App(props) {
499
521
  var _props$transform3;
500
522
  return view3d.setVolumeRotation(currentImage, ((_props$transform3 = props.transform) === null || _props$transform3 === void 0 ? void 0 : _props$transform3.rotation) || [0, 0, 0]);
501
523
  }, [(_props$transform4 = props.transform) === null || _props$transform4 === void 0 ? void 0 : _props$transform4.rotation, view3d]);
502
- var usePerAxisClippingUpdater = function usePerAxisClippingUpdater(axis, _ref, slice, viewMode) {
503
- var _ref2 = _slicedToArray(_ref, 2),
504
- minval = _ref2[0],
505
- maxval = _ref2[1];
506
- useImageEffect(
507
- // Logic to determine axis clipping range, for each of x,y,z,3d slider:
508
- // if slider was same as active axis view mode: [viewerSettings.slice[axis], viewerSettings.slice[axis] + 1.0/volumeSize[axis]]
509
- // if in 3d mode: viewerSettings.region[axis]
510
- // else: [0,1]
511
- function (currentImage) {
512
- var isOrthoAxis = false;
513
- var axismin = 0.0;
514
- var axismax = 1.0;
515
- if (viewMode === ViewMode.threeD) {
516
- axismin = minval;
517
- axismax = maxval;
518
- isOrthoAxis = false;
519
- } else {
520
- isOrthoAxis = activeAxisMap[viewMode] === axis;
521
- var oneSlice = 1 / currentImage.imageInfo.volumeSize[axis];
522
- axismin = isOrthoAxis ? slice : 0.0;
523
- axismax = isOrthoAxis ? slice + oneSlice : 1.0;
524
- if (axis === "z" && viewMode === ViewMode.xy) {
525
- view3d.setZSlice(currentImage, Math.floor(slice * currentImage.imageInfo.volumeSize.z));
526
- }
527
- }
528
- // view3d wants the coordinates in the -0.5 to 0.5 range
529
- view3d.setAxisClip(currentImage, axis, axismin - 0.5, axismax - 0.5, isOrthoAxis);
530
- view3d.setCameraMode(viewMode);
531
- // TODO under some circumstances, this effect will trigger a load. Ideally, this would be reflected in the load
532
- // state managed by `useVolume`. This is complicated by the fact that the relevant methods (`setAxisClip` and
533
- // `setZSlice`) don't provide a channel load callback like other load-triggering methods (e.g. `setTime`).
534
- }, [axis, minval, maxval, slice, viewMode]);
535
- };
536
- usePerAxisClippingUpdater("x", viewerSettings.region.x, viewerSettings.slice.x, viewMode);
537
- usePerAxisClippingUpdater("y", viewerSettings.region.y, viewerSettings.slice.y, viewMode);
538
- usePerAxisClippingUpdater("z", viewerSettings.region.z, viewerSettings.slice.z, viewMode);
539
524
 
540
525
  // Rendering ////////////////////////////////////////////////////////////////
541
526
 
@@ -553,10 +538,10 @@ var App = function App(props) {
553
538
  style: {
554
539
  height: props.appHeight
555
540
  }
556
- }, channelSettings.map(function (channelState, index) {
541
+ }, channelSettings.map(function (_ref, index) {
542
+ var name = _ref.name;
557
543
  return /*#__PURE__*/React.createElement(ChannelUpdater, {
558
- key: "".concat(index, "_").concat(channelState.name),
559
- channelState: channelState,
544
+ key: "".concat(index, "_").concat(name),
560
545
  index: index,
561
546
  view3d: view3d,
562
547
  image: image,
@@ -586,7 +571,7 @@ var App = function App(props) {
586
571
  saveIsosurface: saveIsosurface,
587
572
  onApplyColorPresets: applyColorPresets,
588
573
  viewerChannelSettings: props.viewerChannelSettings,
589
- getMetadata: getMetadata
574
+ metadata: metadata
590
575
  })), /*#__PURE__*/React.createElement(Layout, {
591
576
  className: "cell-viewer-wrapper",
592
577
  style: {
@@ -600,6 +585,7 @@ var App = function App(props) {
600
585
  canPathTrace: view3d ? view3d.hasWebGL2() : false,
601
586
  resetCamera: resetCamera,
602
587
  downloadScreenshot: saveScreenshot,
588
+ resetToSavedViewerState: resetToSavedViewerState,
603
589
  visibleControls: visibleControls
604
590
  }), /*#__PURE__*/React.createElement(CellViewerCanvasWrapper, {
605
591
  view3d: view3d,