@aics/vole-app 2.15.0 → 3.1.2

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 (79) hide show
  1. package/es/aics-image-viewer/components/App/ChannelUpdater.js +21 -3
  2. package/es/aics-image-viewer/components/App/index.js +156 -199
  3. package/es/aics-image-viewer/components/CellViewerCanvasWrapper/index.js +10 -9
  4. package/es/aics-image-viewer/components/ChannelsWidget.js +35 -22
  5. package/es/aics-image-viewer/components/ChannelsWidgetRow/index.js +27 -71
  6. package/es/aics-image-viewer/components/ChannelsWidgetRow/styles.css +0 -6
  7. package/es/aics-image-viewer/components/ColorPicker.js +1 -1
  8. package/es/aics-image-viewer/components/ControlPanel/index.js +5 -4
  9. package/es/aics-image-viewer/components/CustomizeWidget.js +12 -8
  10. package/es/aics-image-viewer/components/ErrorAlert/index.js +4 -4
  11. package/es/aics-image-viewer/components/GlobalVolumeControls.js +12 -10
  12. package/es/aics-image-viewer/components/StyleProvider/index.js +4 -2
  13. package/es/aics-image-viewer/components/TfEditor/index.js +304 -154
  14. package/es/aics-image-viewer/components/TfEditor/styles.css +53 -31
  15. package/es/aics-image-viewer/components/Toolbar/DownloadButton.js +1 -1
  16. package/es/aics-image-viewer/components/Toolbar/index.js +18 -16
  17. package/es/aics-image-viewer/components/shared/SharedCheckBox.js +1 -1
  18. package/es/aics-image-viewer/components/shared/SliderRow/styles.css +1 -0
  19. package/es/aics-image-viewer/components/shared/SmarterSlider.js +1 -1
  20. package/es/aics-image-viewer/components/shared/ViewerIcon.js +1 -1
  21. package/es/aics-image-viewer/components/useVolume.js +68 -59
  22. package/es/aics-image-viewer/shared/constants.js +2 -2
  23. package/es/aics-image-viewer/shared/utils/controlPointsToLut.js +57 -24
  24. package/es/aics-image-viewer/shared/utils/sceneStore.js +14 -4
  25. package/es/aics-image-viewer/shared/utils/storage.js +238 -0
  26. package/es/aics-image-viewer/shared/utils/test/storage.test.js +264 -0
  27. package/es/aics-image-viewer/shared/utils/test/urlParsing.test.js +249 -109
  28. package/es/aics-image-viewer/shared/utils/test/viewerChannelSettings.test.js +1 -1
  29. package/es/aics-image-viewer/shared/utils/urlParsing.js +287 -154
  30. package/es/aics-image-viewer/shared/utils/viewerState.js +4 -24
  31. package/es/aics-image-viewer/state/reset.js +111 -0
  32. package/es/aics-image-viewer/state/store.js +90 -0
  33. package/es/aics-image-viewer/state/subscribers.js +153 -0
  34. package/es/aics-image-viewer/state/test/reset.test.js +247 -0
  35. package/es/aics-image-viewer/state/util.js +71 -0
  36. package/es/index.js +3 -3
  37. package/package.json +18 -11
  38. package/type-declarations/aics-image-viewer/components/App/ChannelUpdater.d.ts +2 -4
  39. package/type-declarations/aics-image-viewer/components/App/types.d.ts +3 -3
  40. package/type-declarations/aics-image-viewer/components/AxisClipSliders/index.d.ts +6 -6
  41. package/type-declarations/aics-image-viewer/components/CellViewerCanvasWrapper/index.d.ts +5 -14
  42. package/type-declarations/aics-image-viewer/components/ChannelsWidget.d.ts +3 -6
  43. package/type-declarations/aics-image-viewer/components/ChannelsWidgetRow/index.d.ts +3 -6
  44. package/type-declarations/aics-image-viewer/components/ColorPicker.d.ts +1 -1
  45. package/type-declarations/aics-image-viewer/components/ControlPanel/index.d.ts +6 -7
  46. package/type-declarations/aics-image-viewer/components/CustomizeWidget.d.ts +2 -8
  47. package/type-declarations/aics-image-viewer/components/ErrorAlert/index.d.ts +4 -0
  48. package/type-declarations/aics-image-viewer/components/GlobalVolumeControls.d.ts +2 -9
  49. package/type-declarations/aics-image-viewer/components/MetadataViewer/index.d.ts +1 -1
  50. package/type-declarations/aics-image-viewer/components/StyleProvider/index.d.ts +1 -1
  51. package/type-declarations/aics-image-viewer/components/TfEditor/index.d.ts +10 -3
  52. package/type-declarations/aics-image-viewer/components/Toolbar/index.d.ts +3 -12
  53. package/type-declarations/aics-image-viewer/components/shared/SliderRow/index.d.ts +1 -1
  54. package/type-declarations/aics-image-viewer/components/shared/SmarterSlider.d.ts +1 -1
  55. package/type-declarations/aics-image-viewer/components/shared/ViewerIcon.d.ts +1 -1
  56. package/type-declarations/aics-image-viewer/components/useVolume.d.ts +7 -4
  57. package/type-declarations/aics-image-viewer/shared/constants.d.ts +5 -5
  58. package/type-declarations/aics-image-viewer/shared/types.d.ts +1 -1
  59. package/type-declarations/aics-image-viewer/shared/utils/controlPointsToLut.d.ts +9 -5
  60. package/type-declarations/aics-image-viewer/shared/utils/firebase/index.d.ts +1 -1
  61. package/type-declarations/aics-image-viewer/shared/utils/hooks.d.ts +1 -1
  62. package/type-declarations/aics-image-viewer/shared/utils/playControls.d.ts +1 -1
  63. package/type-declarations/aics-image-viewer/shared/utils/sceneStore.d.ts +8 -4
  64. package/type-declarations/aics-image-viewer/shared/utils/storage.d.ts +15 -0
  65. package/type-declarations/aics-image-viewer/shared/utils/urlParsing.d.ts +94 -31
  66. package/type-declarations/aics-image-viewer/shared/utils/viewerChannelSettings.d.ts +39 -1
  67. package/type-declarations/aics-image-viewer/shared/utils/viewerState.d.ts +3 -6
  68. package/type-declarations/aics-image-viewer/state/reset.d.ts +29 -0
  69. package/type-declarations/aics-image-viewer/state/store.d.ts +30 -0
  70. package/type-declarations/aics-image-viewer/state/subscribers.d.ts +4 -0
  71. package/type-declarations/aics-image-viewer/state/types.d.ts +46 -0
  72. package/type-declarations/aics-image-viewer/state/util.d.ts +8 -0
  73. package/type-declarations/index.d.ts +4 -4
  74. package/es/aics-image-viewer/components/ViewerStateProvider/ResetStateProvider.js +0 -163
  75. package/es/aics-image-viewer/components/ViewerStateProvider/index.js +0 -259
  76. package/type-declarations/aics-image-viewer/components/ViewerStateProvider/ResetStateProvider.d.ts +0 -34
  77. package/type-declarations/aics-image-viewer/components/ViewerStateProvider/index.d.ts +0 -33
  78. package/type-declarations/aics-image-viewer/components/ViewerStateProvider/types.d.ts +0 -107
  79. /package/es/aics-image-viewer/{components/ViewerStateProvider → state}/types.js +0 -0
@@ -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,25 +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";
29
26
  import { findFirstChannelMatch } from "../../shared/utils/viewerChannelSettings";
27
+ import { select, useViewerState } from "../../state/store";
28
+ import { subscribeImageToState, subscribeViewToState } from "../../state/subscribers";
30
29
  import useVolume, { ImageLoadStatus } from "../useVolume";
31
30
  import CellViewerCanvasWrapper from "../CellViewerCanvasWrapper";
32
31
  import ControlPanel from "../ControlPanel";
33
32
  import { useErrorAlert } from "../ErrorAlert";
34
33
  import StyleProvider from "../StyleProvider";
35
34
  import Toolbar from "../Toolbar";
36
- import { ViewerStateContext } from "../ViewerStateProvider";
37
35
  import ChannelUpdater from "./ChannelUpdater";
38
36
  import "../../assets/styles/globals.css";
39
37
  import "./styles.css";
@@ -106,31 +104,37 @@ var setIndicatorPositions = function setIndicatorPositions(view3d, panelOpen, ha
106
104
  view3d.setScaleBarPosition(scaleBarX, scaleBarY);
107
105
  };
108
106
  var App = function App(props) {
109
- 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;
110
108
  props = _objectSpread(_objectSpread({}, defaultProps), props);
111
109
 
112
110
  // State management /////////////////////////////////////////////////////////
113
- var viewerState = useContext(ViewerStateContext).ref;
114
- var _viewerState$current = viewerState.current,
115
- imageType = _viewerState$current.imageType,
116
- viewMode = _viewerState$current.viewMode,
117
- channelSettings = _viewerState$current.channelSettings,
118
- changeChannelSetting = _viewerState$current.changeChannelSetting,
119
- applyColorPresets = _viewerState$current.applyColorPresets,
120
- setSavedViewerChannelSettings = _viewerState$current.setSavedViewerChannelSettings,
121
- getCurrentViewerChannelSettings = _viewerState$current.getCurrentViewerChannelSettings,
122
- getChannelsAwaitingReset = _viewerState$current.getChannelsAwaitingReset,
123
- onResetChannel = _viewerState$current.onResetChannel;
124
- var _props = props,
125
- onControlPanelToggle = _props.onControlPanelToggle,
126
- onImageTitleChange = _props.onImageTitleChange,
127
- metadata = _props.metadata,
128
- metadataFormatter = _props.metadataFormatter;
129
- useMemo(function () {
130
- if (props.viewerChannelSettings) {
131
- 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
+ }
132
135
  }
133
- }, [props.viewerChannelSettings, setSavedViewerChannelSettings]);
136
+ prevViewerSettingsPropsRef.current = props.viewerSettings;
137
+ }
134
138
  var view3d = useConstructor(function () {
135
139
  return new View3d();
136
140
  });
@@ -156,11 +160,11 @@ var App = function App(props) {
156
160
  }, [view3d, showError]);
157
161
  var imageUrlRef = useRef("");
158
162
  var scenesRef = useRef([]);
159
- var _props2 = props,
160
- imageUrl = _props2.imageUrl,
161
- parentImageUrl = _props2.parentImageUrl,
162
- rawData = _props2.rawData,
163
- rawDims = _props2.rawDims;
163
+ var _props = props,
164
+ imageUrl = _props.imageUrl,
165
+ parentImageUrl = _props.parentImageUrl,
166
+ rawData = _props.rawData,
167
+ rawDims = _props.rawDims;
164
168
  var scenes = useMemo(function () {
165
169
  if (rawData && rawDims) {
166
170
  return [{
@@ -181,21 +185,31 @@ var App = function App(props) {
181
185
  return result;
182
186
  }
183
187
  }, [imageUrl, parentImageUrl, rawData, rawDims, imageType]);
184
- 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;
185
189
 
186
190
  // we need to keep track of channel ranges for remapping control points
187
191
  var channelRangesRef = useRef([]);
192
+ var removePreviousImage = useRef(undefined);
193
+ var _props2 = props,
194
+ onImageTitleChange = _props2.onImageTitleChange;
188
195
  var onCreateImage = useCallback(function (newImage) {
189
- var _newImage$imageInfo$c;
196
+ var _removePreviousImage$, _newImage$imageInfo$c;
197
+ (_removePreviousImage$ = removePreviousImage.current) === null || _removePreviousImage$ === void 0 || _removePreviousImage$.call(removePreviousImage);
190
198
  if (newImage === null) {
191
199
  return;
192
200
  }
201
+ var _useViewerState$getSt = useViewerState.getState(),
202
+ channelState = _useViewerState$getSt.channelSettings;
193
203
  var channelNames = newImage.channelNames;
194
- channelRangesRef.current = new Array(channelNames.length).fill(undefined);
195
- var channelSettings = viewerState.current.channelSettings;
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;
196
211
 
197
212
  // If the image has channel color metadata, apply those colors now
198
- var viewerChannelSettings = getCurrentViewerChannelSettings();
199
213
  var channelColorMeta = (_newImage$imageInfo$c = newImage.imageInfo.channelColors) === null || _newImage$imageInfo$c === void 0 ? void 0 : _newImage$imageInfo$c.map(function (color, index) {
200
214
  // Filter out channels that have colors in `viewerChannelSettings`
201
215
  if (viewerChannelSettings === undefined) {
@@ -222,7 +236,7 @@ var App = function App(props) {
222
236
  channels: newImage.channelNames.map(function (name, index) {
223
237
  var _channelColorMeta$ind;
224
238
  // TODO do we really need to be searching by name here?
225
- var ch = channelSettings.find(function (channel) {
239
+ var ch = channelState.find(function (channel) {
226
240
  return channel.name === name;
227
241
  });
228
242
  if (!ch) {
@@ -237,55 +251,72 @@ var App = function App(props) {
237
251
  };
238
252
  })
239
253
  });
240
- onImageTitleChange === null || onImageTitleChange === void 0 || onImageTitleChange(newImage.imageInfo.imageInfo.name);
241
254
  view3d.updateActiveChannels(newImage);
242
- }, [view3d, viewerState, onImageTitleChange, changeChannelSetting, getCurrentViewerChannelSettings]);
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]);
243
272
  var onChannelLoaded = useCallback(function (image, channelIndex, isInitialLoad) {
244
- // TODO this was once a search by name - is that still necessary or will the index always be correct?
245
- var thisChannelSettings = channelSettings[channelIndex];
246
- var _viewerState$current2 = viewerState.current,
247
- getChannelsAwaitingResetOnLoad = _viewerState$current2.getChannelsAwaitingResetOnLoad,
248
- getCurrentViewerChannelSettings = _viewerState$current2.getCurrentViewerChannelSettings,
249
- 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;
250
281
  var thisChannel = image.getChannel(channelIndex);
251
- var noLut = !thisChannelSettings || !thisChannelSettings.controlPoints || !thisChannelSettings.ramp;
252
- 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;
253
290
  // This channel needs its LUT initialized
254
- var _initializeLut = initializeLut(image, channelIndex, getCurrentViewerChannelSettings()),
291
+ var _initializeLut = initializeLut(image, channelIndex, viewerChannelSettings),
255
292
  ramp = _initializeLut.ramp,
256
293
  controlPoints = _initializeLut.controlPoints;
257
- var range = DTYPE_RANGE[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;
258
300
  changeChannelSetting(channelIndex, {
259
301
  controlPoints: controlPoints,
260
302
  ramp: controlPointsToRamp(ramp),
261
303
  // set the default range of the transfer function editor to cover the full range of the data type
262
- plotMin: range.min,
263
- plotMax: range.max,
264
- isovalue: range.min + (range.max - range.min) / 2
304
+ plotMin: thisChannel.rawMin,
305
+ plotMax: thisChannel.rawMax,
306
+ isovalue: isovalue
265
307
  });
266
308
  } else {
267
- // This channel has already been initialized, but its LUT was just remapped and we need to update some things
268
- var oldRange = channelRangesRef.current[channelIndex];
269
- if (thisChannelSettings.useControlPoints) {
270
- // control points were just automatically remapped - update in state
271
- var rampControlPoints = rampToControlPoints(thisChannelSettings.ramp);
272
- // now manually remap ramp using the channel's old range
273
- var remappedRampControlPoints = remapControlPointsForChannel(rampControlPoints, oldRange, thisChannel);
274
- changeChannelSetting(channelIndex, {
275
- ramp: controlPointsToRamp(remappedRampControlPoints),
276
- controlPoints: thisChannel.lut.controlPoints
277
- });
278
- } else {
279
- // ramp was just automatically remapped - update in state
280
- var _ramp = controlPointsToRamp(thisChannel.lut.controlPoints);
281
- // now manually remap control points using the channel's old range
282
- var _controlPoints = thisChannelSettings.controlPoints;
283
- var remappedControlPoints = remapControlPointsForChannel(_controlPoints, oldRange, thisChannel);
284
- changeChannelSetting(channelIndex, {
285
- controlPoints: remappedControlPoints,
286
- ramp: _ramp
287
- });
288
- }
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
+ });
289
320
  }
290
321
 
291
322
  // save the channel's new range for remapping next time
@@ -298,14 +329,16 @@ var App = function App(props) {
298
329
  if (image.isLoaded()) {
299
330
  view3d.updateActiveChannels(image);
300
331
  }
301
- }, [view3d, channelSettings, maskChannelName, viewerState]);
332
+ }, [view3d, changeChannelSetting, maskChannelName, props.viewerChannelSettings]);
302
333
  var onError = useCallback(function (error) {
303
334
  showError(error);
304
335
  onImageTitleChange === null || onImageTitleChange === void 0 || onImageTitleChange(undefined);
305
336
  }, [showError, onImageTitleChange]);
306
337
  var volume = useVolume(scenes, {
338
+ viewerChannelSettings: props.viewerChannelSettings,
307
339
  onCreateImage: onCreateImage,
308
340
  onChannelLoaded: onChannelLoaded,
341
+ onChangeScene: onChangeScene,
309
342
  onError: onError,
310
343
  maskChannelName: maskChannelName
311
344
  });
@@ -327,26 +360,25 @@ var App = function App(props) {
327
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;
328
361
 
329
362
  // const [channelGroupedByType, setChannelGroupedByType] = useState<ChannelGrouping>({});
330
- var _useState = useState(function () {
363
+ var _useState3 = useState(function () {
331
364
  return window.innerWidth < CONTROL_PANEL_CLOSE_WIDTH;
332
365
  }),
333
- _useState2 = _slicedToArray(_useState, 2),
334
- controlPanelClosed = _useState2[0],
335
- setControlPanelClosed = _useState2[1];
336
- // Only allow auto-close once while the screen is too narrow.
337
- var _useState3 = useState(false),
338
366
  _useState4 = _slicedToArray(_useState3, 2),
339
- hasAutoClosedControlPanel = _useState4[0],
340
- setHasAutoClosedControlPanel = _useState4[1];
341
- 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),
342
371
  _useState6 = _slicedToArray(_useState5, 2),
343
- clippingPanelOpen = _useState6[0],
344
- 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];
345
378
  var clippingPanelOpenTimeout = useRef(0);
346
379
 
347
380
  // Imperative callbacks /////////////////////////////////////////////////////
348
381
 
349
- var viewerSettings = viewerState.current;
350
382
  var saveIsosurface = useCallback(function (channelIndex, type) {
351
383
  if (image) view3d.saveChannelIsosurface(image, channelIndex, type);
352
384
  }, [image, view3d]);
@@ -358,21 +390,24 @@ var App = function App(props) {
358
390
  anchor.click();
359
391
  });
360
392
  }, [view3d]);
361
- var getMetadata = useCallback(function () {
393
+ var _props3 = props,
394
+ metadataFormatter = _props3.metadataFormatter;
395
+ var metadata = useMemo(function () {
362
396
  var imageMetadata = image === null || image === void 0 ? void 0 : image.imageMetadata;
363
397
  if (imageMetadata && metadataFormatter) {
364
398
  imageMetadata = metadataFormatter(imageMetadata);
365
399
  }
400
+ var propsMetadata = props.metadata;
366
401
  var sceneMeta;
367
- if (Array.isArray(metadata)) {
402
+ if (Array.isArray(propsMetadata)) {
368
403
  // If metadata is an array, try to index it by scene
369
- if (metadata.length >= numScenes) {
370
- sceneMeta = metadata[viewerState.current.scene];
404
+ if (propsMetadata.length >= numScenes && loadedScene !== undefined) {
405
+ sceneMeta = propsMetadata[loadedScene];
371
406
  } else {
372
- sceneMeta = metadata[0];
407
+ sceneMeta = propsMetadata[0];
373
408
  }
374
409
  } else {
375
- sceneMeta = metadata;
410
+ sceneMeta = propsMetadata;
376
411
  }
377
412
  if (imageMetadata && Object.keys(imageMetadata).length > 0) {
378
413
  return _objectSpread({
@@ -382,11 +417,11 @@ var App = function App(props) {
382
417
  var _sceneMeta;
383
418
  return (_sceneMeta = sceneMeta) !== null && _sceneMeta !== void 0 ? _sceneMeta : {};
384
419
  }
385
- }, [metadata, metadataFormatter, image, numScenes, viewerState]);
420
+ }, [props.metadata, metadataFormatter, image, loadedScene, numScenes]);
386
421
  useEffect(function () {
387
422
  var hasTime = numTimesteps > 1;
388
423
  var hasScenes = numScenes > 1;
389
- var mode3d = viewerSettings.viewMode === ViewMode.threeD;
424
+ var mode3d = viewMode === ViewMode.threeD;
390
425
  setIndicatorPositions(view3d, clippingPanelOpen, hasTime, hasScenes, mode3d);
391
426
 
392
427
  // Hide indicators while clipping panel is in motion - otherwise they pop to the right place prematurely
@@ -398,12 +433,12 @@ var App = function App(props) {
398
433
  clippingPanelOpenTimeout.current = window.setTimeout(function () {
399
434
  view3d.setShowScaleBar(true);
400
435
  view3d.setShowTimestepIndicator(true);
401
- if (viewerSettings.showAxes) {
436
+ if (showAxes) {
402
437
  view3d.setShowAxis(true);
403
438
  }
404
439
  }, CLIPPING_PANEL_ANIMATION_DURATION_MS);
405
440
  }
406
- }, [view3d, numTimesteps, numScenes, viewerSettings.viewMode, viewerSettings.showAxes, clippingPanelOpen]);
441
+ }, [view3d, numTimesteps, numScenes, viewMode, showAxes, clippingPanelOpen]);
407
442
 
408
443
  // Effects //////////////////////////////////////////////////////////////////
409
444
 
@@ -425,6 +460,8 @@ var App = function App(props) {
425
460
  return window.removeEventListener("resize", onResizeDebounced);
426
461
  };
427
462
  }, [hasAutoClosedControlPanel]);
463
+ var _props4 = props,
464
+ onControlPanelToggle = _props4.onControlPanelToggle;
428
465
  useEffect(function () {
429
466
  return onControlPanelToggle && onControlPanelToggle(controlPanelClosed);
430
467
  }, [controlPanelClosed, onControlPanelToggle]);
@@ -447,36 +484,17 @@ var App = function App(props) {
447
484
  };
448
485
 
449
486
  // Effects to imperatively sync `viewerSettings` to `view3d`
450
- useImageEffect(function (_currentImage) {
451
- view3d.setCameraMode(viewerSettings.viewMode);
452
- view3d.resize(null);
453
- }, [viewerSettings.viewMode, view3d]);
454
- useImageEffect(function (_currentImage) {
455
- if (viewerSettings.cameraState) {
456
- view3d.setCameraState(viewerSettings.cameraState);
457
- }
458
- }, [viewerSettings.cameraState, view3d]);
459
- useImageEffect(function (_currentImage) {
460
- return view3d.setAutoRotate(viewerSettings.autorotate);
461
- }, [viewerSettings.autorotate, view3d]);
462
- useImageEffect(function (_currentImage) {
463
- return view3d.setShowAxis(viewerSettings.showAxes);
464
- }, [viewerSettings.showAxes, view3d]);
465
- useImageEffect(function (_currentImage) {
466
- return view3d.setBackgroundColor(colorArrayToFloats(viewerSettings.backgroundColor));
467
- }, [viewerSettings.backgroundColor, view3d]);
468
- useImageEffect(function (currentImage) {
469
- return view3d.setBoundingBoxColor(currentImage, colorArrayToFloats(viewerSettings.boundingBoxColor));
470
- }, [viewerSettings.boundingBoxColor, view3d]);
471
- useImageEffect(function (currentImage) {
472
- return view3d.setShowBoundingBox(currentImage, viewerSettings.showBoundingBox);
473
- }, [viewerSettings.showBoundingBox, view3d]);
487
+
474
488
  useImageEffect(function (image) {
475
489
  // Check whether any channels are marked to be reset and apply it.
476
- 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;
477
495
  for (var i = 0; i < channelSettings.length; i++) {
478
- if (channelsAwaitingReset.has(i)) {
479
- var _initializeLut2 = initializeLut(image, i, getCurrentViewerChannelSettings()),
496
+ if (channelsToReset.includes(i)) {
497
+ var _initializeLut2 = initializeLut(image, i, viewerChannelSettings),
480
498
  ramp = _initializeLut2.ramp,
481
499
  controlPoints = _initializeLut2.controlPoints;
482
500
  changeChannelSetting(i, {
@@ -486,40 +504,15 @@ var App = function App(props) {
486
504
  onResetChannel(i);
487
505
  }
488
506
  }
489
- }, [changeChannelSetting, channelSettings, getChannelsAwaitingReset, getCurrentViewerChannelSettings, onResetChannel]);
490
- useImageEffect(function (currentImage) {
491
- var renderMode = viewerSettings.renderMode;
492
- view3d.setMaxProjectMode(currentImage, renderMode === RenderMode.maxProject);
493
- view3d.setVolumeRenderMode(renderMode === RenderMode.pathTrace ? RENDERMODE_PATHTRACE : RENDERMODE_RAYMARCH);
494
- view3d.updateActiveChannels(currentImage);
495
- }, [viewerSettings.renderMode, view3d]);
496
- useImageEffect(function (currentImage) {
497
- view3d.updateMaskAlpha(currentImage, alphaSliderToImageValue(viewerSettings.maskAlpha));
498
- view3d.updateActiveChannels(currentImage);
499
- }, [viewerSettings.maskAlpha, view3d]);
500
- useImageEffect(function (_currentImage) {
501
- var brightness = brightnessSliderToImageValue(viewerSettings.brightness);
502
- view3d.updateExposure(brightness);
503
- }, [viewerSettings.brightness, view3d]);
504
- useImageEffect(function (currentImage) {
505
- var density = densitySliderToImageValue(viewerSettings.density);
506
- view3d.updateDensity(currentImage, density);
507
- }, [viewerSettings.density, view3d]);
508
- useImageEffect(function (currentImage) {
509
- var imageValues = gammaSliderToImageValues(viewerSettings.levels);
510
- view3d.setGamma(currentImage, imageValues.min, imageValues.scale, imageValues.max);
511
- }, [viewerSettings.levels, view3d]);
507
+ }, [changeChannelSetting, channelSettings, props.viewerChannelSettings]);
512
508
 
513
509
  // `time` and `scene` have their own special handlers via `volume`, since they both trigger loads
514
510
  useEffect(function () {
515
- return setTime(view3d, viewerSettings.time);
516
- }, [view3d, viewerSettings.time, setTime]);
511
+ return setTime(view3d, time);
512
+ }, [view3d, time, setTime]);
517
513
  useEffect(function () {
518
- return setScene(viewerSettings.scene);
519
- }, [viewerSettings.scene, setScene]);
520
- useImageEffect(function (currentImage) {
521
- return view3d.setInterpolationEnabled(currentImage, viewerSettings.interpolationEnabled);
522
- }, [viewerSettings.interpolationEnabled, view3d]);
514
+ return setScene(scene);
515
+ }, [scene, setScene]);
523
516
  useImageEffect(function (currentImage) {
524
517
  var _props$transform;
525
518
  return view3d.setVolumeTranslation(currentImage, ((_props$transform = props.transform) === null || _props$transform === void 0 ? void 0 : _props$transform.translation) || [0, 0, 0]);
@@ -528,43 +521,6 @@ var App = function App(props) {
528
521
  var _props$transform3;
529
522
  return view3d.setVolumeRotation(currentImage, ((_props$transform3 = props.transform) === null || _props$transform3 === void 0 ? void 0 : _props$transform3.rotation) || [0, 0, 0]);
530
523
  }, [(_props$transform4 = props.transform) === null || _props$transform4 === void 0 ? void 0 : _props$transform4.rotation, view3d]);
531
- var usePerAxisClippingUpdater = function usePerAxisClippingUpdater(axis, _ref, slice, viewMode) {
532
- var _ref2 = _slicedToArray(_ref, 2),
533
- minval = _ref2[0],
534
- maxval = _ref2[1];
535
- useImageEffect(
536
- // Logic to determine axis clipping range, for each of x,y,z,3d slider:
537
- // if slider was same as active axis view mode: [viewerSettings.slice[axis], viewerSettings.slice[axis] + 1.0/volumeSize[axis]]
538
- // if in 3d mode: viewerSettings.region[axis]
539
- // else: [0,1]
540
- function (currentImage) {
541
- var isOrthoAxis = false;
542
- var axismin = 0.0;
543
- var axismax = 1.0;
544
- if (viewMode === ViewMode.threeD) {
545
- axismin = minval;
546
- axismax = maxval;
547
- isOrthoAxis = false;
548
- } else {
549
- isOrthoAxis = activeAxisMap[viewMode] === axis;
550
- var oneSlice = 1 / currentImage.imageInfo.volumeSize[axis];
551
- axismin = isOrthoAxis ? slice : 0.0;
552
- axismax = isOrthoAxis ? slice + oneSlice : 1.0;
553
- if (axis === "z" && viewMode === ViewMode.xy) {
554
- view3d.setZSlice(currentImage, Math.floor(slice * currentImage.imageInfo.volumeSize.z));
555
- }
556
- }
557
- // view3d wants the coordinates in the -0.5 to 0.5 range
558
- view3d.setAxisClip(currentImage, axis, axismin - 0.5, axismax - 0.5, isOrthoAxis);
559
- view3d.setCameraMode(viewMode);
560
- // TODO under some circumstances, this effect will trigger a load. Ideally, this would be reflected in the load
561
- // state managed by `useVolume`. This is complicated by the fact that the relevant methods (`setAxisClip` and
562
- // `setZSlice`) don't provide a channel load callback like other load-triggering methods (e.g. `setTime`).
563
- }, [axis, minval, maxval, slice, viewMode]);
564
- };
565
- usePerAxisClippingUpdater("x", viewerSettings.region.x, viewerSettings.slice.x, viewMode);
566
- usePerAxisClippingUpdater("y", viewerSettings.region.y, viewerSettings.slice.y, viewMode);
567
- usePerAxisClippingUpdater("z", viewerSettings.region.z, viewerSettings.slice.z, viewMode);
568
524
 
569
525
  // Rendering ////////////////////////////////////////////////////////////////
570
526
 
@@ -582,10 +538,10 @@ var App = function App(props) {
582
538
  style: {
583
539
  height: props.appHeight
584
540
  }
585
- }, channelSettings.map(function (channelState, index) {
541
+ }, channelSettings.map(function (_ref, index) {
542
+ var name = _ref.name;
586
543
  return /*#__PURE__*/React.createElement(ChannelUpdater, {
587
- key: "".concat(index, "_").concat(channelState.name),
588
- channelState: channelState,
544
+ key: "".concat(index, "_").concat(name),
589
545
  index: index,
590
546
  view3d: view3d,
591
547
  image: image,
@@ -615,7 +571,7 @@ var App = function App(props) {
615
571
  saveIsosurface: saveIsosurface,
616
572
  onApplyColorPresets: applyColorPresets,
617
573
  viewerChannelSettings: props.viewerChannelSettings,
618
- getMetadata: getMetadata
574
+ metadata: metadata
619
575
  })), /*#__PURE__*/React.createElement(Layout, {
620
576
  className: "cell-viewer-wrapper",
621
577
  style: {
@@ -629,6 +585,7 @@ var App = function App(props) {
629
585
  canPathTrace: view3d ? view3d.hasWebGL2() : false,
630
586
  resetCamera: resetCamera,
631
587
  downloadScreenshot: saveScreenshot,
588
+ resetToSavedViewerState: resetToSavedViewerState,
632
589
  visibleControls: visibleControls
633
590
  }), /*#__PURE__*/React.createElement(CellViewerCanvasWrapper, {
634
591
  view3d: view3d,