@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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { N } from "./index-DvhFVdN_.js";
1
+ import { N } from "./index-BEPd2Tds.js";
2
2
  export {
3
3
  N as NeuroglancerSubscriber
4
4
  };
@@ -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":"AAUA;IACE,wBAcC;IAZC,gBAAgC;IAChC,sBAA8C;IAC9C,oCAAuC;IACvC,iBAAuB;IACvB,gDAA4B;IAC5B,2BAAiC;IACjC,sCAA4B;IAsD9B,2CAGC;IAjDD,4BA4CC;IAhDC,0BAAgD;IAChD,iCAA8D;IAsDhE,yCAQC;IAED,sBAoBC;CACF"}
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"}
@@ -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
- return new ChunkWorker();
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
- viewer.inputEventBindings.sliceView.set('at:dblclick0', () => { });
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
- this.prevClickHandler = (event) => {
37
- if (event.button === 0) {
38
- // Wait for mouseState to update
39
- requestAnimationFrame(() => {
40
- const { pickedValue, pickedRenderLayer } = viewer.mouseState;
41
- // Only trigger selection when a segment is clicked rather than any click on the view
42
- if (pickedValue && pickedValue.low !== undefined && pickedRenderLayer) {
43
- this.latestOnSegmentClick?.(pickedValue.low);
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 { onSegmentClick, onSelectHoveredCoords } = this.props;
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":"AAsEA,gEAusBC"}
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
- if (layerSets && layerIndex) {
136
- const { obsSetColor, obsColorEncoding, obsSetSelection, additionalObsSets, } = segmentationChannelCoordination[0][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) {
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
- // For now, we take the first layer and channel for cell colors.
346
- const cellColorMapping = useMemo(() => (segmentationColorMapping?.[segmentationLayerScopes?.[0]]?.[segmentationChannelScopesByLayer?.[segmentationLayerScopes?.[0]]?.[0]]
347
- ?? {}), [segmentationColorMapping]);
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 newLayer0 = {
466
- ...prevLayer,
467
- segments: nextSegments,
468
- segmentColors: cellColorMapping,
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: prevSegments.length === 0 ? [newLayer0, ...(current?.layers?.slice(1)
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
- }, [cellColorMapping, spatialZoom, spatialRotationX, spatialRotationY,
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 }) }), hasLayers ? (_jsx(NeuroglancerComp, { classes: classes, onSegmentClick: onSegmentClick, onSelectHoveredCoords: onSegmentHighlight, viewerState: derivedViewerState, cellColorMapping: cellColorMapping, setViewerState: handleStateUpdate })) : null] }) }));
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
- allKnownIds: Set<any>;
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: (cellColorMapping: any) => void;
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,sBAA4B;IAG9B;;;;MASE;IAGF,+BAWE;IAGF,mCAKE;IAGF,uDASE;IAGF,0DAgCE;IAEF,0BA+GC;IAED,yDAgIC;IAED,6BAUC;IAcD,mCAQC;IAED,0DAyCE;IAEF,yCAOE;IAEF,0BAsCE;IA5BI,mCAAyB;IA+B/B,sCAWE;IAEF,qCAUE;IAEF,sBAOC;CACF;;qBAh3Ba,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"}
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.allKnownIds = new Set();
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 = (cellColorMapping) => {
426
+ applyColorsAndVisibility = (cellColorMappingByLayer) => {
427
427
  if (!this.viewer)
428
428
  return;
429
- // Track all ids we've ever seen so we can grey the ones
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, idx) => {
449
- // if only one layer, take that or check layer.type === 'segmentation'.
450
- if (idx === 0 || layer?.type === 'segmentation') {
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 (cellColorMapping && Object.keys(cellColorMapping).length) {
637
- this.applyColorsAndVisibility(cellColorMapping);
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 = cellColorMapping ? Object.keys(cellColorMapping).length : 0;
646
- const mappingRefChanged = prevProps.cellColorMapping !== cellColorMapping;
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(cellColorMapping);
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,kFA8CC;AAED,0FAyEC"}
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"}
@@ -73,6 +73,7 @@ export function customIsEqualForCellColors(prevDeps, nextDeps) {
73
73
  'obsColorEncoding',
74
74
  'obsSetSelection',
75
75
  'additionalObsSets',
76
+ 'spatialChannelColor',
76
77
  ])) {
77
78
  forceUpdate = true;
78
79
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/neuroglancer",
3
- "version": "3.9.6",
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/neuroglancer-workers": "3.9.6",
24
- "@vitessce/vit-s": "3.9.6",
25
- "@vitessce/styles": "3.9.6",
26
- "@vitessce/utils": "3.9.6",
27
- "@vitessce/constants-internal": "3.9.6",
28
- "@vitessce/sets-utils": "3.9.6",
29
- "@vitessce/tooltip": "3.9.6",
30
- "@vitessce/legend": "3.9.6"
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",
@@ -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
- return new ChunkWorker();
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
- viewer.inputEventBindings.sliceView.set('at:dblclick0', () => {});
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
- this.prevClickHandler = (event) => {
40
- if (event.button === 0) {
41
- // Wait for mouseState to update
42
- requestAnimationFrame(() => {
43
- const { pickedValue, pickedRenderLayer } = viewer.mouseState;
44
- // Only trigger selection when a segment is clicked rather than any click on the view
45
- if (pickedValue && pickedValue.low !== undefined && pickedRenderLayer) {
46
- this.latestOnSegmentClick?.(pickedValue.low);
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
- viewer.element.addEventListener('mouseup', this.prevClickHandler);
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 { onSegmentClick, onSelectHoveredCoords } = this.props;
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
  }