@vitessce/neuroglancer 4.0.0-test.0 → 4.0.0-test.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.
- package/dist/{ReactNeuroglancer-Bg6f79hO.js → ReactNeuroglancer-UvsFo0UR.js} +88 -26
- package/dist/index-DslldtW7.js +35185 -0
- package/dist/index.js +1 -1
- package/dist-tsc/Neuroglancer.d.ts.map +1 -1
- package/dist-tsc/Neuroglancer.js +28 -6
- package/dist-tsc/NeuroglancerSubscriber.d.ts.map +1 -1
- package/dist-tsc/NeuroglancerSubscriber.js +67 -27
- package/dist-tsc/ReactNeuroglancer.d.ts +13 -2
- package/dist-tsc/ReactNeuroglancer.d.ts.map +1 -1
- package/dist-tsc/ReactNeuroglancer.js +89 -31
- package/dist-tsc/data-hook-ng-utils.d.ts +1 -1
- package/dist-tsc/data-hook-ng-utils.d.ts.map +1 -1
- package/dist-tsc/data-hook-ng-utils.js +18 -4
- package/dist-tsc/shader-utils.d.ts +12 -12
- package/dist-tsc/shader-utils.d.ts.map +1 -1
- package/dist-tsc/shader-utils.js +51 -26
- package/dist-tsc/shader-utils.test.js +20 -0
- package/dist-tsc/use-memo-custom-comparison.d.ts.map +1 -1
- package/dist-tsc/use-memo-custom-comparison.js +6 -0
- package/package.json +9 -9
- package/src/Neuroglancer.js +30 -5
- package/src/NeuroglancerSubscriber.js +97 -49
- package/src/ReactNeuroglancer.js +99 -30
- package/src/data-hook-ng-utils.js +21 -2
- package/src/shader-utils.js +79 -26
- package/src/shader-utils.test.js +20 -0
- package/src/use-memo-custom-comparison.js +7 -0
- package/dist/index-DZhbMDug.js +0 -35064
package/dist/index.js
CHANGED
|
@@ -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,cAqBC;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) {
|
|
@@ -28,11 +30,31 @@ export class NeuroglancerComp extends PureComponent {
|
|
|
28
30
|
// Mount
|
|
29
31
|
const { viewer } = viewerRef;
|
|
30
32
|
this.prevMouseStateChanged = viewer.mouseState.changed;
|
|
31
|
-
|
|
33
|
+
// For now, can omit the sliceView bindings, as we only use perspectiveView
|
|
34
|
+
// viewer.inputEventBindings.sliceView.set('at:dblclick0', () => {});
|
|
32
35
|
viewer.inputEventBindings.perspectiveView.set('at:dblclick0', () => { });
|
|
33
|
-
//
|
|
36
|
+
// Disable space interaction to prevent triggering 4panels layout.
|
|
34
37
|
viewer.inputEventBindings.sliceView.set('at:space', () => { });
|
|
35
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);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (map.parents) {
|
|
54
|
+
map.parents.forEach(p => remapWheelToZoom(p));
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
remapWheelToZoom(viewer.inputEventBindings.perspectiveView);
|
|
36
58
|
this.prevHoverHandler = () => {
|
|
37
59
|
if (viewer.mouseState.pickedValue !== undefined) {
|
|
38
60
|
const pickedSegment = viewer.mouseState.pickedValue;
|
|
@@ -60,7 +82,7 @@ export class NeuroglancerComp extends PureComponent {
|
|
|
60
82
|
}
|
|
61
83
|
}
|
|
62
84
|
render() {
|
|
63
|
-
const { classes, viewerState, cellColorMapping } = this.props;
|
|
64
|
-
return (_jsxs(_Fragment, { children: [_jsx(NeuroglancerGlobalStyles, { classes: classes }), _jsx("div", { className: classes.neuroglancerWrapper, children: _jsx(Suspense, { fallback: _jsx("div", { children: "Loading..." }), children: _jsx(LazyReactNeuroglancer, { brainMapsClientId: "NOT_A_VALID_ID", viewerState: viewerState, onViewerStateChanged: this.onViewerStateChanged, bundleRoot: this.bundleRoot, cellColorMapping: cellColorMapping, ref: this.onRef }) }) })] }));
|
|
85
|
+
const { classes, viewerState, cellColorMapping, onLayerLoadingChange } = this.props;
|
|
86
|
+
return (_jsxs(_Fragment, { children: [_jsx(NeuroglancerGlobalStyles, { classes: classes }), _jsx("div", { className: classes.neuroglancerWrapper, children: _jsx(Suspense, { fallback: _jsx("div", { children: "Loading..." }), children: _jsx(LazyReactNeuroglancer, { brainMapsClientId: "NOT_A_VALID_ID", viewerState: viewerState, onViewerStateChanged: this.onViewerStateChanged, onLayerLoadingChange: onLayerLoadingChange, bundleRoot: this.bundleRoot, cellColorMapping: cellColorMapping, ref: this.onRef }) }) })] }));
|
|
65
87
|
}
|
|
66
88
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NeuroglancerSubscriber.d.ts","sourceRoot":"","sources":["../src/NeuroglancerSubscriber.js"],"names":[],"mappings":"AAwEA,
|
|
1
|
+
{"version":3,"file":"NeuroglancerSubscriber.d.ts","sourceRoot":"","sources":["../src/NeuroglancerSubscriber.js"],"names":[],"mappings":"AAwEA,wDA0vBC"}
|
|
@@ -28,7 +28,7 @@ function rgbToHex(rgb) {
|
|
|
28
28
|
: `#${rgb.map(c => c.toString(16).padStart(2, '0')).join('')}`);
|
|
29
29
|
}
|
|
30
30
|
export function NeuroglancerSubscriber(props) {
|
|
31
|
-
const { uuid, coordinationScopes: coordinationScopesRaw, coordinationScopesBy: coordinationScopesByRaw, closeButtonVisible, downloadButtonVisible, removeGridComponent, theme, title = 'Spatial', subtitle = 'Powered by Neuroglancer', helpText = ViewHelpMapping.NEUROGLANCER,
|
|
31
|
+
const { uuid, coordinationScopes: coordinationScopesRaw, coordinationScopesBy: coordinationScopesByRaw, closeButtonVisible, downloadButtonVisible, removeGridComponent, theme, showAxisLines = false, title = 'Spatial', subtitle = 'Powered by Neuroglancer', helpText = ViewHelpMapping.NEUROGLANCER,
|
|
32
32
|
// Note: this is a temporary mechanism
|
|
33
33
|
// to pass an initial NG camera state.
|
|
34
34
|
// Ideally, all camera state should be passed via
|
|
@@ -95,6 +95,7 @@ export function NeuroglancerSubscriber(props) {
|
|
|
95
95
|
CoordinationType.TOOLTIPS_VISIBLE,
|
|
96
96
|
CoordinationType.TOOLTIP_CROSSHAIRS_VISIBLE,
|
|
97
97
|
CoordinationType.LEGEND_VISIBLE,
|
|
98
|
+
CoordinationType.SPATIAL_POINT_STROKE_WIDTH,
|
|
98
99
|
], coordinationScopes, coordinationScopesBy, CoordinationType.POINT_LAYER);
|
|
99
100
|
// Points data
|
|
100
101
|
const [obsPointsData, obsPointsDataStatus, obsPointsUrls, obsPointsErrors] = useMultiObsPoints(coordinationScopes, coordinationScopesBy, loaders, dataset, mergeCoordination, uuid);
|
|
@@ -133,8 +134,34 @@ export function NeuroglancerSubscriber(props) {
|
|
|
133
134
|
result[layerScope] = {};
|
|
134
135
|
segmentationChannelScopesByLayer?.[layerScope]?.forEach((channelScope) => {
|
|
135
136
|
const { obsSets: layerSets, obsIndex: layerIndex } = obsSegmentationsSetsData?.[layerScope]?.[channelScope] || {};
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
const { obsSetColor, obsColorEncoding, obsSetSelection, additionalObsSets, spatialChannelColor, spatialChannelOpacity, } = segmentationChannelCoordination[0][layerScope][channelScope];
|
|
138
|
+
if (obsColorEncoding === 'spatialChannelColor') {
|
|
139
|
+
// All segments get the same static channel color
|
|
140
|
+
if (layerIndex && spatialChannelColor) {
|
|
141
|
+
const hex = rgbToHex(spatialChannelColor);
|
|
142
|
+
const ngCellColors = {};
|
|
143
|
+
if (obsSetSelection?.length > 0) {
|
|
144
|
+
// Only color the segments belonging to selected sets.
|
|
145
|
+
const mergedCellSets = mergeObsSets(layerSets, additionalObsSets);
|
|
146
|
+
const selectedIds = new Set();
|
|
147
|
+
obsSetSelection.forEach((setPath) => {
|
|
148
|
+
const rootNode = mergedCellSets?.tree?.find(n => n.name === setPath[0]);
|
|
149
|
+
const leafNode = setPath.length > 1
|
|
150
|
+
? rootNode?.children?.find(n => n.name === setPath[1])
|
|
151
|
+
: rootNode;
|
|
152
|
+
leafNode?.set?.forEach(([id]) => selectedIds.add(String(id)));
|
|
153
|
+
});
|
|
154
|
+
layerIndex.forEach((id) => {
|
|
155
|
+
if (selectedIds.has(String(id))) {
|
|
156
|
+
ngCellColors[id] = hex;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
result[layerScope][channelScope] = ngCellColors;
|
|
161
|
+
result[layerScope].opacity = spatialChannelOpacity ?? 1.0;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else if (layerSets && layerIndex) {
|
|
138
165
|
const mergedCellSets = mergeObsSets(layerSets, additionalObsSets);
|
|
139
166
|
const cellColors = getCellColors({
|
|
140
167
|
cellSets: mergedCellSets,
|
|
@@ -148,14 +175,8 @@ export function NeuroglancerSubscriber(props) {
|
|
|
148
175
|
cellColors.forEach((color, i) => {
|
|
149
176
|
ngCellColors[i] = rgbToHex(color);
|
|
150
177
|
});
|
|
151
|
-
/* // TODO: Is this necessary?
|
|
152
|
-
const obsColorIndices = treeToCellSetColorIndicesBySetNames(
|
|
153
|
-
mergedLayerSets,
|
|
154
|
-
obsSetSelection,
|
|
155
|
-
obsSetColor,
|
|
156
|
-
);
|
|
157
|
-
*/
|
|
158
178
|
result[layerScope][channelScope] = ngCellColors;
|
|
179
|
+
result[layerScope].opacity = spatialChannelOpacity ?? 1.0;
|
|
159
180
|
}
|
|
160
181
|
});
|
|
161
182
|
});
|
|
@@ -170,7 +191,7 @@ export function NeuroglancerSubscriber(props) {
|
|
|
170
191
|
theme,
|
|
171
192
|
}, customIsEqualForCellColors);
|
|
172
193
|
// Obtain the Neuroglancer viewerState object.
|
|
173
|
-
const initalViewerState = useNeuroglancerViewerState(theme, segmentationLayerScopes, segmentationChannelScopesByLayer, segmentationLayerCoordination, segmentationChannelCoordination, obsSegmentationsUrls, obsSegmentationsData, pointLayerScopes, pointLayerCoordination, obsPointsUrls, obsPointsData, pointMultiIndicesData);
|
|
194
|
+
const initalViewerState = useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScopes, segmentationChannelScopesByLayer, segmentationLayerCoordination, segmentationChannelCoordination, obsSegmentationsUrls, obsSegmentationsData, pointLayerScopes, pointLayerCoordination, obsPointsUrls, obsPointsData, pointMultiIndicesData);
|
|
174
195
|
const [latestViewerStateIteration, incrementLatestViewerStateIteration] = useReducer(x => x + 1, 0);
|
|
175
196
|
const latestViewerStateRef = useRef({
|
|
176
197
|
...initalViewerState,
|
|
@@ -207,6 +228,8 @@ export function NeuroglancerSubscriber(props) {
|
|
|
207
228
|
z: spatialRotationZ,
|
|
208
229
|
orbit: spatialRotationOrbit,
|
|
209
230
|
});
|
|
231
|
+
// Track layer loading state for showing loading indicator
|
|
232
|
+
const [isLayersLoaded, setIsLayersLoaded] = useState(false);
|
|
210
233
|
// Track the last coord values we saw, and only mark "vitessce"
|
|
211
234
|
// when *those* actually change. This prevents cell set renders
|
|
212
235
|
// from spoofing the source.
|
|
@@ -344,10 +367,18 @@ export function NeuroglancerSubscriber(props) {
|
|
|
344
367
|
}, [additionalCellSets, cellSetColor, setAdditionalCellSets,
|
|
345
368
|
setCellColorEncoding, setCellSetColor, setCellSetSelection,
|
|
346
369
|
]);
|
|
347
|
-
// Get the ultimate cellColorMapping to pass to NeuroglancerComp as a prop.
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
370
|
+
// Get the ultimate cellColorMapping for each layer to pass to NeuroglancerComp as a prop.
|
|
371
|
+
const cellColorMappingByLayer = useMemo(() => {
|
|
372
|
+
const result = {};
|
|
373
|
+
segmentationLayerScopes?.forEach((layerScope) => {
|
|
374
|
+
const channelScope = segmentationChannelScopesByLayer?.[layerScope]?.[0];
|
|
375
|
+
result[layerScope] = {
|
|
376
|
+
colors: segmentationColorMapping?.[layerScope]?.[channelScope] ?? {},
|
|
377
|
+
opacity: segmentationColorMapping?.[layerScope]?.opacity ?? 1.0,
|
|
378
|
+
};
|
|
379
|
+
});
|
|
380
|
+
return result;
|
|
381
|
+
}, [segmentationColorMapping, segmentationLayerScopes, segmentationChannelScopesByLayer]);
|
|
351
382
|
// TODO: try to simplify using useMemoCustomComparison?
|
|
352
383
|
// This would allow us to refactor a lot of the checking-for-changes logic into a comparison function,
|
|
353
384
|
// simplify some of the manual bookkeeping like with prevCoordsRef and lastInteractionSource,
|
|
@@ -358,9 +389,6 @@ export function NeuroglancerSubscriber(props) {
|
|
|
358
389
|
if (current.layers.length <= 0) {
|
|
359
390
|
return current;
|
|
360
391
|
}
|
|
361
|
-
const nextSegments = Object.keys(cellColorMapping);
|
|
362
|
-
const prevLayer = current?.layers?.[0] || {};
|
|
363
|
-
const prevSegments = prevLayer.segments || [];
|
|
364
392
|
const { projectionScale, projectionOrientation, position } = current;
|
|
365
393
|
// Did Vitessce coords change vs the *previous* render?
|
|
366
394
|
const rotChangedNow = !nearEq(spatialRotationX, prevCoordsRef.current.rx, ROTATION_EPS)
|
|
@@ -465,18 +493,23 @@ export function NeuroglancerSubscriber(props) {
|
|
|
465
493
|
nextOrientation = lastNgPushOrientationRef.current ?? projectionOrientation;
|
|
466
494
|
lastInteractionSource.current = null;
|
|
467
495
|
}
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
496
|
+
const updatedLayers = current?.layers?.map((layer, idx) => {
|
|
497
|
+
const layerScope = segmentationLayerScopes?.[idx];
|
|
498
|
+
const layerColorMapping = cellColorMappingByLayer?.[layerScope]?.colors ?? {};
|
|
499
|
+
const layerSegments = Object.keys(layerColorMapping);
|
|
500
|
+
return {
|
|
501
|
+
...layer,
|
|
502
|
+
segments: layerSegments,
|
|
503
|
+
segmentColors: layerColorMapping,
|
|
504
|
+
objectAlpha: cellColorMappingByLayer?.[layerScope]?.opacity ?? 1.0,
|
|
505
|
+
};
|
|
506
|
+
}) ?? [];
|
|
473
507
|
const updated = {
|
|
474
508
|
...current,
|
|
475
509
|
projectionScale: nextProjectionScale,
|
|
476
510
|
projectionOrientation: nextOrientation,
|
|
477
511
|
position: nextPosition,
|
|
478
|
-
layers:
|
|
479
|
-
|| [])] : current?.layers,
|
|
512
|
+
layers: updatedLayers,
|
|
480
513
|
};
|
|
481
514
|
latestViewerStateRef.current = updated;
|
|
482
515
|
prevCoordsRef.current = {
|
|
@@ -489,17 +522,24 @@ export function NeuroglancerSubscriber(props) {
|
|
|
489
522
|
ty: spatialTargetY,
|
|
490
523
|
};
|
|
491
524
|
return updated;
|
|
492
|
-
}, [
|
|
525
|
+
}, [cellColorMappingByLayer, spatialZoom, spatialRotationX, spatialRotationY,
|
|
493
526
|
spatialRotationZ, spatialTargetX, spatialTargetY, initalViewerState,
|
|
494
527
|
latestViewerStateIteration]);
|
|
495
528
|
const onSegmentHighlight = useCallback((obsId) => {
|
|
496
529
|
setCellHighlight(String(obsId));
|
|
497
530
|
}, [setCellHighlight]);
|
|
531
|
+
const handleLayerLoadingChange = useCallback((isLoaded) => {
|
|
532
|
+
setIsLayersLoaded(isLoaded);
|
|
533
|
+
}, []);
|
|
498
534
|
// TODO: if all cells are deselected, a black view is shown, rather we want to show empty NG view?
|
|
499
535
|
// if (!cellColorMapping || Object.keys(cellColorMapping).length === 0) {
|
|
500
536
|
// return;
|
|
501
537
|
// }
|
|
502
538
|
const hasLayers = derivedViewerState?.layers?.length > 0;
|
|
503
539
|
// console.log(derivedViewerState);
|
|
504
|
-
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: _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,
|
|
540
|
+
return (_jsx(TitleInfo, { title: title, info: subtitle, helpText: helpText, isSpatial: true, theme: theme, closeButtonVisible: closeButtonVisible, downloadButtonVisible: downloadButtonVisible, removeGridComponent: removeGridComponent, isReady: isReady && isLayersLoaded, 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,
|
|
541
|
+
// Segmentations
|
|
542
|
+
segmentationLayerScopes: segmentationLayerScopes, segmentationLayerCoordination: segmentationLayerCoordination, segmentationChannelScopesByLayer: segmentationChannelScopesByLayer, segmentationChannelCoordination: segmentationChannelCoordination,
|
|
543
|
+
// Points
|
|
544
|
+
pointLayerScopes: pointLayerScopes, pointLayerCoordination: pointLayerCoordination, pointMultiIndicesData: pointMultiIndicesData }) }), _jsx(NeuroglancerComp, { classes: classes, onSegmentClick: onSegmentClick, onSelectHoveredCoords: onSegmentHighlight, viewerState: derivedViewerState, cellColorMapping: cellColorMappingByLayer, setViewerState: handleStateUpdate, onLayerLoadingChange: handleLayerLoadingChange })] })) : null }));
|
|
505
545
|
}
|
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
* @property {() => void} onSelectionDetailsStateChanged
|
|
35
35
|
* A function of the form `() => {}` to respond to selection changes in the viewer.
|
|
36
36
|
* @property {() => void} onViewerStateChanged
|
|
37
|
+
* @property {(isLoaded: boolean) => void} onLayerLoadingChange
|
|
38
|
+
* A function of the form `(isLoaded) => {}`, called when layer loading state changes.
|
|
39
|
+
* The `isLoaded` argument will be `true` when all segmentation layers have finished loading
|
|
40
|
+
* their data sources, or `false` when layers are still loading.
|
|
37
41
|
*
|
|
38
42
|
* @property {Array<Object>} callbacks
|
|
39
43
|
* // ngServer: string,
|
|
@@ -64,6 +68,7 @@ export default class Neuroglancer {
|
|
|
64
68
|
onVisibleChanged: null;
|
|
65
69
|
onSelectionDetailsStateChanged: null;
|
|
66
70
|
onViewerStateChanged: null;
|
|
71
|
+
onLayerLoadingChange: null;
|
|
67
72
|
key: null;
|
|
68
73
|
callbacks: never[];
|
|
69
74
|
ngServer: string;
|
|
@@ -77,7 +82,7 @@ export default class Neuroglancer {
|
|
|
77
82
|
disposers: any[];
|
|
78
83
|
prevColorOverrides: Set<any>;
|
|
79
84
|
overrideColorsById: any;
|
|
80
|
-
|
|
85
|
+
allKnownIdsByLayer: {};
|
|
81
86
|
minimalPoseSnapshot: () => {
|
|
82
87
|
position: any[];
|
|
83
88
|
projectionScale: any;
|
|
@@ -86,7 +91,7 @@ export default class Neuroglancer {
|
|
|
86
91
|
scheduleEmit: () => () => void;
|
|
87
92
|
withoutEmitting: (fn: any) => void;
|
|
88
93
|
didLayersChange: (prevVS: any, nextVS: any) => boolean;
|
|
89
|
-
applyColorsAndVisibility: (
|
|
94
|
+
applyColorsAndVisibility: (cellColorMappingByLayer: any) => void;
|
|
90
95
|
componentDidMount(): void;
|
|
91
96
|
componentDidUpdate(prevProps: any, prevState: any): void;
|
|
92
97
|
componentWillUnmount(): void;
|
|
@@ -140,6 +145,12 @@ export type NgProps = {
|
|
|
140
145
|
*/
|
|
141
146
|
onSelectionDetailsStateChanged: () => void;
|
|
142
147
|
onViewerStateChanged: () => void;
|
|
148
|
+
/**
|
|
149
|
+
* A function of the form `(isLoaded) => {}`, called when layer loading state changes.
|
|
150
|
+
* The `isLoaded` argument will be `true` when all segmentation layers have finished loading
|
|
151
|
+
* their data sources, or `false` when layers are still loading.
|
|
152
|
+
*/
|
|
153
|
+
onLayerLoadingChange: (isLoaded: boolean) => void;
|
|
143
154
|
/**
|
|
144
155
|
* // ngServer: string,
|
|
145
156
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReactNeuroglancer.d.ts","sourceRoot":"","sources":["../src/ReactNeuroglancer.js"],"names":[],"mappings":"AAiCA
|
|
1
|
+
{"version":3,"file":"ReactNeuroglancer.d.ts","sourceRoot":"","sources":["../src/ReactNeuroglancer.js"],"names":[],"mappings":"AAiCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;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;;;;;;;;;;;;;MAaE;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,0BAiJC;IAED,yDAwJC;IAED,6BAUC;IAcD,mCAQC;IAED,0DAyCE;IAEF,yCAOE;IAEF,0BAsCE;IA5BI,mCAAyB;IA+B/B,sCAWE;IAEF,qCAUE;IAEF,cAOC;CACF;;qBAr7Ba,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;;;;;;0BACV,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI;;;;eAK3B,KAAK,CAAC,MAAM,CAAC"}
|
|
@@ -63,6 +63,10 @@ let viewerNoKey;
|
|
|
63
63
|
* @property {() => void} onSelectionDetailsStateChanged
|
|
64
64
|
* A function of the form `() => {}` to respond to selection changes in the viewer.
|
|
65
65
|
* @property {() => void} onViewerStateChanged
|
|
66
|
+
* @property {(isLoaded: boolean) => void} onLayerLoadingChange
|
|
67
|
+
* A function of the form `(isLoaded) => {}`, called when layer loading state changes.
|
|
68
|
+
* The `isLoaded` argument will be `true` when all segmentation layers have finished loading
|
|
69
|
+
* their data sources, or `false` when layers are still loading.
|
|
66
70
|
*
|
|
67
71
|
* @property {Array<Object>} callbacks
|
|
68
72
|
* // ngServer: string,
|
|
@@ -358,6 +362,7 @@ export default class Neuroglancer extends React.Component {
|
|
|
358
362
|
onVisibleChanged: null,
|
|
359
363
|
onSelectionDetailsStateChanged: null,
|
|
360
364
|
onViewerStateChanged: null,
|
|
365
|
+
onLayerLoadingChange: null,
|
|
361
366
|
key: null,
|
|
362
367
|
callbacks: [],
|
|
363
368
|
ngServer: 'https://neuroglancer-demo.appspot.com/',
|
|
@@ -373,7 +378,7 @@ export default class Neuroglancer extends React.Component {
|
|
|
373
378
|
this.disposers = [];
|
|
374
379
|
this.prevColorOverrides = new Set();
|
|
375
380
|
this.overrideColorsById = Object.create(null);
|
|
376
|
-
this.
|
|
381
|
+
this.allKnownIdsByLayer = {};
|
|
377
382
|
}
|
|
378
383
|
minimalPoseSnapshot = () => {
|
|
379
384
|
const v = this.viewer;
|
|
@@ -423,31 +428,33 @@ export default class Neuroglancer extends React.Component {
|
|
|
423
428
|
return JSON.stringify(prevLayers) !== JSON.stringify(nextLayers);
|
|
424
429
|
};
|
|
425
430
|
/* To add colors to the segments, turning unselected to grey */
|
|
426
|
-
applyColorsAndVisibility = (
|
|
431
|
+
applyColorsAndVisibility = (cellColorMappingByLayer) => {
|
|
427
432
|
if (!this.viewer)
|
|
428
433
|
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)
|
|
434
|
+
// Build full color table per layer
|
|
446
435
|
const baseLayers = (this.props.viewerState?.layers)
|
|
447
436
|
?? (this.viewer.state.toJSON().layers || []);
|
|
448
|
-
const newLayers = baseLayers.map((layer
|
|
449
|
-
// if
|
|
450
|
-
|
|
437
|
+
const newLayers = baseLayers.map((layer) => {
|
|
438
|
+
// Match layerScope by checking if the NG layer name contains the scope key.
|
|
439
|
+
// NG layer names are of the form:
|
|
440
|
+
// "obsSegmentations-init_A_obsSegmentations_0-init_A_obsSegmentations_0"
|
|
441
|
+
const layerScope = Object.keys(cellColorMappingByLayer).find(scope => layer.name?.includes(scope));
|
|
442
|
+
const selected = { ...(cellColorMappingByLayer[layerScope]?.colors || {}) };
|
|
443
|
+
// Track all known IDs for this layer scope
|
|
444
|
+
if (!this.allKnownIdsByLayer)
|
|
445
|
+
this.allKnownIdsByLayer = {};
|
|
446
|
+
if (!this.allKnownIdsByLayer[layerScope]) {
|
|
447
|
+
this.allKnownIdsByLayer[layerScope] = new Set();
|
|
448
|
+
}
|
|
449
|
+
for (const id of Object.keys(selected)) {
|
|
450
|
+
this.allKnownIdsByLayer[layerScope].add(id);
|
|
451
|
+
}
|
|
452
|
+
// Build a full color table: selected keep their hex, others grey
|
|
453
|
+
const fullSegmentColors = {};
|
|
454
|
+
for (const id of this.allKnownIdsByLayer[layerScope] || []) {
|
|
455
|
+
fullSegmentColors[id] = selected[id] || GREY_HEX;
|
|
456
|
+
}
|
|
457
|
+
if (layer.type === 'segmentation') {
|
|
451
458
|
return { ...layer, segmentColors: fullSegmentColors };
|
|
452
459
|
}
|
|
453
460
|
return layer;
|
|
@@ -552,11 +559,42 @@ export default class Neuroglancer extends React.Component {
|
|
|
552
559
|
else {
|
|
553
560
|
viewerNoKey = this.viewer;
|
|
554
561
|
}
|
|
555
|
-
|
|
562
|
+
const { visibleChunksChanged } = this.viewer.chunkQueueManager;
|
|
563
|
+
let firstChunkLoaded = false;
|
|
564
|
+
this.disposers.push(visibleChunksChanged.add(() => {
|
|
565
|
+
if (!firstChunkLoaded) {
|
|
566
|
+
for (const layer of this.viewer.layerManager.managedLayers) {
|
|
567
|
+
if (layer.layer instanceof SegmentationUserLayer) {
|
|
568
|
+
const hasVisibleChunk = layer.layer.renderLayers?.some((rl) => {
|
|
569
|
+
const { numVisibleChunksAvailable, numVisibleChunksNeeded, } = rl.layerChunkProgressInfo || {};
|
|
570
|
+
if (!numVisibleChunksNeeded || !numVisibleChunksAvailable)
|
|
571
|
+
return false;
|
|
572
|
+
// Neuroglancer only shows chunks when a certain % is loaded.
|
|
573
|
+
// The 0.25 is from testing different values, can be reduced to 0.2 to shorten loader time
|
|
574
|
+
return (numVisibleChunksAvailable / numVisibleChunksNeeded) > 0.25;
|
|
575
|
+
});
|
|
576
|
+
if (hasVisibleChunk) {
|
|
577
|
+
firstChunkLoaded = true;
|
|
578
|
+
// Two frames to avoid flash while the following two happens
|
|
579
|
+
// Neuroglancer issues WebGL draw calls
|
|
580
|
+
requestAnimationFrame(() => {
|
|
581
|
+
// GPU has painted, pixels visible on screen
|
|
582
|
+
requestAnimationFrame(() => {
|
|
583
|
+
this.props.onLayerLoadingChange?.(true);
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}));
|
|
592
|
+
this.disposers.push(() => { firstChunkLoaded = false; });
|
|
593
|
+
// TODO: This is purely for debugging - exposes the NG viewer to be tested via console
|
|
556
594
|
// window.viewer = this.viewer;
|
|
557
595
|
}
|
|
558
596
|
componentDidUpdate(prevProps, prevState) {
|
|
559
|
-
const { viewerState, cellColorMapping } = this.props;
|
|
597
|
+
const { viewerState, cellColorMapping: cellColorMappingByLayer } = this.props;
|
|
560
598
|
// The restoreState() call clears the 'selected' (hovered on) segment, which is needed
|
|
561
599
|
// by Neuroglancer's code to toggle segment visibilty on a mouse click. To free the user
|
|
562
600
|
// from having to move the mouse before clicking, save the selected segment and restore
|
|
@@ -592,6 +630,23 @@ export default class Neuroglancer extends React.Component {
|
|
|
592
630
|
if (layer.layer instanceof SegmentationUserLayer) {
|
|
593
631
|
const { segmentSelectionState } = layer.layer.displayState;
|
|
594
632
|
segmentSelectionState.set(selectedSegments[layer.name]);
|
|
633
|
+
const layerScope = Object.keys(cellColorMappingByLayer).find(scope => layer.name?.includes(scope));
|
|
634
|
+
if (layerScope) {
|
|
635
|
+
const opacity = cellColorMappingByLayer[layerScope]?.opacity ?? 1.0;
|
|
636
|
+
layer.layer.displayState.objectAlpha.value = opacity;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
// Update annotation layer shaders from viewerState config,
|
|
640
|
+
// skipping update if shader is unchanged to avoid costly re-renders
|
|
641
|
+
if (layer.layer instanceof AnnotationUserLayer) {
|
|
642
|
+
const matchingLayer = (viewerState?.layers || []).find(l => l.name === layer.name);
|
|
643
|
+
if (matchingLayer?.shader) {
|
|
644
|
+
/* eslint-disable-next-line no-underscore-dangle */
|
|
645
|
+
const currentShader = layer.layer.annotationDisplayState.shader.value_;
|
|
646
|
+
if (currentShader !== matchingLayer.shader) {
|
|
647
|
+
layer.layer.annotationDisplayState.shader.value = matchingLayer.shader;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
595
650
|
}
|
|
596
651
|
}
|
|
597
652
|
// For some reason setting position to an empty array doesn't reset
|
|
@@ -633,21 +688,24 @@ export default class Neuroglancer extends React.Component {
|
|
|
633
688
|
this.withoutEmitting(() => {
|
|
634
689
|
const layers = Array.isArray(viewerState.layers) ? viewerState.layers : [];
|
|
635
690
|
this.viewer.state.restoreState({ layers });
|
|
636
|
-
if (
|
|
637
|
-
this.applyColorsAndVisibility(
|
|
691
|
+
if (cellColorMappingByLayer && Object.keys(cellColorMappingByLayer).length) {
|
|
692
|
+
this.applyColorsAndVisibility(cellColorMappingByLayer);
|
|
638
693
|
}
|
|
639
694
|
});
|
|
640
695
|
}
|
|
641
696
|
// If colors changed (but layers didn’t): re-apply colors
|
|
642
697
|
// this was to avid NG randomly assigning colors to the segments by resetting them
|
|
643
698
|
const prevSize = prevProps.cellColorMapping
|
|
644
|
-
? Object.
|
|
645
|
-
|
|
646
|
-
const
|
|
699
|
+
? Object.values(prevProps.cellColorMapping)
|
|
700
|
+
.reduce((acc, v) => acc + Object.keys(v?.colors || {}).length, 0) : 0;
|
|
701
|
+
const currSize = cellColorMappingByLayer
|
|
702
|
+
? Object.values(cellColorMappingByLayer)
|
|
703
|
+
.reduce((acc, v) => acc + Object.keys(v?.colors || {}).length, 0) : 0;
|
|
704
|
+
const mappingRefChanged = prevProps.cellColorMapping !== this.props.cellColorMapping;
|
|
647
705
|
if (!this.didLayersChange(prevVS, viewerState)
|
|
648
706
|
&& (mappingRefChanged || prevSize !== currSize)) {
|
|
649
707
|
this.withoutEmitting(() => {
|
|
650
|
-
this.applyColorsAndVisibility(
|
|
708
|
+
this.applyColorsAndVisibility(cellColorMappingByLayer);
|
|
651
709
|
});
|
|
652
710
|
}
|
|
653
711
|
// Treat "real" layer source/type changes differently from segment list changes.
|
|
@@ -655,7 +713,7 @@ export default class Neuroglancer extends React.Component {
|
|
|
655
713
|
const stripSegFields = layers => (layers || []).map((l) => {
|
|
656
714
|
if (!l)
|
|
657
715
|
return l;
|
|
658
|
-
const { segments, segmentColors, ...rest } = l;
|
|
716
|
+
const { segments, segmentColors, objectAlpha, ...rest } = l;
|
|
659
717
|
return rest; // ignore segments + segmentColors for comparison
|
|
660
718
|
});
|
|
661
719
|
const prevLayers = prevProps.viewerState?.layers;
|
|
@@ -21,7 +21,7 @@ export function toNgLayerName(dataType: any, layerScope: any, channelScope?: nul
|
|
|
21
21
|
/**
|
|
22
22
|
* @returns [viewerState]
|
|
23
23
|
*/
|
|
24
|
-
export function useNeuroglancerViewerState(theme: any, segmentationLayerScopes: any, segmentationChannelScopesByLayer: any, segmentationLayerCoordination: any, segmentationChannelCoordination: any, obsSegmentationsUrls: any, obsSegmentationsData: any, pointLayerScopes: any, pointLayerCoordination: any, obsPointsUrls: any, obsPointsData: any, pointMultiIndicesData: any): any;
|
|
24
|
+
export function useNeuroglancerViewerState(theme: any, showAxisLines: any, segmentationLayerScopes: any, segmentationChannelScopesByLayer: any, segmentationLayerCoordination: any, segmentationChannelCoordination: any, obsSegmentationsUrls: any, obsSegmentationsData: any, pointLayerScopes: any, pointLayerCoordination: any, obsPointsUrls: any, obsPointsData: any, pointMultiIndicesData: any): any;
|
|
25
25
|
export namespace DEFAULT_NG_PROPS {
|
|
26
26
|
let layout: string;
|
|
27
27
|
let position: number[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-hook-ng-utils.d.ts","sourceRoot":"","sources":["../src/data-hook-ng-utils.js"],"names":[],"mappings":"AAgDA;;;;KAIK;AACL,sDAHa,MAAM,GACJ;IAAE,CAAC,EAAC,CAAC,MAAM,EAAC,IAAI,CAAC,CAAC;IAAC,CAAC,EAAC,CAAC,MAAM,EAAC,IAAI,CAAC,CAAC;IAAC,CAAC,EAAC,CAAC,MAAM,EAAC,IAAI,CAAC,CAAA;CAAE,CA+BnE;AAED,2FAQC;AAED;;;;;;;;GAQG;AACH;;GAEG;AACH,
|
|
1
|
+
{"version":3,"file":"data-hook-ng-utils.d.ts","sourceRoot":"","sources":["../src/data-hook-ng-utils.js"],"names":[],"mappings":"AAgDA;;;;KAIK;AACL,sDAHa,MAAM,GACJ;IAAE,CAAC,EAAC,CAAC,MAAM,EAAC,IAAI,CAAC,CAAC;IAAC,CAAC,EAAC,CAAC,MAAM,EAAC,IAAI,CAAC,CAAC;IAAC,CAAC,EAAC,CAAC,MAAM,EAAC,IAAI,CAAC,CAAA;CAAE,CA+BnE;AAED,2FAQC;AAED;;;;;;;;GAQG;AACH;;GAEG;AACH,6YAoKC"}
|
|
@@ -88,7 +88,7 @@ export function toNgLayerName(dataType, layerScope, channelScope = null) {
|
|
|
88
88
|
/**
|
|
89
89
|
* @returns [viewerState]
|
|
90
90
|
*/
|
|
91
|
-
export function useNeuroglancerViewerState(theme, segmentationLayerScopes, segmentationChannelScopesByLayer, segmentationLayerCoordination, segmentationChannelCoordination, obsSegmentationsUrls, obsSegmentationsData, pointLayerScopes, pointLayerCoordination, obsPointsUrls, obsPointsData, pointMultiIndicesData) {
|
|
91
|
+
export function useNeuroglancerViewerState(theme, showAxisLines, segmentationLayerScopes, segmentationChannelScopesByLayer, segmentationLayerCoordination, segmentationChannelCoordination, obsSegmentationsUrls, obsSegmentationsData, pointLayerScopes, pointLayerCoordination, obsPointsUrls, obsPointsData, pointMultiIndicesData) {
|
|
92
92
|
const viewerState = useMemoCustomComparison(() => {
|
|
93
93
|
let result = cloneDeep(DEFAULT_NG_PROPS);
|
|
94
94
|
// ======= SEGMENTATIONS =======
|
|
@@ -103,20 +103,32 @@ export function useNeuroglancerViewerState(theme, segmentationLayerScopes, segme
|
|
|
103
103
|
channelScopes.forEach((channelScope) => {
|
|
104
104
|
const channelCoordination = segmentationChannelCoordination[0]?.[layerScope]?.[channelScope];
|
|
105
105
|
const { spatialChannelVisible, } = channelCoordination || {};
|
|
106
|
+
const { source: ngSource, ...otherNgOptions } = layerData.neuroglancerOptions ?? {};
|
|
107
|
+
// Build source: if neuroglancerOptions has subsources
|
|
108
|
+
const hasNgSourceOptions = layerData.neuroglancerOptions?.subsources
|
|
109
|
+
|| layerData.neuroglancerOptions?.enableDefaultSubsources !== undefined;
|
|
110
|
+
const source = hasNgSourceOptions
|
|
111
|
+
? {
|
|
112
|
+
url: toPrecomputedSource(layerUrl),
|
|
113
|
+
subsources: layerData.neuroglancerOptions.subsources,
|
|
114
|
+
enableDefaultSubsources: layerData.neuroglancerOptions.enableDefaultSubsources
|
|
115
|
+
?? false,
|
|
116
|
+
}
|
|
117
|
+
: toPrecomputedSource(layerUrl);
|
|
106
118
|
result = {
|
|
107
119
|
...result,
|
|
108
120
|
layers: [
|
|
109
121
|
...result.layers,
|
|
110
122
|
{
|
|
111
123
|
type: 'segmentation',
|
|
112
|
-
source
|
|
124
|
+
source,
|
|
113
125
|
segments: [],
|
|
114
126
|
name: toNgLayerName(DataType.OBS_SEGMENTATIONS, layerScope, channelScope),
|
|
115
127
|
visible: spatialLayerVisible && spatialChannelVisible, // Both layer and channel
|
|
116
128
|
// visibility must be true for the layer to be visible.
|
|
117
129
|
// TODO: update this to extract specific properties from
|
|
118
130
|
// neuroglancerOptions as needed.
|
|
119
|
-
...
|
|
131
|
+
...otherNgOptions,
|
|
120
132
|
},
|
|
121
133
|
],
|
|
122
134
|
};
|
|
@@ -131,7 +143,7 @@ export function useNeuroglancerViewerState(theme, segmentationLayerScopes, segme
|
|
|
131
143
|
const layerUrl = obsPointsUrls[layerScope]?.[0]?.url;
|
|
132
144
|
const featureIndex = pointMultiIndicesData[layerScope]?.featureIndex;
|
|
133
145
|
if (layerUrl && layerData) {
|
|
134
|
-
const { spatialLayerVisible, spatialLayerOpacity, obsColorEncoding, spatialLayerColor, featureSelection, featureFilterMode, featureColor, } = layerCoordination || {};
|
|
146
|
+
const { spatialLayerVisible, spatialLayerOpacity, obsColorEncoding, spatialLayerColor, featureSelection, featureFilterMode, featureColor, spatialPointStrokeWidth, } = layerCoordination || {};
|
|
135
147
|
// Dynamically construct the shader based on the color encoding
|
|
136
148
|
// and other coordination values.
|
|
137
149
|
const shader = getPointsShader({
|
|
@@ -145,6 +157,7 @@ export function useNeuroglancerViewerState(theme, segmentationLayerScopes, segme
|
|
|
145
157
|
featureColor,
|
|
146
158
|
featureIndexProp: layerData.neuroglancerOptions?.featureIndexProp,
|
|
147
159
|
pointIndexProp: layerData.neuroglancerOptions?.pointIndexProp,
|
|
160
|
+
pointMarkerBorderWidth: spatialPointStrokeWidth ?? 0.0,
|
|
148
161
|
});
|
|
149
162
|
result = {
|
|
150
163
|
...result,
|
|
@@ -182,6 +195,7 @@ export function useNeuroglancerViewerState(theme, segmentationLayerScopes, segme
|
|
|
182
195
|
return result;
|
|
183
196
|
}, {
|
|
184
197
|
theme,
|
|
198
|
+
showAxisLines,
|
|
185
199
|
segmentationLayerScopes,
|
|
186
200
|
segmentationChannelScopesByLayer,
|
|
187
201
|
segmentationLayerCoordination,
|