@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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { N } from "./index-DZhbMDug.js";
1
+ import { N } from "./index-DslldtW7.js";
2
2
  export {
3
3
  N as NeuroglancerSubscriber
4
4
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Neuroglancer.d.ts","sourceRoot":"","sources":["../src/Neuroglancer.js"],"names":[],"mappings":"AAUA;IACE,wBAYC;IAVC,gBAAgC;IAChC,sBAA8C;IAC9C,oCAAuC;IACvC,2BAAiC;IACjC,sCAA4B;IAwC9B,2CAGC;IAnCD,4BA8BC;IAlCC,0BAAgD;IAChD,iCAA8D;IAwChE,yCAKC;IAED,cAoBC;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,cAqBC;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) {
@@ -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
- 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', () => {});
32
35
  viewer.inputEventBindings.perspectiveView.set('at:dblclick0', () => { });
33
- // To disable space interaction causing 4panels layout
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,wDA0sBC"}
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
- if (layerSets && layerIndex) {
137
- const { obsSetColor, obsColorEncoding, obsSetSelection, additionalObsSets, } = segmentationChannelCoordination[0][layerScope][channelScope];
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
- // For now, we take the first layer and channel for cell colors.
349
- const cellColorMapping = useMemo(() => (segmentationColorMapping?.[segmentationLayerScopes?.[0]]?.[segmentationChannelScopesByLayer?.[segmentationLayerScopes?.[0]]?.[0]]
350
- ?? {}), [segmentationColorMapping]);
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 newLayer0 = {
469
- ...prevLayer,
470
- segments: nextSegments,
471
- segmentColors: cellColorMapping,
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: prevSegments.length === 0 ? [newLayer0, ...(current?.layers?.slice(1)
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
- }, [cellColorMapping, spatialZoom, spatialRotationX, spatialRotationY,
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, segmentationLayerScopes: segmentationLayerScopes, segmentationLayerCoordination: segmentationLayerCoordination, segmentationChannelScopesByLayer: segmentationChannelScopesByLayer, segmentationChannelCoordination: segmentationChannelCoordination }) }), hasLayers ? (_jsx(NeuroglancerComp, { classes: classes, onSegmentClick: onSegmentClick, onSelectHoveredCoords: onSegmentHighlight, viewerState: derivedViewerState, cellColorMapping: cellColorMapping, setViewerState: handleStateUpdate })) : null] }) }));
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
- allKnownIds: Set<any>;
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: (cellColorMapping: any) => void;
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,cAOC;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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.allKnownIds = new Set();
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 = (cellColorMapping) => {
431
+ applyColorsAndVisibility = (cellColorMappingByLayer) => {
427
432
  if (!this.viewer)
428
433
  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)
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, idx) => {
449
- // if only one layer, take that or check layer.type === 'segmentation'.
450
- if (idx === 0 || layer?.type === 'segmentation') {
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
- // TODO: This is purely for debugging and we need to remove it.
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 (cellColorMapping && Object.keys(cellColorMapping).length) {
637
- this.applyColorsAndVisibility(cellColorMapping);
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.keys(prevProps.cellColorMapping).length : 0;
645
- const currSize = cellColorMapping ? Object.keys(cellColorMapping).length : 0;
646
- const mappingRefChanged = prevProps.cellColorMapping !== cellColorMapping;
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(cellColorMapping);
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,yXAiJC"}
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: toPrecomputedSource(layerUrl),
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
- ...(layerData.neuroglancerOptions ?? {}),
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,