@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
|
@@ -57,6 +57,8 @@ const ROTATION_EPS = 1e-3;
|
|
|
57
57
|
const TARGET_EPS = 0.5;
|
|
58
58
|
const NG_ROT_COOLDOWN_MS = 120;
|
|
59
59
|
|
|
60
|
+
const GUIDE_URL = 'https://vitessce.io/docs/ng-guide/';
|
|
61
|
+
|
|
60
62
|
const LAST_INTERACTION_SOURCE = {
|
|
61
63
|
vitessce: 'vitessce',
|
|
62
64
|
neuroglancer: 'neuroglancer',
|
|
@@ -281,13 +283,40 @@ export function NeuroglancerSubscriber(props) {
|
|
|
281
283
|
segmentationChannelScopesByLayer?.[layerScope]?.forEach((channelScope) => {
|
|
282
284
|
const { obsSets: layerSets, obsIndex: layerIndex } = obsSegmentationsSetsData
|
|
283
285
|
?.[layerScope]?.[channelScope] || {};
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
286
|
+
const {
|
|
287
|
+
obsSetColor,
|
|
288
|
+
obsColorEncoding,
|
|
289
|
+
obsSetSelection,
|
|
290
|
+
additionalObsSets,
|
|
291
|
+
spatialChannelColor,
|
|
292
|
+
} = segmentationChannelCoordination[0][layerScope][channelScope];
|
|
293
|
+
|
|
294
|
+
if (obsColorEncoding === 'spatialChannelColor') {
|
|
295
|
+
// All segments get the same static channel color
|
|
296
|
+
if (layerIndex && spatialChannelColor) {
|
|
297
|
+
const hex = rgbToHex(spatialChannelColor);
|
|
298
|
+
const ngCellColors = {};
|
|
299
|
+
|
|
300
|
+
if (obsSetSelection?.length > 0) {
|
|
301
|
+
// Only color the segments belonging to selected sets.
|
|
302
|
+
const mergedCellSets = mergeObsSets(layerSets, additionalObsSets);
|
|
303
|
+
const selectedIds = new Set();
|
|
304
|
+
obsSetSelection.forEach((setPath) => {
|
|
305
|
+
const rootNode = mergedCellSets?.tree?.find(n => n.name === setPath[0]);
|
|
306
|
+
const leafNode = setPath.length > 1
|
|
307
|
+
? rootNode?.children?.find(n => n.name === setPath[1])
|
|
308
|
+
: rootNode;
|
|
309
|
+
leafNode?.set?.forEach(([id]) => selectedIds.add(String(id)));
|
|
310
|
+
});
|
|
311
|
+
layerIndex.forEach((id) => {
|
|
312
|
+
if (selectedIds.has(String(id))) {
|
|
313
|
+
ngCellColors[id] = hex;
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
result[layerScope][channelScope] = ngCellColors;
|
|
318
|
+
}
|
|
319
|
+
} else if (layerSets && layerIndex) {
|
|
291
320
|
const mergedCellSets = mergeObsSets(layerSets, additionalObsSets);
|
|
292
321
|
const cellColors = getCellColors({
|
|
293
322
|
cellSets: mergedCellSets,
|
|
@@ -301,13 +330,6 @@ export function NeuroglancerSubscriber(props) {
|
|
|
301
330
|
cellColors.forEach((color, i) => {
|
|
302
331
|
ngCellColors[i] = rgbToHex(color);
|
|
303
332
|
});
|
|
304
|
-
/* // TODO: Is this necessary?
|
|
305
|
-
const obsColorIndices = treeToCellSetColorIndicesBySetNames(
|
|
306
|
-
mergedLayerSets,
|
|
307
|
-
obsSetSelection,
|
|
308
|
-
obsSetColor,
|
|
309
|
-
);
|
|
310
|
-
*/
|
|
311
333
|
result[layerScope][channelScope] = ngCellColors;
|
|
312
334
|
}
|
|
313
335
|
});
|
|
@@ -512,6 +534,8 @@ export function NeuroglancerSubscriber(props) {
|
|
|
512
534
|
}, []);
|
|
513
535
|
|
|
514
536
|
const onSegmentClick = useCallback((value) => {
|
|
537
|
+
// Note: this callback is no longer called by the child component.
|
|
538
|
+
// Reference: https://github.com/vitessce/vitessce/pull/2439
|
|
515
539
|
if (value) {
|
|
516
540
|
const id = String(value);
|
|
517
541
|
const selectedCellIds = [id];
|
|
@@ -536,14 +560,16 @@ export function NeuroglancerSubscriber(props) {
|
|
|
536
560
|
setCellColorEncoding, setCellSetColor, setCellSetSelection,
|
|
537
561
|
]);
|
|
538
562
|
|
|
539
|
-
// Get the ultimate cellColorMapping to pass to NeuroglancerComp as a prop.
|
|
540
|
-
// For now, we take the first layer and channel for cell colors.
|
|
541
|
-
const cellColorMapping = useMemo(() => (segmentationColorMapping
|
|
542
|
-
?.[segmentationLayerScopes?.[0]]
|
|
543
|
-
?.[segmentationChannelScopesByLayer?.[segmentationLayerScopes?.[0]]?.[0]]
|
|
544
|
-
?? {}
|
|
545
|
-
), [segmentationColorMapping]);
|
|
563
|
+
// Get the ultimate cellColorMapping for each layer to pass to NeuroglancerComp as a prop.
|
|
546
564
|
|
|
565
|
+
const cellColorMappingByLayer = useMemo(() => {
|
|
566
|
+
const result = {};
|
|
567
|
+
segmentationLayerScopes?.forEach((layerScope) => {
|
|
568
|
+
const channelScope = segmentationChannelScopesByLayer?.[layerScope]?.[0];
|
|
569
|
+
result[layerScope] = segmentationColorMapping?.[layerScope]?.[channelScope] ?? {};
|
|
570
|
+
});
|
|
571
|
+
return result;
|
|
572
|
+
}, [segmentationColorMapping, segmentationLayerScopes, segmentationChannelScopesByLayer]);
|
|
547
573
|
|
|
548
574
|
// TODO: try to simplify using useMemoCustomComparison?
|
|
549
575
|
// This would allow us to refactor a lot of the checking-for-changes logic into a comparison function,
|
|
@@ -556,9 +582,6 @@ export function NeuroglancerSubscriber(props) {
|
|
|
556
582
|
return current;
|
|
557
583
|
}
|
|
558
584
|
|
|
559
|
-
const nextSegments = Object.keys(cellColorMapping);
|
|
560
|
-
const prevLayer = current?.layers?.[0] || {};
|
|
561
|
-
const prevSegments = prevLayer.segments || [];
|
|
562
585
|
const { projectionScale, projectionOrientation, position } = current;
|
|
563
586
|
|
|
564
587
|
// Did Vitessce coords change vs the *previous* render?
|
|
@@ -695,20 +718,23 @@ export function NeuroglancerSubscriber(props) {
|
|
|
695
718
|
lastInteractionSource.current = null;
|
|
696
719
|
}
|
|
697
720
|
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
721
|
+
const updatedLayers = current?.layers?.map((layer, idx) => {
|
|
722
|
+
const layerScope = segmentationLayerScopes?.[idx];
|
|
723
|
+
const layerColorMapping = cellColorMappingByLayer?.[layerScope] ?? {};
|
|
724
|
+
const layerSegments = Object.keys(layerColorMapping);
|
|
725
|
+
return {
|
|
726
|
+
...layer,
|
|
727
|
+
segments: layerSegments,
|
|
728
|
+
segmentColors: layerColorMapping,
|
|
729
|
+
};
|
|
730
|
+
}) ?? [];
|
|
704
731
|
|
|
705
732
|
const updated = {
|
|
706
733
|
...current,
|
|
707
734
|
projectionScale: nextProjectionScale,
|
|
708
735
|
projectionOrientation: nextOrientation,
|
|
709
736
|
position: nextPosition,
|
|
710
|
-
layers:
|
|
711
|
-
|| [])] : current?.layers,
|
|
737
|
+
layers: updatedLayers,
|
|
712
738
|
};
|
|
713
739
|
|
|
714
740
|
latestViewerStateRef.current = updated;
|
|
@@ -724,7 +750,7 @@ export function NeuroglancerSubscriber(props) {
|
|
|
724
750
|
};
|
|
725
751
|
|
|
726
752
|
return updated;
|
|
727
|
-
}, [
|
|
753
|
+
}, [cellColorMappingByLayer, spatialZoom, spatialRotationX, spatialRotationY,
|
|
728
754
|
spatialRotationZ, spatialTargetX, spatialTargetY, initalViewerState,
|
|
729
755
|
latestViewerStateIteration]);
|
|
730
756
|
|
|
@@ -741,6 +767,7 @@ export function NeuroglancerSubscriber(props) {
|
|
|
741
767
|
// console.log(derivedViewerState);
|
|
742
768
|
|
|
743
769
|
return (
|
|
770
|
+
|
|
744
771
|
<TitleInfo
|
|
745
772
|
title={title}
|
|
746
773
|
info={subtitle}
|
|
@@ -753,30 +780,32 @@ export function NeuroglancerSubscriber(props) {
|
|
|
753
780
|
isReady={isReady}
|
|
754
781
|
errors={errors}
|
|
755
782
|
withPadding={false}
|
|
783
|
+
guideUrl={GUIDE_URL}
|
|
756
784
|
>
|
|
757
|
-
|
|
758
|
-
<div style={{ position: '
|
|
759
|
-
<
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
785
|
+
{hasLayers ? (
|
|
786
|
+
<div style={{ position: 'relative', width: '100%', height: '100%' }} ref={containerRef}>
|
|
787
|
+
<div style={{ position: 'absolute', top: 0, right: 0, zIndex: 50 }}>
|
|
788
|
+
<MultiLegend
|
|
789
|
+
theme="dark"
|
|
790
|
+
maxHeight={ngHeight}
|
|
791
|
+
segmentationLayerScopes={segmentationLayerScopes}
|
|
792
|
+
segmentationLayerCoordination={segmentationLayerCoordination}
|
|
793
|
+
segmentationChannelScopesByLayer={segmentationChannelScopesByLayer}
|
|
794
|
+
segmentationChannelCoordination={segmentationChannelCoordination}
|
|
795
|
+
/>
|
|
796
|
+
</div>
|
|
768
797
|
|
|
769
|
-
{hasLayers ? (
|
|
770
798
|
<NeuroglancerComp
|
|
771
799
|
classes={classes}
|
|
772
800
|
onSegmentClick={onSegmentClick}
|
|
773
801
|
onSelectHoveredCoords={onSegmentHighlight}
|
|
774
802
|
viewerState={derivedViewerState}
|
|
775
|
-
cellColorMapping={
|
|
803
|
+
cellColorMapping={cellColorMappingByLayer}
|
|
776
804
|
setViewerState={handleStateUpdate}
|
|
777
805
|
/>
|
|
778
|
-
|
|
779
|
-
|
|
806
|
+
</div>
|
|
807
|
+
) : null}
|
|
780
808
|
</TitleInfo>
|
|
809
|
+
|
|
781
810
|
);
|
|
782
811
|
}
|
package/src/ReactNeuroglancer.js
CHANGED
|
@@ -427,7 +427,7 @@ export default class Neuroglancer extends React.Component {
|
|
|
427
427
|
this.disposers = [];
|
|
428
428
|
this.prevColorOverrides = new Set();
|
|
429
429
|
this.overrideColorsById = Object.create(null);
|
|
430
|
-
this.
|
|
430
|
+
this.allKnownIdsByLayer = {};
|
|
431
431
|
}
|
|
432
432
|
|
|
433
433
|
minimalPoseSnapshot = () => {
|
|
@@ -476,30 +476,36 @@ export default class Neuroglancer extends React.Component {
|
|
|
476
476
|
};
|
|
477
477
|
|
|
478
478
|
/* To add colors to the segments, turning unselected to grey */
|
|
479
|
-
applyColorsAndVisibility = (
|
|
479
|
+
applyColorsAndVisibility = (cellColorMappingByLayer) => {
|
|
480
480
|
if (!this.viewer) return;
|
|
481
|
-
//
|
|
482
|
-
// that drop out of the current selection.
|
|
483
|
-
const selected = { ...(cellColorMapping || {}) }; // clone, don't mutate props
|
|
484
|
-
for (const id of Object.keys(selected)) this.allKnownIds.add(id);
|
|
485
|
-
// If empty on first call, seed from initial segmentColors (if present)
|
|
486
|
-
if (this.allKnownIds.size === 0) {
|
|
487
|
-
const init = this.props.viewerState?.layers?.[0]?.segmentColors || {};
|
|
488
|
-
for (const id of Object.keys(init)) this.allKnownIds.add(id);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// Build a full color table: selected keep their hex, others grey
|
|
492
|
-
const fullSegmentColors = {};
|
|
493
|
-
for (const id of this.allKnownIds) {
|
|
494
|
-
fullSegmentColors[id] = selected[id] || GREY_HEX;
|
|
495
|
-
}
|
|
496
|
-
// Patch layers with the new segmentColors (pose untouched)
|
|
481
|
+
// Build full color table per layer
|
|
497
482
|
const baseLayers = (this.props.viewerState?.layers)
|
|
498
483
|
?? (this.viewer.state.toJSON().layers || []);
|
|
499
484
|
|
|
500
|
-
const newLayers = baseLayers.map((layer
|
|
501
|
-
// if
|
|
502
|
-
|
|
485
|
+
const newLayers = baseLayers.map((layer) => {
|
|
486
|
+
// Match layerScope by checking if the NG layer name contains the scope key.
|
|
487
|
+
// NG layer names are of the form:
|
|
488
|
+
// "obsSegmentations-init_A_obsSegmentations_0-init_A_obsSegmentations_0"
|
|
489
|
+
const layerScope = Object.keys(cellColorMappingByLayer).find(scope => layer.name?.includes(scope));
|
|
490
|
+
|
|
491
|
+
const selected = { ...(cellColorMappingByLayer[layerScope] || {}) };
|
|
492
|
+
|
|
493
|
+
// Track all known IDs for this layer scope
|
|
494
|
+
if (!this.allKnownIdsByLayer) this.allKnownIdsByLayer = {};
|
|
495
|
+
if (!this.allKnownIdsByLayer[layerScope]) {
|
|
496
|
+
this.allKnownIdsByLayer[layerScope] = new Set();
|
|
497
|
+
}
|
|
498
|
+
for (const id of Object.keys(selected)) {
|
|
499
|
+
this.allKnownIdsByLayer[layerScope].add(id);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Build a full color table: selected keep their hex, others grey
|
|
503
|
+
const fullSegmentColors = {};
|
|
504
|
+
for (const id of this.allKnownIdsByLayer[layerScope] || []) {
|
|
505
|
+
fullSegmentColors[id] = selected[id] || GREY_HEX;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (layer.type === 'segmentation') {
|
|
503
509
|
return { ...layer, segmentColors: fullSegmentColors };
|
|
504
510
|
}
|
|
505
511
|
return layer;
|
|
@@ -624,7 +630,7 @@ export default class Neuroglancer extends React.Component {
|
|
|
624
630
|
}
|
|
625
631
|
|
|
626
632
|
componentDidUpdate(prevProps, prevState) {
|
|
627
|
-
const { viewerState, cellColorMapping } = this.props;
|
|
633
|
+
const { viewerState, cellColorMapping: cellColorMappingByLayer } = this.props;
|
|
628
634
|
// The restoreState() call clears the 'selected' (hovered on) segment, which is needed
|
|
629
635
|
// by Neuroglancer's code to toggle segment visibilty on a mouse click. To free the user
|
|
630
636
|
// from having to move the mouse before clicking, save the selected segment and restore
|
|
@@ -702,8 +708,8 @@ export default class Neuroglancer extends React.Component {
|
|
|
702
708
|
this.withoutEmitting(() => {
|
|
703
709
|
const layers = Array.isArray(viewerState.layers) ? viewerState.layers : [];
|
|
704
710
|
this.viewer.state.restoreState({ layers });
|
|
705
|
-
if (
|
|
706
|
-
this.applyColorsAndVisibility(
|
|
711
|
+
if (cellColorMappingByLayer && Object.keys(cellColorMappingByLayer).length) {
|
|
712
|
+
this.applyColorsAndVisibility(cellColorMappingByLayer);
|
|
707
713
|
}
|
|
708
714
|
});
|
|
709
715
|
}
|
|
@@ -712,12 +718,13 @@ export default class Neuroglancer extends React.Component {
|
|
|
712
718
|
// this was to avid NG randomly assigning colors to the segments by resetting them
|
|
713
719
|
const prevSize = prevProps.cellColorMapping
|
|
714
720
|
? Object.keys(prevProps.cellColorMapping).length : 0;
|
|
715
|
-
const currSize =
|
|
716
|
-
|
|
721
|
+
const currSize = cellColorMappingByLayer
|
|
722
|
+
? Object.keys(cellColorMappingByLayer).length : 0;
|
|
723
|
+
const mappingRefChanged = prevProps.cellColorMapping !== this.props.cellColorMapping;
|
|
717
724
|
if (!this.didLayersChange(prevVS, viewerState)
|
|
718
725
|
&& (mappingRefChanged || prevSize !== currSize)) {
|
|
719
726
|
this.withoutEmitting(() => {
|
|
720
|
-
this.applyColorsAndVisibility(
|
|
727
|
+
this.applyColorsAndVisibility(cellColorMappingByLayer);
|
|
721
728
|
});
|
|
722
729
|
}
|
|
723
730
|
|