@vitessce/neuroglancer 3.9.6 → 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-BSLfuCt9.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 +52 -25
- 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/use-memo-custom-comparison.d.ts.map +1 -1
- package/dist-tsc/use-memo-custom-comparison.js +1 -0
- package/package.json +9 -9
- package/src/Neuroglancer.js +31 -26
- package/src/NeuroglancerSubscriber.js +77 -48
- package/src/ReactNeuroglancer.js +34 -27
- package/src/use-memo-custom-comparison.js +1 -0
- package/dist/index-DvhFVdN_.js +0 -37826
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"}
|
|
@@ -17,6 +17,7 @@ const ZOOM_EPS = 1e-2;
|
|
|
17
17
|
const ROTATION_EPS = 1e-3;
|
|
18
18
|
const TARGET_EPS = 0.5;
|
|
19
19
|
const NG_ROT_COOLDOWN_MS = 120;
|
|
20
|
+
const GUIDE_URL = 'https://vitessce.io/docs/ng-guide/';
|
|
20
21
|
const LAST_INTERACTION_SOURCE = {
|
|
21
22
|
vitessce: 'vitessce',
|
|
22
23
|
neuroglancer: 'neuroglancer',
|
|
@@ -132,8 +133,33 @@ export function NeuroglancerSubscriber(props) {
|
|
|
132
133
|
result[layerScope] = {};
|
|
133
134
|
segmentationChannelScopesByLayer?.[layerScope]?.forEach((channelScope) => {
|
|
134
135
|
const { obsSets: layerSets, obsIndex: layerIndex } = obsSegmentationsSetsData?.[layerScope]?.[channelScope] || {};
|
|
135
|
-
|
|
136
|
-
|
|
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) {
|
|
137
163
|
const mergedCellSets = mergeObsSets(layerSets, additionalObsSets);
|
|
138
164
|
const cellColors = getCellColors({
|
|
139
165
|
cellSets: mergedCellSets,
|
|
@@ -147,13 +173,6 @@ export function NeuroglancerSubscriber(props) {
|
|
|
147
173
|
cellColors.forEach((color, i) => {
|
|
148
174
|
ngCellColors[i] = rgbToHex(color);
|
|
149
175
|
});
|
|
150
|
-
/* // TODO: Is this necessary?
|
|
151
|
-
const obsColorIndices = treeToCellSetColorIndicesBySetNames(
|
|
152
|
-
mergedLayerSets,
|
|
153
|
-
obsSetSelection,
|
|
154
|
-
obsSetColor,
|
|
155
|
-
);
|
|
156
|
-
*/
|
|
157
176
|
result[layerScope][channelScope] = ngCellColors;
|
|
158
177
|
}
|
|
159
178
|
});
|
|
@@ -324,6 +343,8 @@ export function NeuroglancerSubscriber(props) {
|
|
|
324
343
|
};
|
|
325
344
|
}, []);
|
|
326
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
|
|
327
348
|
if (value) {
|
|
328
349
|
const id = String(value);
|
|
329
350
|
const selectedCellIds = [id];
|
|
@@ -341,10 +362,15 @@ export function NeuroglancerSubscriber(props) {
|
|
|
341
362
|
}, [additionalCellSets, cellSetColor, setAdditionalCellSets,
|
|
342
363
|
setCellColorEncoding, setCellSetColor, setCellSetSelection,
|
|
343
364
|
]);
|
|
344
|
-
// Get the ultimate cellColorMapping to pass to NeuroglancerComp as a prop.
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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] ?? {};
|
|
371
|
+
});
|
|
372
|
+
return result;
|
|
373
|
+
}, [segmentationColorMapping, segmentationLayerScopes, segmentationChannelScopesByLayer]);
|
|
348
374
|
// TODO: try to simplify using useMemoCustomComparison?
|
|
349
375
|
// This would allow us to refactor a lot of the checking-for-changes logic into a comparison function,
|
|
350
376
|
// simplify some of the manual bookkeeping like with prevCoordsRef and lastInteractionSource,
|
|
@@ -355,9 +381,6 @@ export function NeuroglancerSubscriber(props) {
|
|
|
355
381
|
if (current.layers.length <= 0) {
|
|
356
382
|
return current;
|
|
357
383
|
}
|
|
358
|
-
const nextSegments = Object.keys(cellColorMapping);
|
|
359
|
-
const prevLayer = current?.layers?.[0] || {};
|
|
360
|
-
const prevSegments = prevLayer.segments || [];
|
|
361
384
|
const { projectionScale, projectionOrientation, position } = current;
|
|
362
385
|
// Did Vitessce coords change vs the *previous* render?
|
|
363
386
|
const rotChangedNow = !nearEq(spatialRotationX, prevCoordsRef.current.rx, ROTATION_EPS)
|
|
@@ -462,18 +485,22 @@ export function NeuroglancerSubscriber(props) {
|
|
|
462
485
|
nextOrientation = lastNgPushOrientationRef.current ?? projectionOrientation;
|
|
463
486
|
lastInteractionSource.current = null;
|
|
464
487
|
}
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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
|
+
}) ?? [];
|
|
470
498
|
const updated = {
|
|
471
499
|
...current,
|
|
472
500
|
projectionScale: nextProjectionScale,
|
|
473
501
|
projectionOrientation: nextOrientation,
|
|
474
502
|
position: nextPosition,
|
|
475
|
-
layers:
|
|
476
|
-
|| [])] : current?.layers,
|
|
503
|
+
layers: updatedLayers,
|
|
477
504
|
};
|
|
478
505
|
latestViewerStateRef.current = updated;
|
|
479
506
|
prevCoordsRef.current = {
|
|
@@ -486,7 +513,7 @@ export function NeuroglancerSubscriber(props) {
|
|
|
486
513
|
ty: spatialTargetY,
|
|
487
514
|
};
|
|
488
515
|
return updated;
|
|
489
|
-
}, [
|
|
516
|
+
}, [cellColorMappingByLayer, spatialZoom, spatialRotationX, spatialRotationY,
|
|
490
517
|
spatialRotationZ, spatialTargetX, spatialTargetY, initalViewerState,
|
|
491
518
|
latestViewerStateIteration]);
|
|
492
519
|
const onSegmentHighlight = useCallback((obsId) => {
|
|
@@ -498,5 +525,5 @@ export function NeuroglancerSubscriber(props) {
|
|
|
498
525
|
// }
|
|
499
526
|
const hasLayers = derivedViewerState?.layers?.length > 0;
|
|
500
527
|
// console.log(derivedViewerState);
|
|
501
|
-
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, 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, segmentationLayerScopes: segmentationLayerScopes, segmentationLayerCoordination: segmentationLayerCoordination, segmentationChannelScopesByLayer: segmentationChannelScopesByLayer, segmentationChannelCoordination: segmentationChannelCoordination }) }),
|
|
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 }));
|
|
502
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.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-memo-custom-comparison.d.ts","sourceRoot":"","sources":["../src/use-memo-custom-comparison.js"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AACH,wCAPa,CAAC,WACH,MAAM,CAAC,gBACP,GAAG,iBACH,CAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,KAAK,OAAO,GAEvC,CAAC,CAUb;AA6CD,
|
|
1
|
+
{"version":3,"file":"use-memo-custom-comparison.d.ts","sourceRoot":"","sources":["../src/use-memo-custom-comparison.js"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AACH,wCAPa,CAAC,WACH,MAAM,CAAC,gBACP,GAAG,iBACH,CAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,KAAK,OAAO,GAEvC,CAAC,CAUb;AA6CD,kFA+CC;AAED,0FAyEC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitessce/neuroglancer",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.7",
|
|
4
4
|
"author": "Gehlenborg Lab",
|
|
5
5
|
"homepage": "http://vitessce.io",
|
|
6
6
|
"repository": {
|
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
"lodash-es": "^4.17.21",
|
|
21
21
|
"three": "^0.154.0",
|
|
22
22
|
"react": "18.3.1",
|
|
23
|
-
"@vitessce/
|
|
24
|
-
"@vitessce/
|
|
25
|
-
"@vitessce/
|
|
26
|
-
"@vitessce/utils": "3.9.
|
|
27
|
-
"@vitessce/
|
|
28
|
-
"@vitessce/sets-utils": "3.9.
|
|
29
|
-
"@vitessce/tooltip": "3.9.
|
|
30
|
-
"@vitessce/legend": "3.9.
|
|
23
|
+
"@vitessce/styles": "3.9.7",
|
|
24
|
+
"@vitessce/constants-internal": "3.9.7",
|
|
25
|
+
"@vitessce/vit-s": "3.9.7",
|
|
26
|
+
"@vitessce/utils": "3.9.7",
|
|
27
|
+
"@vitessce/neuroglancer-workers": "3.9.7",
|
|
28
|
+
"@vitessce/sets-utils": "3.9.7",
|
|
29
|
+
"@vitessce/tooltip": "3.9.7",
|
|
30
|
+
"@vitessce/legend": "3.9.7"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@testing-library/jest-dom": "^6.6.3",
|
package/src/Neuroglancer.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/* eslint-disable react-refresh/only-export-components */
|
|
2
2
|
import React, { PureComponent, Suspense } from 'react';
|
|
3
|
-
import { ChunkWorker } from '@vitessce/neuroglancer-workers';
|
|
3
|
+
import { ChunkWorker, AsyncComputationWorker } from '@vitessce/neuroglancer-workers';
|
|
4
4
|
import { NeuroglancerGlobalStyles } from './styles.js';
|
|
5
5
|
|
|
6
6
|
const LazyReactNeuroglancer = React.lazy(() => import('./ReactNeuroglancer.js'));
|
|
7
7
|
|
|
8
8
|
function createWorker() {
|
|
9
|
-
|
|
9
|
+
const worker = new ChunkWorker();
|
|
10
|
+
worker.AsyncComputationWorker = AsyncComputationWorker;
|
|
11
|
+
return worker;
|
|
10
12
|
}
|
|
11
13
|
export class NeuroglancerComp extends PureComponent {
|
|
12
14
|
constructor(props) {
|
|
@@ -14,8 +16,6 @@ export class NeuroglancerComp extends PureComponent {
|
|
|
14
16
|
this.bundleRoot = createWorker();
|
|
15
17
|
this.cellColorMapping = props.cellColorMapping;
|
|
16
18
|
this.justReceivedExternalUpdate = false;
|
|
17
|
-
this.prevElement = null;
|
|
18
|
-
this.prevClickHandler = null;
|
|
19
19
|
this.prevMouseStateChanged = null;
|
|
20
20
|
this.prevHoverHandler = null;
|
|
21
21
|
this.onViewerStateChanged = this.onViewerStateChanged.bind(this);
|
|
@@ -32,41 +32,49 @@ export class NeuroglancerComp extends PureComponent {
|
|
|
32
32
|
if (viewerRef) {
|
|
33
33
|
// Mount
|
|
34
34
|
const { viewer } = viewerRef;
|
|
35
|
-
this.prevElement = viewer.element;
|
|
36
35
|
this.prevMouseStateChanged = viewer.mouseState.changed;
|
|
37
|
-
|
|
36
|
+
// For now, can omit the sliceView bindings, as we only use perspectiveView
|
|
37
|
+
// viewer.inputEventBindings.sliceView.set('at:dblclick0', () => {});
|
|
38
38
|
viewer.inputEventBindings.perspectiveView.set('at:dblclick0', () => {});
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
|
|
40
|
+
// Disable space interaction to prevent triggering 4panels layout.
|
|
41
|
+
viewer.inputEventBindings.sliceView.set('at:space', () => {});
|
|
42
|
+
viewer.inputEventBindings.perspectiveView.set('at:space', () => {});
|
|
43
|
+
|
|
44
|
+
// Remap plain wheel to ctrl+wheel (zoom) action
|
|
45
|
+
// by traversing the parent binding maps.
|
|
46
|
+
const remapWheelToZoom = (map) => {
|
|
47
|
+
if (map.bindings) {
|
|
48
|
+
const ctrlWheelAction = map.bindings.get('at:control+wheel');
|
|
49
|
+
if (ctrlWheelAction) {
|
|
50
|
+
// Replace plain wheel with the zoom action
|
|
51
|
+
map.bindings.set('at:wheel', ctrlWheelAction);
|
|
52
|
+
const ctrlWheelBubble = map.bindings.get('bubble:control+wheel');
|
|
53
|
+
if (ctrlWheelBubble) {
|
|
54
|
+
map.bindings.set('bubble:wheel', ctrlWheelBubble);
|
|
47
55
|
}
|
|
48
|
-
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (map.parents) {
|
|
59
|
+
map.parents.forEach(p => remapWheelToZoom(p));
|
|
49
60
|
}
|
|
50
61
|
};
|
|
62
|
+
|
|
63
|
+
remapWheelToZoom(viewer.inputEventBindings.perspectiveView);
|
|
64
|
+
|
|
51
65
|
this.prevHoverHandler = () => {
|
|
52
66
|
if (viewer.mouseState.pickedValue !== undefined) {
|
|
53
67
|
const pickedSegment = viewer.mouseState.pickedValue;
|
|
54
68
|
this.latestOnSelectHoveredCoords?.(pickedSegment?.low);
|
|
55
69
|
}
|
|
56
70
|
};
|
|
57
|
-
|
|
71
|
+
|
|
58
72
|
viewer.mouseState.changed.add(this.prevHoverHandler);
|
|
59
73
|
} else {
|
|
60
|
-
// Unmount (viewerRef is null)
|
|
61
|
-
if (this.prevElement && this.prevClickHandler) {
|
|
62
|
-
this.prevElement.removeEventListener('mouseup', this.prevClickHandler);
|
|
63
|
-
this.prevClickHandler = null;
|
|
64
|
-
}
|
|
65
74
|
if (this.prevMouseStateChanged && this.prevHoverHandler) {
|
|
66
75
|
this.prevMouseStateChanged.remove(this.prevHoverHandler);
|
|
67
76
|
this.prevHoverHandler = null;
|
|
68
77
|
}
|
|
69
|
-
this.prevElement = null;
|
|
70
78
|
this.prevMouseStateChanged = null;
|
|
71
79
|
}
|
|
72
80
|
}
|
|
@@ -77,10 +85,7 @@ export class NeuroglancerComp extends PureComponent {
|
|
|
77
85
|
}
|
|
78
86
|
|
|
79
87
|
componentDidUpdate(prevProps) {
|
|
80
|
-
const {
|
|
81
|
-
if (prevProps.onSegmentClick !== onSegmentClick) {
|
|
82
|
-
this.latestOnSegmentClick = onSegmentClick;
|
|
83
|
-
}
|
|
88
|
+
const { onSelectHoveredCoords } = this.props;
|
|
84
89
|
if (prevProps.onSelectHoveredCoords !== onSelectHoveredCoords) {
|
|
85
90
|
this.latestOnSelectHoveredCoords = onSelectHoveredCoords;
|
|
86
91
|
}
|