@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/dist/index.js
CHANGED
|
@@ -3,8 +3,6 @@ export class NeuroglancerComp {
|
|
|
3
3
|
bundleRoot: any;
|
|
4
4
|
cellColorMapping: any;
|
|
5
5
|
justReceivedExternalUpdate: boolean;
|
|
6
|
-
prevElement: any;
|
|
7
|
-
prevClickHandler: ((event: any) => void) | null;
|
|
8
6
|
prevMouseStateChanged: any;
|
|
9
7
|
prevHoverHandler: (() => void) | null;
|
|
10
8
|
onViewerStateChanged(nextState: any): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Neuroglancer.d.ts","sourceRoot":"","sources":["../src/Neuroglancer.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Neuroglancer.d.ts","sourceRoot":"","sources":["../src/Neuroglancer.js"],"names":[],"mappings":"AAYA;IACE,wBAYC;IAVC,gBAAgC;IAChC,sBAA8C;IAC9C,oCAAuC;IACvC,2BAAiC;IACjC,sCAA4B;IA8D9B,2CAGC;IAzDD,4BAoDC;IAxDC,0BAAgD;IAChD,iCAA8D;IA8DhE,yCAKC;IAED,sBAoBC;CACF"}
|
package/dist-tsc/Neuroglancer.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/* eslint-disable react-refresh/only-export-components */
|
|
3
3
|
import React, { PureComponent, Suspense } from 'react';
|
|
4
|
-
import { ChunkWorker } from '@vitessce/neuroglancer-workers';
|
|
4
|
+
import { ChunkWorker, AsyncComputationWorker } from '@vitessce/neuroglancer-workers';
|
|
5
5
|
import { NeuroglancerGlobalStyles } from './styles.js';
|
|
6
6
|
const LazyReactNeuroglancer = React.lazy(() => import('./ReactNeuroglancer.js'));
|
|
7
7
|
function createWorker() {
|
|
8
|
-
|
|
8
|
+
const worker = new ChunkWorker();
|
|
9
|
+
worker.AsyncComputationWorker = AsyncComputationWorker;
|
|
10
|
+
return worker;
|
|
9
11
|
}
|
|
10
12
|
export class NeuroglancerComp extends PureComponent {
|
|
11
13
|
constructor(props) {
|
|
@@ -13,8 +15,6 @@ export class NeuroglancerComp extends PureComponent {
|
|
|
13
15
|
this.bundleRoot = createWorker();
|
|
14
16
|
this.cellColorMapping = props.cellColorMapping;
|
|
15
17
|
this.justReceivedExternalUpdate = false;
|
|
16
|
-
this.prevElement = null;
|
|
17
|
-
this.prevClickHandler = null;
|
|
18
18
|
this.prevMouseStateChanged = null;
|
|
19
19
|
this.prevHoverHandler = null;
|
|
20
20
|
this.onViewerStateChanged = this.onViewerStateChanged.bind(this);
|
|
@@ -29,42 +29,45 @@ export class NeuroglancerComp extends PureComponent {
|
|
|
29
29
|
if (viewerRef) {
|
|
30
30
|
// Mount
|
|
31
31
|
const { viewer } = viewerRef;
|
|
32
|
-
this.prevElement = viewer.element;
|
|
33
32
|
this.prevMouseStateChanged = viewer.mouseState.changed;
|
|
34
|
-
|
|
33
|
+
// For now, can omit the sliceView bindings, as we only use perspectiveView
|
|
34
|
+
// viewer.inputEventBindings.sliceView.set('at:dblclick0', () => {});
|
|
35
35
|
viewer.inputEventBindings.perspectiveView.set('at:dblclick0', () => { });
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
// Disable space interaction to prevent triggering 4panels layout.
|
|
37
|
+
viewer.inputEventBindings.sliceView.set('at:space', () => { });
|
|
38
|
+
viewer.inputEventBindings.perspectiveView.set('at:space', () => { });
|
|
39
|
+
// Remap plain wheel to ctrl+wheel (zoom) action
|
|
40
|
+
// by traversing the parent binding maps.
|
|
41
|
+
const remapWheelToZoom = (map) => {
|
|
42
|
+
if (map.bindings) {
|
|
43
|
+
const ctrlWheelAction = map.bindings.get('at:control+wheel');
|
|
44
|
+
if (ctrlWheelAction) {
|
|
45
|
+
// Replace plain wheel with the zoom action
|
|
46
|
+
map.bindings.set('at:wheel', ctrlWheelAction);
|
|
47
|
+
const ctrlWheelBubble = map.bindings.get('bubble:control+wheel');
|
|
48
|
+
if (ctrlWheelBubble) {
|
|
49
|
+
map.bindings.set('bubble:wheel', ctrlWheelBubble);
|
|
44
50
|
}
|
|
45
|
-
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (map.parents) {
|
|
54
|
+
map.parents.forEach(p => remapWheelToZoom(p));
|
|
46
55
|
}
|
|
47
56
|
};
|
|
57
|
+
remapWheelToZoom(viewer.inputEventBindings.perspectiveView);
|
|
48
58
|
this.prevHoverHandler = () => {
|
|
49
59
|
if (viewer.mouseState.pickedValue !== undefined) {
|
|
50
60
|
const pickedSegment = viewer.mouseState.pickedValue;
|
|
51
61
|
this.latestOnSelectHoveredCoords?.(pickedSegment?.low);
|
|
52
62
|
}
|
|
53
63
|
};
|
|
54
|
-
viewer.element.addEventListener('mouseup', this.prevClickHandler);
|
|
55
64
|
viewer.mouseState.changed.add(this.prevHoverHandler);
|
|
56
65
|
}
|
|
57
66
|
else {
|
|
58
|
-
// Unmount (viewerRef is null)
|
|
59
|
-
if (this.prevElement && this.prevClickHandler) {
|
|
60
|
-
this.prevElement.removeEventListener('mouseup', this.prevClickHandler);
|
|
61
|
-
this.prevClickHandler = null;
|
|
62
|
-
}
|
|
63
67
|
if (this.prevMouseStateChanged && this.prevHoverHandler) {
|
|
64
68
|
this.prevMouseStateChanged.remove(this.prevHoverHandler);
|
|
65
69
|
this.prevHoverHandler = null;
|
|
66
70
|
}
|
|
67
|
-
this.prevElement = null;
|
|
68
71
|
this.prevMouseStateChanged = null;
|
|
69
72
|
}
|
|
70
73
|
}
|
|
@@ -73,10 +76,7 @@ export class NeuroglancerComp extends PureComponent {
|
|
|
73
76
|
setViewerState(nextState);
|
|
74
77
|
}
|
|
75
78
|
componentDidUpdate(prevProps) {
|
|
76
|
-
const {
|
|
77
|
-
if (prevProps.onSegmentClick !== onSegmentClick) {
|
|
78
|
-
this.latestOnSegmentClick = onSegmentClick;
|
|
79
|
-
}
|
|
79
|
+
const { onSelectHoveredCoords } = this.props;
|
|
80
80
|
if (prevProps.onSelectHoveredCoords !== onSelectHoveredCoords) {
|
|
81
81
|
this.latestOnSelectHoveredCoords = onSelectHoveredCoords;
|
|
82
82
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NeuroglancerSubscriber.d.ts","sourceRoot":"","sources":["../src/NeuroglancerSubscriber.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"NeuroglancerSubscriber.d.ts","sourceRoot":"","sources":["../src/NeuroglancerSubscriber.js"],"names":[],"mappings":"AAwEA,gEAkuBC"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/* eslint-disable max-len */
|
|
2
3
|
/* eslint-disable no-unused-vars */
|
|
3
|
-
import React, { useCallback, useMemo, useRef, useEffect, useState } from 'react';
|
|
4
|
-
import { TitleInfo, useCoordination,
|
|
5
|
-
import { ViewHelpMapping, ViewType, COMPONENT_COORDINATION_TYPES, } from '@vitessce/constants-internal';
|
|
4
|
+
import React, { useCallback, useMemo, useRef, useEffect, useState, useReducer } from 'react';
|
|
5
|
+
import { TitleInfo, useReady, useInitialCoordination, useCoordination, useCoordinationScopes, useCoordinationScopesBy, useComplexCoordination, useMultiCoordinationScopesNonNull, useMultiCoordinationScopesSecondaryNonNull, useComplexCoordinationSecondary, useLoaders, useMergeCoordination, useMultiObsPoints, usePointMultiObsFeatureMatrixIndices, useMultiObsSegmentations, useSegmentationMultiFeatureSelection, useSegmentationMultiObsFeatureMatrixIndices, useSegmentationMultiObsSets, useGridItemSize, } from '@vitessce/vit-s';
|
|
6
|
+
import { ViewHelpMapping, ViewType, CoordinationType, COMPONENT_COORDINATION_TYPES, } from '@vitessce/constants-internal';
|
|
6
7
|
import { mergeObsSets, getCellColors, setObsSelection } from '@vitessce/sets-utils';
|
|
8
|
+
import { MultiLegend } from '@vitessce/legend';
|
|
7
9
|
import { NeuroglancerComp } from './Neuroglancer.js';
|
|
8
10
|
import { useNeuroglancerViewerState } from './data-hook-ng-utils.js';
|
|
11
|
+
import { useMemoCustomComparison, customIsEqualForCellColors, } from './use-memo-custom-comparison.js';
|
|
9
12
|
import { useStyles } from './styles.js';
|
|
10
13
|
import { quaternionToEuler, eulerToQuaternion, valueGreaterThanEpsilon, nearEq, makeVitNgZoomCalibrator, conjQuat, multiplyQuat, rad2deg, deg2rad, Q_Y_UP, } from './utils.js';
|
|
11
14
|
const VITESSCE_INTERACTION_DELAY = 50;
|
|
@@ -14,6 +17,7 @@ const ZOOM_EPS = 1e-2;
|
|
|
14
17
|
const ROTATION_EPS = 1e-3;
|
|
15
18
|
const TARGET_EPS = 0.5;
|
|
16
19
|
const NG_ROT_COOLDOWN_MS = 120;
|
|
20
|
+
const GUIDE_URL = 'https://vitessce.io/docs/ng-guide/';
|
|
17
21
|
const LAST_INTERACTION_SOURCE = {
|
|
18
22
|
vitessce: 'vitessce',
|
|
19
23
|
neuroglancer: 'neuroglancer',
|
|
@@ -24,21 +28,187 @@ function rgbToHex(rgb) {
|
|
|
24
28
|
: `#${rgb.map(c => c.toString(16).padStart(2, '0')).join('')}`);
|
|
25
29
|
}
|
|
26
30
|
export function NeuroglancerSubscriber(props) {
|
|
27
|
-
const { coordinationScopes: coordinationScopesRaw, closeButtonVisible, downloadButtonVisible, removeGridComponent, theme, title = 'Neuroglancer', helpText = ViewHelpMapping.NEUROGLANCER,
|
|
31
|
+
const { uuid, coordinationScopes: coordinationScopesRaw, coordinationScopesBy: coordinationScopesByRaw, closeButtonVisible, downloadButtonVisible, removeGridComponent, theme, title = 'Spatial', subtitle = 'Powered by Neuroglancer', helpText = ViewHelpMapping.NEUROGLANCER,
|
|
32
|
+
// Note: this is a temporary mechanism
|
|
33
|
+
// to pass an initial NG camera state.
|
|
34
|
+
// Ideally, all camera state should be passed via
|
|
35
|
+
// the existing spatialZoom, spatialTargetX, spatialRotationOrbit, etc,
|
|
36
|
+
// and then NeuroglancerSubscriber should internally convert
|
|
37
|
+
// to NG-compatible values, which would eliminate the need for this.
|
|
38
|
+
initialNgCameraState, } = props;
|
|
28
39
|
const loaders = useLoaders();
|
|
40
|
+
const mergeCoordination = useMergeCoordination();
|
|
41
|
+
// Acccount for possible meta-coordination.
|
|
29
42
|
const coordinationScopes = useCoordinationScopes(coordinationScopesRaw);
|
|
43
|
+
const coordinationScopesBy = useCoordinationScopesBy(coordinationScopes, coordinationScopesByRaw);
|
|
30
44
|
const [{ dataset, obsType, spatialZoom, spatialTargetX, spatialTargetY, spatialRotationX, spatialRotationY, spatialRotationZ, spatialRotationOrbit,
|
|
31
45
|
// spatialOrbitAxis, // always along Y-axis - not used in conversion
|
|
32
46
|
embeddingType: mapping, obsSetColor: cellSetColor, obsSetSelection: cellSetSelection, additionalObsSets: additionalCellSets, }, { setAdditionalObsSets: setAdditionalCellSets, setObsSetColor: setCellSetColor, setObsColorEncoding: setCellColorEncoding, setObsSetSelection: setCellSetSelection, setObsHighlight: setCellHighlight, setSpatialTargetX: setTargetX, setSpatialTargetY: setTargetY, setSpatialRotationX: setRotationX,
|
|
33
47
|
// setSpatialRotationY: setRotationY,
|
|
34
48
|
// setSpatialRotationZ: setRotationZ,
|
|
35
49
|
setSpatialRotationOrbit: setRotationOrbit, setSpatialZoom: setZoom, }] = useCoordination(COMPONENT_COORDINATION_TYPES[ViewType.NEUROGLANCER], coordinationScopes);
|
|
50
|
+
const [ngWidth, ngHeight, containerRef] = useGridItemSize();
|
|
51
|
+
const [segmentationLayerScopes, segmentationChannelScopesByLayer,] = useMultiCoordinationScopesSecondaryNonNull(CoordinationType.SEGMENTATION_CHANNEL, CoordinationType.SEGMENTATION_LAYER, coordinationScopes, coordinationScopesBy);
|
|
52
|
+
const pointLayerScopes = useMultiCoordinationScopesNonNull(CoordinationType.POINT_LAYER, coordinationScopes);
|
|
53
|
+
// Object keys are coordination scope names for spatialSegmentationLayer.
|
|
54
|
+
const segmentationLayerCoordination = useComplexCoordination([
|
|
55
|
+
CoordinationType.FILE_UID,
|
|
56
|
+
CoordinationType.SEGMENTATION_CHANNEL,
|
|
57
|
+
CoordinationType.SPATIAL_LAYER_VISIBLE,
|
|
58
|
+
CoordinationType.SPATIAL_LAYER_OPACITY,
|
|
59
|
+
], coordinationScopes, coordinationScopesBy, CoordinationType.SEGMENTATION_LAYER);
|
|
60
|
+
// Object keys are coordination scope names for spatialSegmentationChannel.
|
|
61
|
+
const segmentationChannelCoordination = useComplexCoordinationSecondary([
|
|
62
|
+
CoordinationType.OBS_TYPE,
|
|
63
|
+
CoordinationType.SPATIAL_TARGET_C,
|
|
64
|
+
CoordinationType.SPATIAL_CHANNEL_VISIBLE,
|
|
65
|
+
CoordinationType.SPATIAL_CHANNEL_OPACITY,
|
|
66
|
+
CoordinationType.SPATIAL_CHANNEL_COLOR,
|
|
67
|
+
CoordinationType.SPATIAL_SEGMENTATION_FILLED,
|
|
68
|
+
CoordinationType.SPATIAL_SEGMENTATION_STROKE_WIDTH,
|
|
69
|
+
CoordinationType.OBS_COLOR_ENCODING,
|
|
70
|
+
CoordinationType.FEATURE_SELECTION,
|
|
71
|
+
CoordinationType.FEATURE_AGGREGATION_STRATEGY,
|
|
72
|
+
CoordinationType.FEATURE_VALUE_COLORMAP,
|
|
73
|
+
CoordinationType.FEATURE_VALUE_COLORMAP_RANGE,
|
|
74
|
+
CoordinationType.OBS_SET_COLOR,
|
|
75
|
+
CoordinationType.OBS_SET_SELECTION,
|
|
76
|
+
CoordinationType.ADDITIONAL_OBS_SETS,
|
|
77
|
+
CoordinationType.OBS_HIGHLIGHT,
|
|
78
|
+
CoordinationType.TOOLTIPS_VISIBLE,
|
|
79
|
+
CoordinationType.TOOLTIP_CROSSHAIRS_VISIBLE,
|
|
80
|
+
CoordinationType.LEGEND_VISIBLE,
|
|
81
|
+
], coordinationScopes, coordinationScopesBy, CoordinationType.SEGMENTATION_LAYER, CoordinationType.SEGMENTATION_CHANNEL);
|
|
82
|
+
// Point layer
|
|
83
|
+
const pointLayerCoordination = useComplexCoordination([
|
|
84
|
+
CoordinationType.OBS_TYPE,
|
|
85
|
+
CoordinationType.SPATIAL_LAYER_VISIBLE,
|
|
86
|
+
CoordinationType.SPATIAL_LAYER_OPACITY,
|
|
87
|
+
CoordinationType.OBS_COLOR_ENCODING,
|
|
88
|
+
CoordinationType.FEATURE_COLOR,
|
|
89
|
+
CoordinationType.FEATURE_FILTER_MODE,
|
|
90
|
+
CoordinationType.FEATURE_SELECTION,
|
|
91
|
+
CoordinationType.FEATURE_VALUE_COLORMAP,
|
|
92
|
+
CoordinationType.FEATURE_VALUE_COLORMAP_RANGE,
|
|
93
|
+
CoordinationType.SPATIAL_LAYER_COLOR,
|
|
94
|
+
CoordinationType.OBS_HIGHLIGHT,
|
|
95
|
+
CoordinationType.TOOLTIPS_VISIBLE,
|
|
96
|
+
CoordinationType.TOOLTIP_CROSSHAIRS_VISIBLE,
|
|
97
|
+
CoordinationType.LEGEND_VISIBLE,
|
|
98
|
+
], coordinationScopes, coordinationScopesBy, CoordinationType.POINT_LAYER);
|
|
99
|
+
// Points data
|
|
100
|
+
const [obsPointsData, obsPointsDataStatus, obsPointsUrls, obsPointsErrors] = useMultiObsPoints(coordinationScopes, coordinationScopesBy, loaders, dataset, mergeCoordination, uuid);
|
|
101
|
+
const [pointMultiIndicesData, pointMultiIndicesDataStatus, pointMultiIndicesDataErrors] = usePointMultiObsFeatureMatrixIndices(coordinationScopes, coordinationScopesBy, loaders, dataset);
|
|
102
|
+
// Segmentations data
|
|
103
|
+
const [obsSegmentationsData, obsSegmentationsDataStatus, obsSegmentationsUrls, obsSegmentationsDataErrors] = useMultiObsSegmentations(coordinationScopes, coordinationScopesBy, loaders, dataset, mergeCoordination, uuid);
|
|
104
|
+
const [obsSegmentationsSetsData, obsSegmentationsSetsDataStatus, obsSegmentationsSetsDataErrors] = useSegmentationMultiObsSets(coordinationScopes, coordinationScopesBy, loaders, dataset);
|
|
105
|
+
const [segmentationMultiExpressionData, segmentationMultiLoadedFeatureSelection, segmentationMultiExpressionExtents, segmentationMultiExpressionNormData, segmentationMultiFeatureSelectionStatus, segmentationMultiFeatureSelectionErrors,] = useSegmentationMultiFeatureSelection(coordinationScopes, coordinationScopesBy, loaders, dataset);
|
|
106
|
+
const [segmentationMultiIndicesData, segmentationMultiIndicesDataStatus, segmentationMultiIndicesDataErrors] = useSegmentationMultiObsFeatureMatrixIndices(coordinationScopes, coordinationScopesBy, loaders, dataset);
|
|
107
|
+
const errors = [
|
|
108
|
+
...obsPointsErrors,
|
|
109
|
+
...obsSegmentationsDataErrors,
|
|
110
|
+
...obsSegmentationsSetsDataErrors,
|
|
111
|
+
...pointMultiIndicesDataErrors,
|
|
112
|
+
...segmentationMultiFeatureSelectionErrors,
|
|
113
|
+
...segmentationMultiIndicesDataErrors,
|
|
114
|
+
];
|
|
115
|
+
const isReady = useReady([
|
|
116
|
+
// Points
|
|
117
|
+
obsPointsDataStatus,
|
|
118
|
+
pointMultiIndicesDataStatus,
|
|
119
|
+
// Segmentations
|
|
120
|
+
obsSegmentationsDataStatus,
|
|
121
|
+
obsSegmentationsSetsDataStatus,
|
|
122
|
+
segmentationMultiFeatureSelectionStatus,
|
|
123
|
+
segmentationMultiIndicesDataStatus,
|
|
124
|
+
]);
|
|
36
125
|
// console.log("NG Subs Render orbit", spatialRotationX, spatialRotationY, spatialRotationOrbit);
|
|
37
126
|
const { classes } = useStyles();
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
127
|
+
const segmentationColorMapping = useMemoCustomComparison(() => {
|
|
128
|
+
// TODO: ultimately, segmentationColorMapping becomes cellColorMapping, and makes its way into the viewerState.
|
|
129
|
+
// It may make sense to merge the multiple useMemoCustomComparisons upstream of derivedViewerState into one.
|
|
130
|
+
// This would complicate the comparison function, but the multiple separate useMemos are not really necessary.
|
|
131
|
+
const result = {};
|
|
132
|
+
segmentationLayerScopes?.forEach((layerScope) => {
|
|
133
|
+
result[layerScope] = {};
|
|
134
|
+
segmentationChannelScopesByLayer?.[layerScope]?.forEach((channelScope) => {
|
|
135
|
+
const { obsSets: layerSets, obsIndex: layerIndex } = obsSegmentationsSetsData?.[layerScope]?.[channelScope] || {};
|
|
136
|
+
const { obsSetColor, obsColorEncoding, obsSetSelection, additionalObsSets, spatialChannelColor, } = segmentationChannelCoordination[0][layerScope][channelScope];
|
|
137
|
+
if (obsColorEncoding === 'spatialChannelColor') {
|
|
138
|
+
// All segments get the same static channel color
|
|
139
|
+
if (layerIndex && spatialChannelColor) {
|
|
140
|
+
const hex = rgbToHex(spatialChannelColor);
|
|
141
|
+
const ngCellColors = {};
|
|
142
|
+
if (obsSetSelection?.length > 0) {
|
|
143
|
+
// Only color the segments belonging to selected sets.
|
|
144
|
+
const mergedCellSets = mergeObsSets(layerSets, additionalObsSets);
|
|
145
|
+
const selectedIds = new Set();
|
|
146
|
+
obsSetSelection.forEach((setPath) => {
|
|
147
|
+
const rootNode = mergedCellSets?.tree?.find(n => n.name === setPath[0]);
|
|
148
|
+
const leafNode = setPath.length > 1
|
|
149
|
+
? rootNode?.children?.find(n => n.name === setPath[1])
|
|
150
|
+
: rootNode;
|
|
151
|
+
leafNode?.set?.forEach(([id]) => selectedIds.add(String(id)));
|
|
152
|
+
});
|
|
153
|
+
layerIndex.forEach((id) => {
|
|
154
|
+
if (selectedIds.has(String(id))) {
|
|
155
|
+
ngCellColors[id] = hex;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
result[layerScope][channelScope] = ngCellColors;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else if (layerSets && layerIndex) {
|
|
163
|
+
const mergedCellSets = mergeObsSets(layerSets, additionalObsSets);
|
|
164
|
+
const cellColors = getCellColors({
|
|
165
|
+
cellSets: mergedCellSets,
|
|
166
|
+
cellSetSelection: obsSetSelection,
|
|
167
|
+
cellSetColor: obsSetColor,
|
|
168
|
+
obsIndex: layerIndex,
|
|
169
|
+
theme,
|
|
170
|
+
});
|
|
171
|
+
// Convert the list of colors to an object of hex strings, which NG requires.
|
|
172
|
+
const ngCellColors = {};
|
|
173
|
+
cellColors.forEach((color, i) => {
|
|
174
|
+
ngCellColors[i] = rgbToHex(color);
|
|
175
|
+
});
|
|
176
|
+
result[layerScope][channelScope] = ngCellColors;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
return result;
|
|
181
|
+
}, {
|
|
182
|
+
// The dependencies for the comparison,
|
|
183
|
+
// used by the custom equality function.
|
|
184
|
+
segmentationLayerScopes,
|
|
185
|
+
segmentationChannelScopesByLayer,
|
|
186
|
+
obsSegmentationsSetsData,
|
|
187
|
+
segmentationChannelCoordination,
|
|
188
|
+
theme,
|
|
189
|
+
}, customIsEqualForCellColors);
|
|
190
|
+
// Obtain the Neuroglancer viewerState object.
|
|
191
|
+
const initalViewerState = useNeuroglancerViewerState(theme, segmentationLayerScopes, segmentationChannelScopesByLayer, segmentationLayerCoordination, segmentationChannelCoordination, obsSegmentationsUrls, obsSegmentationsData, pointLayerScopes, pointLayerCoordination, obsPointsUrls, obsPointsData, pointMultiIndicesData);
|
|
192
|
+
const [latestViewerStateIteration, incrementLatestViewerStateIteration] = useReducer(x => x + 1, 0);
|
|
193
|
+
const latestViewerStateRef = useRef({
|
|
194
|
+
...initalViewerState,
|
|
195
|
+
...(initialNgCameraState ?? {}),
|
|
196
|
+
});
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
const prevNgCameraState = {
|
|
199
|
+
position: latestViewerStateRef.current.position,
|
|
200
|
+
projectionOrientation: latestViewerStateRef.current.projectionOrientation,
|
|
201
|
+
projectionScale: latestViewerStateRef.current.projectionScale,
|
|
202
|
+
};
|
|
203
|
+
latestViewerStateRef.current = {
|
|
204
|
+
...initalViewerState,
|
|
205
|
+
...prevNgCameraState,
|
|
206
|
+
};
|
|
207
|
+
// Force a re-render by incrementing a piece of state.
|
|
208
|
+
// This works because we have made latestViewerStateIteration
|
|
209
|
+
// a dependency for derivedViewerState, triggering the useMemo downstream.
|
|
210
|
+
incrementLatestViewerStateIteration();
|
|
211
|
+
}, [initalViewerState]);
|
|
42
212
|
const initialRotationPushedRef = useRef(false);
|
|
43
213
|
const ngRotPushAtRef = useRef(0);
|
|
44
214
|
const lastInteractionSource = useRef(null);
|
|
@@ -67,15 +237,6 @@ export function NeuroglancerSubscriber(props) {
|
|
|
67
237
|
tx: spatialTargetX,
|
|
68
238
|
ty: spatialTargetY,
|
|
69
239
|
});
|
|
70
|
-
const mergedCellSets = useMemo(() => mergeObsSets(cellSets, additionalCellSets), [cellSets, additionalCellSets]);
|
|
71
|
-
const cellColors = useMemo(() => getCellColors({
|
|
72
|
-
cellSets: mergedCellSets,
|
|
73
|
-
cellSetSelection,
|
|
74
|
-
cellSetColor,
|
|
75
|
-
obsIndex,
|
|
76
|
-
theme,
|
|
77
|
-
}), [mergedCellSets, theme,
|
|
78
|
-
cellSetColor, cellSetSelection, obsIndex]);
|
|
79
240
|
/*
|
|
80
241
|
* handleStateUpdate - Interactions from NG to Vitessce are pushed here
|
|
81
242
|
*/
|
|
@@ -182,6 +343,8 @@ export function NeuroglancerSubscriber(props) {
|
|
|
182
343
|
};
|
|
183
344
|
}, []);
|
|
184
345
|
const onSegmentClick = useCallback((value) => {
|
|
346
|
+
// Note: this callback is no longer called by the child component.
|
|
347
|
+
// Reference: https://github.com/vitessce/vitessce/pull/2439
|
|
185
348
|
if (value) {
|
|
186
349
|
const id = String(value);
|
|
187
350
|
const selectedCellIds = [id];
|
|
@@ -190,38 +353,34 @@ export function NeuroglancerSubscriber(props) {
|
|
|
190
353
|
if (alreadySelectedId) {
|
|
191
354
|
return;
|
|
192
355
|
}
|
|
356
|
+
// TODO: update this now that we are using layer/channel-based organization of segmentations.
|
|
357
|
+
// There is no more "top-level" obsSets coordination; it is only on a per-layer basis.
|
|
358
|
+
// We should probably just assume the first segmentation layer/channel when updating the logic,
|
|
359
|
+
// since it is not clear how we would determine which layer/channel to update if there are multiple.
|
|
193
360
|
setObsSelection(selectedCellIds, additionalCellSets, cellSetColor, setCellSetSelection, setAdditionalCellSets, setCellSetColor, setCellColorEncoding, 'Selection ', `: based on selected segments ${value}`);
|
|
194
361
|
}
|
|
195
362
|
}, [additionalCellSets, cellSetColor, setAdditionalCellSets,
|
|
196
363
|
setCellColorEncoding, setCellSetColor, setCellSetSelection,
|
|
197
364
|
]);
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
batchedUpdateTimeoutRef.current = setTimeout(() => {
|
|
205
|
-
setBatchedCellColors(cellColors);
|
|
206
|
-
}, 100);
|
|
207
|
-
// TODO: look into deferredValue from React
|
|
208
|
-
// startTransition(() => {
|
|
209
|
-
// setBatchedCellColors(cellColors);
|
|
210
|
-
// });
|
|
211
|
-
}, [cellColors]);
|
|
212
|
-
// TODO use a ref if slow - see prev commits
|
|
213
|
-
const cellColorMapping = useMemo(() => {
|
|
214
|
-
const colorMapping = {};
|
|
215
|
-
batchedCellColors.forEach((color, cell) => {
|
|
216
|
-
colorMapping[cell] = rgbToHex(color);
|
|
365
|
+
// Get the ultimate cellColorMapping for each layer to pass to NeuroglancerComp as a prop.
|
|
366
|
+
const cellColorMappingByLayer = useMemo(() => {
|
|
367
|
+
const result = {};
|
|
368
|
+
segmentationLayerScopes?.forEach((layerScope) => {
|
|
369
|
+
const channelScope = segmentationChannelScopesByLayer?.[layerScope]?.[0];
|
|
370
|
+
result[layerScope] = segmentationColorMapping?.[layerScope]?.[channelScope] ?? {};
|
|
217
371
|
});
|
|
218
|
-
return
|
|
219
|
-
}, [
|
|
372
|
+
return result;
|
|
373
|
+
}, [segmentationColorMapping, segmentationLayerScopes, segmentationChannelScopesByLayer]);
|
|
374
|
+
// TODO: try to simplify using useMemoCustomComparison?
|
|
375
|
+
// This would allow us to refactor a lot of the checking-for-changes logic into a comparison function,
|
|
376
|
+
// simplify some of the manual bookkeeping like with prevCoordsRef and lastInteractionSource,
|
|
377
|
+
// and would allow us to potentially remove usage of some refs (e.g., latestViewerStateRef)
|
|
378
|
+
// by relying on the memoization to prevent unnecessary updates.
|
|
220
379
|
const derivedViewerState = useMemo(() => {
|
|
221
380
|
const { current } = latestViewerStateRef;
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
381
|
+
if (current.layers.length <= 0) {
|
|
382
|
+
return current;
|
|
383
|
+
}
|
|
225
384
|
const { projectionScale, projectionOrientation, position } = current;
|
|
226
385
|
// Did Vitessce coords change vs the *previous* render?
|
|
227
386
|
const rotChangedNow = !nearEq(spatialRotationX, prevCoordsRef.current.rx, ROTATION_EPS)
|
|
@@ -326,18 +485,22 @@ export function NeuroglancerSubscriber(props) {
|
|
|
326
485
|
nextOrientation = lastNgPushOrientationRef.current ?? projectionOrientation;
|
|
327
486
|
lastInteractionSource.current = null;
|
|
328
487
|
}
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
488
|
+
const updatedLayers = current?.layers?.map((layer, idx) => {
|
|
489
|
+
const layerScope = segmentationLayerScopes?.[idx];
|
|
490
|
+
const layerColorMapping = cellColorMappingByLayer?.[layerScope] ?? {};
|
|
491
|
+
const layerSegments = Object.keys(layerColorMapping);
|
|
492
|
+
return {
|
|
493
|
+
...layer,
|
|
494
|
+
segments: layerSegments,
|
|
495
|
+
segmentColors: layerColorMapping,
|
|
496
|
+
};
|
|
497
|
+
}) ?? [];
|
|
334
498
|
const updated = {
|
|
335
499
|
...current,
|
|
336
500
|
projectionScale: nextProjectionScale,
|
|
337
501
|
projectionOrientation: nextOrientation,
|
|
338
502
|
position: nextPosition,
|
|
339
|
-
layers:
|
|
340
|
-
|| [])] : current?.layers,
|
|
503
|
+
layers: updatedLayers,
|
|
341
504
|
};
|
|
342
505
|
latestViewerStateRef.current = updated;
|
|
343
506
|
prevCoordsRef.current = {
|
|
@@ -350,14 +513,17 @@ export function NeuroglancerSubscriber(props) {
|
|
|
350
513
|
ty: spatialTargetY,
|
|
351
514
|
};
|
|
352
515
|
return updated;
|
|
353
|
-
}, [
|
|
354
|
-
spatialRotationZ, spatialTargetX, spatialTargetY
|
|
516
|
+
}, [cellColorMappingByLayer, spatialZoom, spatialRotationX, spatialRotationY,
|
|
517
|
+
spatialRotationZ, spatialTargetX, spatialTargetY, initalViewerState,
|
|
518
|
+
latestViewerStateIteration]);
|
|
355
519
|
const onSegmentHighlight = useCallback((obsId) => {
|
|
356
520
|
setCellHighlight(String(obsId));
|
|
357
|
-
}, [
|
|
521
|
+
}, [setCellHighlight]);
|
|
358
522
|
// TODO: if all cells are deselected, a black view is shown, rather we want to show empty NG view?
|
|
359
523
|
// if (!cellColorMapping || Object.keys(cellColorMapping).length === 0) {
|
|
360
524
|
// return;
|
|
361
525
|
// }
|
|
362
|
-
|
|
526
|
+
const hasLayers = derivedViewerState?.layers?.length > 0;
|
|
527
|
+
// console.log(derivedViewerState);
|
|
528
|
+
return (_jsx(TitleInfo, { title: title, info: subtitle, helpText: helpText, isSpatial: true, theme: theme, closeButtonVisible: closeButtonVisible, downloadButtonVisible: downloadButtonVisible, removeGridComponent: removeGridComponent, isReady: isReady, errors: errors, withPadding: false, guideUrl: GUIDE_URL, children: hasLayers ? (_jsxs("div", { style: { position: 'relative', width: '100%', height: '100%' }, ref: containerRef, children: [_jsx("div", { style: { position: 'absolute', top: 0, right: 0, zIndex: 50 }, children: _jsx(MultiLegend, { theme: "dark", maxHeight: ngHeight, segmentationLayerScopes: segmentationLayerScopes, segmentationLayerCoordination: segmentationLayerCoordination, segmentationChannelScopesByLayer: segmentationChannelScopesByLayer, segmentationChannelCoordination: segmentationChannelCoordination }) }), _jsx(NeuroglancerComp, { classes: classes, onSegmentClick: onSegmentClick, onSelectHoveredCoords: onSegmentHighlight, viewerState: derivedViewerState, cellColorMapping: cellColorMappingByLayer, setViewerState: handleStateUpdate })] })) : null }));
|
|
363
529
|
}
|
|
@@ -77,7 +77,7 @@ export default class Neuroglancer {
|
|
|
77
77
|
disposers: any[];
|
|
78
78
|
prevColorOverrides: Set<any>;
|
|
79
79
|
overrideColorsById: any;
|
|
80
|
-
|
|
80
|
+
allKnownIdsByLayer: {};
|
|
81
81
|
minimalPoseSnapshot: () => {
|
|
82
82
|
position: any[];
|
|
83
83
|
projectionScale: any;
|
|
@@ -86,7 +86,7 @@ export default class Neuroglancer {
|
|
|
86
86
|
scheduleEmit: () => () => void;
|
|
87
87
|
withoutEmitting: (fn: any) => void;
|
|
88
88
|
didLayersChange: (prevVS: any, nextVS: any) => boolean;
|
|
89
|
-
applyColorsAndVisibility: (
|
|
89
|
+
applyColorsAndVisibility: (cellColorMappingByLayer: any) => void;
|
|
90
90
|
componentDidMount(): void;
|
|
91
91
|
componentDidUpdate(prevProps: any, prevState: any): void;
|
|
92
92
|
componentWillUnmount(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReactNeuroglancer.d.ts","sourceRoot":"","sources":["../src/ReactNeuroglancer.js"],"names":[],"mappings":"AAiCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAGH,4CAsBC;AAED,0DAGC;AAED,gEA2BC;AAED,kDAKC;AAED,+CAMC;AAED,0DAMC;AAED,6DAMC;AAED,8DAWC;AAED,+EAYC;AAED,yFAcC;AAED,+EA6BC;AAmFD,2FAwBC;AAED,kGAeC;AAED,gFAUC;AAED,uEA0BC;AAED,0CAA0C;AAC1C;IACE;;;;;;;;;;;;MAYE;IAEF,wBAYC;IAVC,iBAAoC;IACpC,YAAkB;IAElB,2BAA8B;IAC9B,yBAA+B;IAC/B,kBAAwB;IACxB,iBAAmB;IACnB,6BAAmC;IACnC,wBAA6C;IAC7C,
|
|
1
|
+
{"version":3,"file":"ReactNeuroglancer.d.ts","sourceRoot":"","sources":["../src/ReactNeuroglancer.js"],"names":[],"mappings":"AAiCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAGH,4CAsBC;AAED,0DAGC;AAED,gEA2BC;AAED,kDAKC;AAED,+CAMC;AAED,0DAMC;AAED,6DAMC;AAED,8DAWC;AAED,+EAYC;AAED,yFAcC;AAED,+EA6BC;AAmFD,2FAwBC;AAED,kGAeC;AAED,gFAUC;AAED,uEA0BC;AAED,0CAA0C;AAC1C;IACE;;;;;;;;;;;;MAYE;IAEF,wBAYC;IAVC,iBAAoC;IACpC,YAAkB;IAElB,2BAA8B;IAC9B,yBAA+B;IAC/B,kBAAwB;IACxB,iBAAmB;IACnB,6BAAmC;IACnC,wBAA6C;IAC7C,uBAA4B;IAG9B;;;;MASE;IAGF,+BAWE;IAGF,mCAKE;IAGF,uDASE;IAGF,iEAsCE;IAEF,0BA+GC;IAED,yDAiIC;IAED,6BAUC;IAcD,mCAQC;IAED,0DAyCE;IAEF,yCAOE;IAEF,0BAsCE;IA5BI,mCAAyB;IA+B/B,sCAWE;IAEF,qCAUE;IAEF,sBAOC;CACF;;qBAv3Ba,MAAM;iBACN,MAAM;uBACN,MAAM;SACN,MAAM;sBACN;YAAO,MAAM,GAAE,MAAM;KAAC;;;;;;;;;;;;;;;;;;;uBAWtB,CAAC,OAAO,EAAC,GAAG,GAAC,IAAI,EAAE,KAAK,EAAC,GAAG,KAAK,IAAI;;;;;;;;;sBAQrC,CAAC,QAAQ,EAAC,GAAG,EAAE,KAAK,EAAC,GAAG,KAAK,IAAI;;;;oCAQjC,MAAM,IAAI;0BAEV,MAAM,IAAI;;;;eAEV,KAAK,CAAC,MAAM,CAAC"}
|
|
@@ -373,7 +373,7 @@ export default class Neuroglancer extends React.Component {
|
|
|
373
373
|
this.disposers = [];
|
|
374
374
|
this.prevColorOverrides = new Set();
|
|
375
375
|
this.overrideColorsById = Object.create(null);
|
|
376
|
-
this.
|
|
376
|
+
this.allKnownIdsByLayer = {};
|
|
377
377
|
}
|
|
378
378
|
minimalPoseSnapshot = () => {
|
|
379
379
|
const v = this.viewer;
|
|
@@ -423,31 +423,33 @@ export default class Neuroglancer extends React.Component {
|
|
|
423
423
|
return JSON.stringify(prevLayers) !== JSON.stringify(nextLayers);
|
|
424
424
|
};
|
|
425
425
|
/* To add colors to the segments, turning unselected to grey */
|
|
426
|
-
applyColorsAndVisibility = (
|
|
426
|
+
applyColorsAndVisibility = (cellColorMappingByLayer) => {
|
|
427
427
|
if (!this.viewer)
|
|
428
428
|
return;
|
|
429
|
-
//
|
|
430
|
-
// that drop out of the current selection.
|
|
431
|
-
const selected = { ...(cellColorMapping || {}) }; // clone, don't mutate props
|
|
432
|
-
for (const id of Object.keys(selected))
|
|
433
|
-
this.allKnownIds.add(id);
|
|
434
|
-
// If empty on first call, seed from initial segmentColors (if present)
|
|
435
|
-
if (this.allKnownIds.size === 0) {
|
|
436
|
-
const init = this.props.viewerState?.layers?.[0]?.segmentColors || {};
|
|
437
|
-
for (const id of Object.keys(init))
|
|
438
|
-
this.allKnownIds.add(id);
|
|
439
|
-
}
|
|
440
|
-
// Build a full color table: selected keep their hex, others grey
|
|
441
|
-
const fullSegmentColors = {};
|
|
442
|
-
for (const id of this.allKnownIds) {
|
|
443
|
-
fullSegmentColors[id] = selected[id] || GREY_HEX;
|
|
444
|
-
}
|
|
445
|
-
// Patch layers with the new segmentColors (pose untouched)
|
|
429
|
+
// Build full color table per layer
|
|
446
430
|
const baseLayers = (this.props.viewerState?.layers)
|
|
447
431
|
?? (this.viewer.state.toJSON().layers || []);
|
|
448
|
-
const newLayers = baseLayers.map((layer
|
|
449
|
-
// if
|
|
450
|
-
|
|
432
|
+
const newLayers = baseLayers.map((layer) => {
|
|
433
|
+
// Match layerScope by checking if the NG layer name contains the scope key.
|
|
434
|
+
// NG layer names are of the form:
|
|
435
|
+
// "obsSegmentations-init_A_obsSegmentations_0-init_A_obsSegmentations_0"
|
|
436
|
+
const layerScope = Object.keys(cellColorMappingByLayer).find(scope => layer.name?.includes(scope));
|
|
437
|
+
const selected = { ...(cellColorMappingByLayer[layerScope] || {}) };
|
|
438
|
+
// Track all known IDs for this layer scope
|
|
439
|
+
if (!this.allKnownIdsByLayer)
|
|
440
|
+
this.allKnownIdsByLayer = {};
|
|
441
|
+
if (!this.allKnownIdsByLayer[layerScope]) {
|
|
442
|
+
this.allKnownIdsByLayer[layerScope] = new Set();
|
|
443
|
+
}
|
|
444
|
+
for (const id of Object.keys(selected)) {
|
|
445
|
+
this.allKnownIdsByLayer[layerScope].add(id);
|
|
446
|
+
}
|
|
447
|
+
// Build a full color table: selected keep their hex, others grey
|
|
448
|
+
const fullSegmentColors = {};
|
|
449
|
+
for (const id of this.allKnownIdsByLayer[layerScope] || []) {
|
|
450
|
+
fullSegmentColors[id] = selected[id] || GREY_HEX;
|
|
451
|
+
}
|
|
452
|
+
if (layer.type === 'segmentation') {
|
|
451
453
|
return { ...layer, segmentColors: fullSegmentColors };
|
|
452
454
|
}
|
|
453
455
|
return layer;
|
|
@@ -556,7 +558,7 @@ export default class Neuroglancer extends React.Component {
|
|
|
556
558
|
// window.viewer = this.viewer;
|
|
557
559
|
}
|
|
558
560
|
componentDidUpdate(prevProps, prevState) {
|
|
559
|
-
const { viewerState, cellColorMapping } = this.props;
|
|
561
|
+
const { viewerState, cellColorMapping: cellColorMappingByLayer } = this.props;
|
|
560
562
|
// The restoreState() call clears the 'selected' (hovered on) segment, which is needed
|
|
561
563
|
// by Neuroglancer's code to toggle segment visibilty on a mouse click. To free the user
|
|
562
564
|
// from having to move the mouse before clicking, save the selected segment and restore
|
|
@@ -633,8 +635,8 @@ export default class Neuroglancer extends React.Component {
|
|
|
633
635
|
this.withoutEmitting(() => {
|
|
634
636
|
const layers = Array.isArray(viewerState.layers) ? viewerState.layers : [];
|
|
635
637
|
this.viewer.state.restoreState({ layers });
|
|
636
|
-
if (
|
|
637
|
-
this.applyColorsAndVisibility(
|
|
638
|
+
if (cellColorMappingByLayer && Object.keys(cellColorMappingByLayer).length) {
|
|
639
|
+
this.applyColorsAndVisibility(cellColorMappingByLayer);
|
|
638
640
|
}
|
|
639
641
|
});
|
|
640
642
|
}
|
|
@@ -642,12 +644,13 @@ export default class Neuroglancer extends React.Component {
|
|
|
642
644
|
// this was to avid NG randomly assigning colors to the segments by resetting them
|
|
643
645
|
const prevSize = prevProps.cellColorMapping
|
|
644
646
|
? Object.keys(prevProps.cellColorMapping).length : 0;
|
|
645
|
-
const currSize =
|
|
646
|
-
|
|
647
|
+
const currSize = cellColorMappingByLayer
|
|
648
|
+
? Object.keys(cellColorMappingByLayer).length : 0;
|
|
649
|
+
const mappingRefChanged = prevProps.cellColorMapping !== this.props.cellColorMapping;
|
|
647
650
|
if (!this.didLayersChange(prevVS, viewerState)
|
|
648
651
|
&& (mappingRefChanged || prevSize !== currSize)) {
|
|
649
652
|
this.withoutEmitting(() => {
|
|
650
|
-
this.applyColorsAndVisibility(
|
|
653
|
+
this.applyColorsAndVisibility(cellColorMappingByLayer);
|
|
651
654
|
});
|
|
652
655
|
}
|
|
653
656
|
// Treat "real" layer source/type changes differently from segment list changes.
|