@vitessce/neuroglancer 3.9.5 → 3.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ReactNeuroglancer-BCg93QGV.js → ReactNeuroglancer-pv4bM8Yp.js} +43 -26
- package/dist/index-BEPd2Tds.js +37856 -0
- package/dist/index.js +1 -1
- package/dist-tsc/Neuroglancer.d.ts +0 -2
- package/dist-tsc/Neuroglancer.d.ts.map +1 -1
- package/dist-tsc/Neuroglancer.js +26 -26
- package/dist-tsc/NeuroglancerSubscriber.d.ts.map +1 -1
- package/dist-tsc/NeuroglancerSubscriber.js +219 -53
- package/dist-tsc/ReactNeuroglancer.d.ts +2 -2
- package/dist-tsc/ReactNeuroglancer.d.ts.map +1 -1
- package/dist-tsc/ReactNeuroglancer.js +31 -28
- package/dist-tsc/data-hook-ng-utils.d.ts +18 -20
- package/dist-tsc/data-hook-ng-utils.d.ts.map +1 -1
- package/dist-tsc/data-hook-ng-utils.js +136 -68
- package/dist-tsc/shader-utils.d.ts +126 -0
- package/dist-tsc/shader-utils.d.ts.map +1 -0
- package/dist-tsc/shader-utils.js +547 -0
- package/dist-tsc/shader-utils.test.d.ts +2 -0
- package/dist-tsc/shader-utils.test.d.ts.map +1 -0
- package/dist-tsc/shader-utils.test.js +364 -0
- package/dist-tsc/use-memo-custom-comparison.d.ts +14 -0
- package/dist-tsc/use-memo-custom-comparison.d.ts.map +1 -0
- package/dist-tsc/use-memo-custom-comparison.js +150 -0
- package/package.json +9 -8
- package/src/Neuroglancer.js +31 -26
- package/src/NeuroglancerSubscriber.js +361 -81
- package/src/README.md +28 -0
- package/src/ReactNeuroglancer.js +34 -27
- package/src/data-hook-ng-utils.js +178 -78
- package/src/shader-utils.js +653 -0
- package/src/shader-utils.test.js +432 -0
- package/src/use-memo-custom-comparison.js +190 -0
- package/dist/index-Wdrc02VW.js +0 -32390
- package/dist-tsc/data-hook-ng-utils.test.d.ts +0 -2
- package/dist-tsc/data-hook-ng-utils.test.d.ts.map +0 -1
- package/dist-tsc/data-hook-ng-utils.test.js +0 -35
- package/src/data-hook-ng-utils.test.js +0 -52
package/src/ReactNeuroglancer.js
CHANGED
|
@@ -427,7 +427,7 @@ export default class Neuroglancer extends React.Component {
|
|
|
427
427
|
this.disposers = [];
|
|
428
428
|
this.prevColorOverrides = new Set();
|
|
429
429
|
this.overrideColorsById = Object.create(null);
|
|
430
|
-
this.
|
|
430
|
+
this.allKnownIdsByLayer = {};
|
|
431
431
|
}
|
|
432
432
|
|
|
433
433
|
minimalPoseSnapshot = () => {
|
|
@@ -476,30 +476,36 @@ export default class Neuroglancer extends React.Component {
|
|
|
476
476
|
};
|
|
477
477
|
|
|
478
478
|
/* To add colors to the segments, turning unselected to grey */
|
|
479
|
-
applyColorsAndVisibility = (
|
|
479
|
+
applyColorsAndVisibility = (cellColorMappingByLayer) => {
|
|
480
480
|
if (!this.viewer) return;
|
|
481
|
-
//
|
|
482
|
-
// that drop out of the current selection.
|
|
483
|
-
const selected = { ...(cellColorMapping || {}) }; // clone, don't mutate props
|
|
484
|
-
for (const id of Object.keys(selected)) this.allKnownIds.add(id);
|
|
485
|
-
// If empty on first call, seed from initial segmentColors (if present)
|
|
486
|
-
if (this.allKnownIds.size === 0) {
|
|
487
|
-
const init = this.props.viewerState?.layers?.[0]?.segmentColors || {};
|
|
488
|
-
for (const id of Object.keys(init)) this.allKnownIds.add(id);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// Build a full color table: selected keep their hex, others grey
|
|
492
|
-
const fullSegmentColors = {};
|
|
493
|
-
for (const id of this.allKnownIds) {
|
|
494
|
-
fullSegmentColors[id] = selected[id] || GREY_HEX;
|
|
495
|
-
}
|
|
496
|
-
// Patch layers with the new segmentColors (pose untouched)
|
|
481
|
+
// Build full color table per layer
|
|
497
482
|
const baseLayers = (this.props.viewerState?.layers)
|
|
498
483
|
?? (this.viewer.state.toJSON().layers || []);
|
|
499
484
|
|
|
500
|
-
const newLayers = baseLayers.map((layer
|
|
501
|
-
// if
|
|
502
|
-
|
|
485
|
+
const newLayers = baseLayers.map((layer) => {
|
|
486
|
+
// Match layerScope by checking if the NG layer name contains the scope key.
|
|
487
|
+
// NG layer names are of the form:
|
|
488
|
+
// "obsSegmentations-init_A_obsSegmentations_0-init_A_obsSegmentations_0"
|
|
489
|
+
const layerScope = Object.keys(cellColorMappingByLayer).find(scope => layer.name?.includes(scope));
|
|
490
|
+
|
|
491
|
+
const selected = { ...(cellColorMappingByLayer[layerScope] || {}) };
|
|
492
|
+
|
|
493
|
+
// Track all known IDs for this layer scope
|
|
494
|
+
if (!this.allKnownIdsByLayer) this.allKnownIdsByLayer = {};
|
|
495
|
+
if (!this.allKnownIdsByLayer[layerScope]) {
|
|
496
|
+
this.allKnownIdsByLayer[layerScope] = new Set();
|
|
497
|
+
}
|
|
498
|
+
for (const id of Object.keys(selected)) {
|
|
499
|
+
this.allKnownIdsByLayer[layerScope].add(id);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Build a full color table: selected keep their hex, others grey
|
|
503
|
+
const fullSegmentColors = {};
|
|
504
|
+
for (const id of this.allKnownIdsByLayer[layerScope] || []) {
|
|
505
|
+
fullSegmentColors[id] = selected[id] || GREY_HEX;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (layer.type === 'segmentation') {
|
|
503
509
|
return { ...layer, segmentColors: fullSegmentColors };
|
|
504
510
|
}
|
|
505
511
|
return layer;
|
|
@@ -624,7 +630,7 @@ export default class Neuroglancer extends React.Component {
|
|
|
624
630
|
}
|
|
625
631
|
|
|
626
632
|
componentDidUpdate(prevProps, prevState) {
|
|
627
|
-
const { viewerState, cellColorMapping } = this.props;
|
|
633
|
+
const { viewerState, cellColorMapping: cellColorMappingByLayer } = this.props;
|
|
628
634
|
// The restoreState() call clears the 'selected' (hovered on) segment, which is needed
|
|
629
635
|
// by Neuroglancer's code to toggle segment visibilty on a mouse click. To free the user
|
|
630
636
|
// from having to move the mouse before clicking, save the selected segment and restore
|
|
@@ -702,8 +708,8 @@ export default class Neuroglancer extends React.Component {
|
|
|
702
708
|
this.withoutEmitting(() => {
|
|
703
709
|
const layers = Array.isArray(viewerState.layers) ? viewerState.layers : [];
|
|
704
710
|
this.viewer.state.restoreState({ layers });
|
|
705
|
-
if (
|
|
706
|
-
this.applyColorsAndVisibility(
|
|
711
|
+
if (cellColorMappingByLayer && Object.keys(cellColorMappingByLayer).length) {
|
|
712
|
+
this.applyColorsAndVisibility(cellColorMappingByLayer);
|
|
707
713
|
}
|
|
708
714
|
});
|
|
709
715
|
}
|
|
@@ -712,12 +718,13 @@ export default class Neuroglancer extends React.Component {
|
|
|
712
718
|
// this was to avid NG randomly assigning colors to the segments by resetting them
|
|
713
719
|
const prevSize = prevProps.cellColorMapping
|
|
714
720
|
? Object.keys(prevProps.cellColorMapping).length : 0;
|
|
715
|
-
const currSize =
|
|
716
|
-
|
|
721
|
+
const currSize = cellColorMappingByLayer
|
|
722
|
+
? Object.keys(cellColorMappingByLayer).length : 0;
|
|
723
|
+
const mappingRefChanged = prevProps.cellColorMapping !== this.props.cellColorMapping;
|
|
717
724
|
if (!this.didLayersChange(prevVS, viewerState)
|
|
718
725
|
&& (mappingRefChanged || prevSize !== currSize)) {
|
|
719
726
|
this.withoutEmitting(() => {
|
|
720
|
-
this.applyColorsAndVisibility(
|
|
727
|
+
this.applyColorsAndVisibility(cellColorMappingByLayer);
|
|
721
728
|
});
|
|
722
729
|
}
|
|
723
730
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
1
|
import { DataType } from '@vitessce/constants-internal';
|
|
2
|
+
import { cloneDeep } from 'lodash-es';
|
|
3
|
+
import { useMemoCustomComparison, customIsEqualForInitialViewerState } from './use-memo-custom-comparison.js';
|
|
4
|
+
import { getPointsShader } from './shader-utils.js';
|
|
3
5
|
|
|
4
6
|
|
|
5
7
|
export const DEFAULT_NG_PROPS = {
|
|
@@ -8,11 +10,17 @@ export const DEFAULT_NG_PROPS = {
|
|
|
8
10
|
projectionOrientation: [0, 0, 0, 1],
|
|
9
11
|
projectionScale: 1024,
|
|
10
12
|
crossSectionScale: 1,
|
|
13
|
+
dimensions: {
|
|
14
|
+
x: [1, 'nm'],
|
|
15
|
+
y: [1, 'nm'],
|
|
16
|
+
z: [1, 'nm'],
|
|
17
|
+
},
|
|
18
|
+
layers: [],
|
|
11
19
|
};
|
|
12
20
|
|
|
13
21
|
function toPrecomputedSource(url) {
|
|
14
22
|
if (!url) {
|
|
15
|
-
|
|
23
|
+
throw new Error('toPrecomputedSource: URL is required');
|
|
16
24
|
}
|
|
17
25
|
return `precomputed://${url}`;
|
|
18
26
|
}
|
|
@@ -43,8 +51,14 @@ function isInNanometerRange(value, unit, minNm = 1, maxNm = 100) {
|
|
|
43
51
|
* @param {object} opts
|
|
44
52
|
* @returns {{ x:[number,'nm'], y:[number,'nm'], z:[number,'nm'] }}
|
|
45
53
|
*/
|
|
46
|
-
function normalizeDimensionsToNanometers(opts) {
|
|
47
|
-
const {
|
|
54
|
+
export function normalizeDimensionsToNanometers(opts) {
|
|
55
|
+
const {
|
|
56
|
+
dimensionUnit,
|
|
57
|
+
dimensionX,
|
|
58
|
+
dimensionY,
|
|
59
|
+
dimensionZ,
|
|
60
|
+
...otherOptions
|
|
61
|
+
} = opts;
|
|
48
62
|
|
|
49
63
|
if (!dimensionUnit || !dimensionX || !dimensionY || !dimensionZ) {
|
|
50
64
|
console.warn('Missing dimension info');
|
|
@@ -56,82 +70,28 @@ function normalizeDimensionsToNanometers(opts) {
|
|
|
56
70
|
console.warn('Dimension was converted to nm units');
|
|
57
71
|
}
|
|
58
72
|
return {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
73
|
+
// The dimension-related fields are formatted differently in the fileDef.options
|
|
74
|
+
// vs. what the viewerState expects.
|
|
75
|
+
dimensions: {
|
|
76
|
+
x: xNm ? [dimensionX, dimensionUnit] : [1, 'nm'],
|
|
77
|
+
y: yNm ? [dimensionY, dimensionUnit] : [1, 'nm'],
|
|
78
|
+
z: zNm ? [dimensionZ, dimensionUnit] : [1, 'nm'],
|
|
79
|
+
},
|
|
80
|
+
// The non-dimension-related options can be passed through without modification.
|
|
81
|
+
...otherOptions,
|
|
62
82
|
};
|
|
63
83
|
}
|
|
64
84
|
|
|
65
|
-
export function
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
?? loader?.coordinationValues?.fileUid
|
|
74
|
-
?? undefined;
|
|
75
|
-
|
|
76
|
-
const { position, projectionOrientation,
|
|
77
|
-
projectionScale, crossSectionScale } = loader?.options ?? {};
|
|
78
|
-
const isPrecomputed = loader?.fileType.includes('precomputed');
|
|
79
|
-
if (!isPrecomputed) {
|
|
80
|
-
console.warn('Filetype needs to be precomputed');
|
|
81
|
-
}
|
|
82
|
-
return {
|
|
83
|
-
key,
|
|
84
|
-
type: 'segmentation',
|
|
85
|
-
fileUid,
|
|
86
|
-
layout: DEFAULT_NG_PROPS.layout,
|
|
87
|
-
url,
|
|
88
|
-
source: toPrecomputedSource(url),
|
|
89
|
-
name: fileUid ?? key?.name ?? 'segmentation',
|
|
90
|
-
// For precomputed: nm is the unit used
|
|
91
|
-
dimensions: normalizeDimensionsToNanometers(loader?.options),
|
|
92
|
-
// If not provided, no error, but difficult to see the data
|
|
93
|
-
position: Array.isArray(position) && position.length === 3
|
|
94
|
-
? position : DEFAULT_NG_PROPS.position,
|
|
95
|
-
// If not provided, will have a default orientation
|
|
96
|
-
projectionOrientation: Array.isArray(projectionOrientation)
|
|
97
|
-
&& projectionOrientation.length === 4
|
|
98
|
-
? projectionOrientation : DEFAULT_NG_PROPS.projectionOrientation,
|
|
99
|
-
projectionScale: Number.isFinite(projectionScale)
|
|
100
|
-
? projectionScale : DEFAULT_NG_PROPS.projectionScale,
|
|
101
|
-
crossSectionScale: Number.isFinite(crossSectionScale)
|
|
102
|
-
? crossSectionScale : DEFAULT_NG_PROPS.crossSectionScale,
|
|
103
|
-
};
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function useExtractOptionsForNg(loaders, dataset, dataType) {
|
|
108
|
-
const extractedEntities = useMemo(
|
|
109
|
-
() => extractDataTypeEntities(loaders, dataset, dataType),
|
|
110
|
-
[loaders, dataset, dataType],
|
|
111
|
-
);
|
|
112
|
-
const layers = useMemo(() => extractedEntities
|
|
113
|
-
.filter(t => t.source)
|
|
114
|
-
.map(t => ({
|
|
115
|
-
type: t.type,
|
|
116
|
-
source: t.source,
|
|
117
|
-
segments: [],
|
|
118
|
-
name: t.name || 'segmentation',
|
|
119
|
-
})), [extractedEntities]);
|
|
120
|
-
|
|
121
|
-
const viewerState = useMemo(() => ({
|
|
122
|
-
dimensions: extractedEntities[0]?.dimensions,
|
|
123
|
-
position: extractedEntities[0]?.position,
|
|
124
|
-
crossSectionScale: extractedEntities[0]?.crossSectionScale,
|
|
125
|
-
projectionOrientation: extractedEntities[0]?.projectionOrientation,
|
|
126
|
-
projectionScale: extractedEntities[0]?.projectionScale,
|
|
127
|
-
layers,
|
|
128
|
-
layout: extractedEntities[0].layout,
|
|
129
|
-
}));
|
|
130
|
-
|
|
131
|
-
return [viewerState];
|
|
85
|
+
export function toNgLayerName(dataType, layerScope, channelScope = null) {
|
|
86
|
+
if (dataType === DataType.OBS_SEGMENTATIONS) {
|
|
87
|
+
return `obsSegmentations-${layerScope}-${channelScope}`;
|
|
88
|
+
}
|
|
89
|
+
if (dataType === DataType.OBS_POINTS) {
|
|
90
|
+
return `obsPoints-${layerScope}`;
|
|
91
|
+
}
|
|
92
|
+
throw new Error(`Unsupported data type: ${dataType}`);
|
|
132
93
|
}
|
|
133
94
|
|
|
134
|
-
|
|
135
95
|
/**
|
|
136
96
|
* Get the parameters for NG's viewerstate.
|
|
137
97
|
* @param {object} loaders The object mapping
|
|
@@ -145,8 +105,148 @@ export function useExtractOptionsForNg(loaders, dataset, dataType) {
|
|
|
145
105
|
* @returns [viewerState]
|
|
146
106
|
*/
|
|
147
107
|
export function useNeuroglancerViewerState(
|
|
148
|
-
|
|
149
|
-
|
|
108
|
+
theme,
|
|
109
|
+
segmentationLayerScopes,
|
|
110
|
+
segmentationChannelScopesByLayer,
|
|
111
|
+
segmentationLayerCoordination,
|
|
112
|
+
segmentationChannelCoordination,
|
|
113
|
+
obsSegmentationsUrls,
|
|
114
|
+
obsSegmentationsData,
|
|
115
|
+
pointLayerScopes,
|
|
116
|
+
pointLayerCoordination,
|
|
117
|
+
obsPointsUrls,
|
|
118
|
+
obsPointsData,
|
|
119
|
+
pointMultiIndicesData,
|
|
150
120
|
) {
|
|
151
|
-
|
|
121
|
+
const viewerState = useMemoCustomComparison(() => {
|
|
122
|
+
let result = cloneDeep(DEFAULT_NG_PROPS);
|
|
123
|
+
|
|
124
|
+
// ======= SEGMENTATIONS =======
|
|
125
|
+
|
|
126
|
+
// Iterate over segmentation layers and channels.
|
|
127
|
+
segmentationLayerScopes.forEach((layerScope) => {
|
|
128
|
+
const layerCoordination = segmentationLayerCoordination[0][layerScope];
|
|
129
|
+
const channelScopes = segmentationChannelScopesByLayer[layerScope] || [];
|
|
130
|
+
const layerData = obsSegmentationsData[layerScope];
|
|
131
|
+
const layerUrl = obsSegmentationsUrls[layerScope]?.[0]?.url;
|
|
132
|
+
|
|
133
|
+
if (layerUrl && layerData) {
|
|
134
|
+
const {
|
|
135
|
+
spatialLayerVisible,
|
|
136
|
+
} = layerCoordination || {};
|
|
137
|
+
channelScopes.forEach((channelScope) => {
|
|
138
|
+
const channelCoordination = segmentationChannelCoordination[0]
|
|
139
|
+
?.[layerScope]?.[channelScope];
|
|
140
|
+
const {
|
|
141
|
+
spatialChannelVisible,
|
|
142
|
+
} = channelCoordination || {};
|
|
143
|
+
result = {
|
|
144
|
+
...result,
|
|
145
|
+
layers: [
|
|
146
|
+
...result.layers,
|
|
147
|
+
{
|
|
148
|
+
type: 'segmentation',
|
|
149
|
+
source: toPrecomputedSource(layerUrl),
|
|
150
|
+
segments: [],
|
|
151
|
+
name: toNgLayerName(DataType.OBS_SEGMENTATIONS, layerScope, channelScope),
|
|
152
|
+
visible: spatialLayerVisible && spatialChannelVisible, // Both layer and channel
|
|
153
|
+
// visibility must be true for the layer to be visible.
|
|
154
|
+
// TODO: update this to extract specific properties from
|
|
155
|
+
// neuroglancerOptions as needed.
|
|
156
|
+
...(layerData.neuroglancerOptions ?? {}),
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// ======= POINTS =======
|
|
165
|
+
|
|
166
|
+
// Iterate over point layers.
|
|
167
|
+
pointLayerScopes.forEach((layerScope) => {
|
|
168
|
+
const layerCoordination = pointLayerCoordination[0][layerScope];
|
|
169
|
+
const layerData = obsPointsData[layerScope];
|
|
170
|
+
const layerUrl = obsPointsUrls[layerScope]?.[0]?.url;
|
|
171
|
+
|
|
172
|
+
const featureIndex = pointMultiIndicesData[layerScope]?.featureIndex;
|
|
173
|
+
|
|
174
|
+
if (layerUrl && layerData) {
|
|
175
|
+
const {
|
|
176
|
+
spatialLayerVisible,
|
|
177
|
+
spatialLayerOpacity,
|
|
178
|
+
obsColorEncoding,
|
|
179
|
+
spatialLayerColor,
|
|
180
|
+
featureSelection,
|
|
181
|
+
featureFilterMode,
|
|
182
|
+
featureColor,
|
|
183
|
+
} = layerCoordination || {};
|
|
184
|
+
|
|
185
|
+
// Dynamically construct the shader based on the color encoding
|
|
186
|
+
// and other coordination values.
|
|
187
|
+
const shader = getPointsShader({
|
|
188
|
+
theme,
|
|
189
|
+
featureIndex,
|
|
190
|
+
spatialLayerOpacity,
|
|
191
|
+
obsColorEncoding,
|
|
192
|
+
spatialLayerColor,
|
|
193
|
+
featureSelection,
|
|
194
|
+
featureFilterMode,
|
|
195
|
+
featureColor,
|
|
196
|
+
|
|
197
|
+
featureIndexProp: layerData.neuroglancerOptions?.featureIndexProp,
|
|
198
|
+
pointIndexProp: layerData.neuroglancerOptions?.pointIndexProp,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
result = {
|
|
202
|
+
...result,
|
|
203
|
+
layers: [
|
|
204
|
+
...result.layers,
|
|
205
|
+
{
|
|
206
|
+
type: 'annotation',
|
|
207
|
+
source: {
|
|
208
|
+
url: toPrecomputedSource(layerUrl),
|
|
209
|
+
subsources: {
|
|
210
|
+
default: true,
|
|
211
|
+
},
|
|
212
|
+
enableDefaultSubsources: false,
|
|
213
|
+
},
|
|
214
|
+
tab: 'annotations',
|
|
215
|
+
shader,
|
|
216
|
+
name: toNgLayerName(DataType.OBS_POINTS, layerScope),
|
|
217
|
+
visible: spatialLayerVisible,
|
|
218
|
+
// Options from layerData.neuroglancerOptions
|
|
219
|
+
// like projectionAnnotationSpacing:
|
|
220
|
+
projectionAnnotationSpacing: layerData.neuroglancerOptions
|
|
221
|
+
?.projectionAnnotationSpacing ?? 1.0,
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
|
|
225
|
+
// TODO: is this needed?
|
|
226
|
+
// The selected layer here will overwrite anything
|
|
227
|
+
// that was previously specified.
|
|
228
|
+
selectedLayer: {
|
|
229
|
+
// size: ? // TODO: is this needed?
|
|
230
|
+
layer: toNgLayerName(DataType.OBS_POINTS, layerScope),
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
return result;
|
|
236
|
+
}, {
|
|
237
|
+
theme,
|
|
238
|
+
segmentationLayerScopes,
|
|
239
|
+
segmentationChannelScopesByLayer,
|
|
240
|
+
segmentationLayerCoordination,
|
|
241
|
+
segmentationChannelCoordination,
|
|
242
|
+
obsSegmentationsUrls,
|
|
243
|
+
obsSegmentationsData,
|
|
244
|
+
pointLayerScopes,
|
|
245
|
+
pointLayerCoordination,
|
|
246
|
+
obsPointsUrls,
|
|
247
|
+
obsPointsData,
|
|
248
|
+
pointMultiIndicesData,
|
|
249
|
+
}, customIsEqualForInitialViewerState);
|
|
250
|
+
|
|
251
|
+
return viewerState;
|
|
152
252
|
}
|