@vitessce/neuroglancer 3.9.5 → 3.9.6

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.
@@ -1,5 +1,7 @@
1
- import { useMemo } from 'react';
2
1
  import { DataType } from '@vitessce/constants-internal';
2
+ import { cloneDeep } from 'lodash-es';
3
+ import { useMemoCustomComparison, customIsEqualForInitialViewerState } from './use-memo-custom-comparison.js';
4
+ import { getPointsShader } from './shader-utils.js';
3
5
 
4
6
 
5
7
  export const DEFAULT_NG_PROPS = {
@@ -8,11 +10,17 @@ export const DEFAULT_NG_PROPS = {
8
10
  projectionOrientation: [0, 0, 0, 1],
9
11
  projectionScale: 1024,
10
12
  crossSectionScale: 1,
13
+ dimensions: {
14
+ x: [1, 'nm'],
15
+ y: [1, 'nm'],
16
+ z: [1, 'nm'],
17
+ },
18
+ layers: [],
11
19
  };
12
20
 
13
21
  function toPrecomputedSource(url) {
14
22
  if (!url) {
15
- return undefined;
23
+ throw new Error('toPrecomputedSource: URL is required');
16
24
  }
17
25
  return `precomputed://${url}`;
18
26
  }
@@ -43,8 +51,14 @@ function isInNanometerRange(value, unit, minNm = 1, maxNm = 100) {
43
51
  * @param {object} opts
44
52
  * @returns {{ x:[number,'nm'], y:[number,'nm'], z:[number,'nm'] }}
45
53
  */
46
- function normalizeDimensionsToNanometers(opts) {
47
- const { dimensionUnit, dimensionX, dimensionY, dimensionZ } = opts;
54
+ export function normalizeDimensionsToNanometers(opts) {
55
+ const {
56
+ dimensionUnit,
57
+ dimensionX,
58
+ dimensionY,
59
+ dimensionZ,
60
+ ...otherOptions
61
+ } = opts;
48
62
 
49
63
  if (!dimensionUnit || !dimensionX || !dimensionY || !dimensionZ) {
50
64
  console.warn('Missing dimension info');
@@ -56,82 +70,28 @@ function normalizeDimensionsToNanometers(opts) {
56
70
  console.warn('Dimension was converted to nm units');
57
71
  }
58
72
  return {
59
- x: xNm ? [dimensionX, dimensionUnit] : [1, 'nm'],
60
- y: yNm ? [dimensionY, dimensionUnit] : [1, 'nm'],
61
- z: zNm ? [dimensionZ, dimensionUnit] : [1, 'nm'],
73
+ // The dimension-related fields are formatted differently in the fileDef.options
74
+ // vs. what the viewerState expects.
75
+ dimensions: {
76
+ x: xNm ? [dimensionX, dimensionUnit] : [1, 'nm'],
77
+ y: yNm ? [dimensionY, dimensionUnit] : [1, 'nm'],
78
+ z: zNm ? [dimensionZ, dimensionUnit] : [1, 'nm'],
79
+ },
80
+ // The non-dimension-related options can be passed through without modification.
81
+ ...otherOptions,
62
82
  };
63
83
  }
64
84
 
65
- export function extractDataTypeEntities(loaders, dataset, dataType) {
66
- const datasetEntry = loaders?.[dataset];
67
- const internMap = datasetEntry?.loaders?.[dataType];
68
- if (!internMap || typeof internMap.entries !== 'function') return [];
69
-
70
- return Array.from(internMap.entries()).map(([key, loader]) => {
71
- const url = loader?.url ?? loader?.dataSource?.url ?? undefined;
72
- const fileUid = key?.fileUid
73
- ?? loader?.coordinationValues?.fileUid
74
- ?? undefined;
75
-
76
- const { position, projectionOrientation,
77
- projectionScale, crossSectionScale } = loader?.options ?? {};
78
- const isPrecomputed = loader?.fileType.includes('precomputed');
79
- if (!isPrecomputed) {
80
- console.warn('Filetype needs to be precomputed');
81
- }
82
- return {
83
- key,
84
- type: 'segmentation',
85
- fileUid,
86
- layout: DEFAULT_NG_PROPS.layout,
87
- url,
88
- source: toPrecomputedSource(url),
89
- name: fileUid ?? key?.name ?? 'segmentation',
90
- // For precomputed: nm is the unit used
91
- dimensions: normalizeDimensionsToNanometers(loader?.options),
92
- // If not provided, no error, but difficult to see the data
93
- position: Array.isArray(position) && position.length === 3
94
- ? position : DEFAULT_NG_PROPS.position,
95
- // If not provided, will have a default orientation
96
- projectionOrientation: Array.isArray(projectionOrientation)
97
- && projectionOrientation.length === 4
98
- ? projectionOrientation : DEFAULT_NG_PROPS.projectionOrientation,
99
- projectionScale: Number.isFinite(projectionScale)
100
- ? projectionScale : DEFAULT_NG_PROPS.projectionScale,
101
- crossSectionScale: Number.isFinite(crossSectionScale)
102
- ? crossSectionScale : DEFAULT_NG_PROPS.crossSectionScale,
103
- };
104
- });
105
- }
106
-
107
- export function useExtractOptionsForNg(loaders, dataset, dataType) {
108
- const extractedEntities = useMemo(
109
- () => extractDataTypeEntities(loaders, dataset, dataType),
110
- [loaders, dataset, dataType],
111
- );
112
- const layers = useMemo(() => extractedEntities
113
- .filter(t => t.source)
114
- .map(t => ({
115
- type: t.type,
116
- source: t.source,
117
- segments: [],
118
- name: t.name || 'segmentation',
119
- })), [extractedEntities]);
120
-
121
- const viewerState = useMemo(() => ({
122
- dimensions: extractedEntities[0]?.dimensions,
123
- position: extractedEntities[0]?.position,
124
- crossSectionScale: extractedEntities[0]?.crossSectionScale,
125
- projectionOrientation: extractedEntities[0]?.projectionOrientation,
126
- projectionScale: extractedEntities[0]?.projectionScale,
127
- layers,
128
- layout: extractedEntities[0].layout,
129
- }));
130
-
131
- return [viewerState];
85
+ export function toNgLayerName(dataType, layerScope, channelScope = null) {
86
+ if (dataType === DataType.OBS_SEGMENTATIONS) {
87
+ return `obsSegmentations-${layerScope}-${channelScope}`;
88
+ }
89
+ if (dataType === DataType.OBS_POINTS) {
90
+ return `obsPoints-${layerScope}`;
91
+ }
92
+ throw new Error(`Unsupported data type: ${dataType}`);
132
93
  }
133
94
 
134
-
135
95
  /**
136
96
  * Get the parameters for NG's viewerstate.
137
97
  * @param {object} loaders The object mapping
@@ -145,8 +105,148 @@ export function useExtractOptionsForNg(loaders, dataset, dataType) {
145
105
  * @returns [viewerState]
146
106
  */
147
107
  export function useNeuroglancerViewerState(
148
- loaders, dataset, isRequired,
149
- coordinationSetters, initialCoordinationValues, matchOn,
108
+ theme,
109
+ segmentationLayerScopes,
110
+ segmentationChannelScopesByLayer,
111
+ segmentationLayerCoordination,
112
+ segmentationChannelCoordination,
113
+ obsSegmentationsUrls,
114
+ obsSegmentationsData,
115
+ pointLayerScopes,
116
+ pointLayerCoordination,
117
+ obsPointsUrls,
118
+ obsPointsData,
119
+ pointMultiIndicesData,
150
120
  ) {
151
- return useExtractOptionsForNg(loaders, dataset, DataType.OBS_SEGMENTATIONS, matchOn);
121
+ const viewerState = useMemoCustomComparison(() => {
122
+ let result = cloneDeep(DEFAULT_NG_PROPS);
123
+
124
+ // ======= SEGMENTATIONS =======
125
+
126
+ // Iterate over segmentation layers and channels.
127
+ segmentationLayerScopes.forEach((layerScope) => {
128
+ const layerCoordination = segmentationLayerCoordination[0][layerScope];
129
+ const channelScopes = segmentationChannelScopesByLayer[layerScope] || [];
130
+ const layerData = obsSegmentationsData[layerScope];
131
+ const layerUrl = obsSegmentationsUrls[layerScope]?.[0]?.url;
132
+
133
+ if (layerUrl && layerData) {
134
+ const {
135
+ spatialLayerVisible,
136
+ } = layerCoordination || {};
137
+ channelScopes.forEach((channelScope) => {
138
+ const channelCoordination = segmentationChannelCoordination[0]
139
+ ?.[layerScope]?.[channelScope];
140
+ const {
141
+ spatialChannelVisible,
142
+ } = channelCoordination || {};
143
+ result = {
144
+ ...result,
145
+ layers: [
146
+ ...result.layers,
147
+ {
148
+ type: 'segmentation',
149
+ source: toPrecomputedSource(layerUrl),
150
+ segments: [],
151
+ name: toNgLayerName(DataType.OBS_SEGMENTATIONS, layerScope, channelScope),
152
+ visible: spatialLayerVisible && spatialChannelVisible, // Both layer and channel
153
+ // visibility must be true for the layer to be visible.
154
+ // TODO: update this to extract specific properties from
155
+ // neuroglancerOptions as needed.
156
+ ...(layerData.neuroglancerOptions ?? {}),
157
+ },
158
+ ],
159
+ };
160
+ });
161
+ }
162
+ });
163
+
164
+ // ======= POINTS =======
165
+
166
+ // Iterate over point layers.
167
+ pointLayerScopes.forEach((layerScope) => {
168
+ const layerCoordination = pointLayerCoordination[0][layerScope];
169
+ const layerData = obsPointsData[layerScope];
170
+ const layerUrl = obsPointsUrls[layerScope]?.[0]?.url;
171
+
172
+ const featureIndex = pointMultiIndicesData[layerScope]?.featureIndex;
173
+
174
+ if (layerUrl && layerData) {
175
+ const {
176
+ spatialLayerVisible,
177
+ spatialLayerOpacity,
178
+ obsColorEncoding,
179
+ spatialLayerColor,
180
+ featureSelection,
181
+ featureFilterMode,
182
+ featureColor,
183
+ } = layerCoordination || {};
184
+
185
+ // Dynamically construct the shader based on the color encoding
186
+ // and other coordination values.
187
+ const shader = getPointsShader({
188
+ theme,
189
+ featureIndex,
190
+ spatialLayerOpacity,
191
+ obsColorEncoding,
192
+ spatialLayerColor,
193
+ featureSelection,
194
+ featureFilterMode,
195
+ featureColor,
196
+
197
+ featureIndexProp: layerData.neuroglancerOptions?.featureIndexProp,
198
+ pointIndexProp: layerData.neuroglancerOptions?.pointIndexProp,
199
+ });
200
+
201
+ result = {
202
+ ...result,
203
+ layers: [
204
+ ...result.layers,
205
+ {
206
+ type: 'annotation',
207
+ source: {
208
+ url: toPrecomputedSource(layerUrl),
209
+ subsources: {
210
+ default: true,
211
+ },
212
+ enableDefaultSubsources: false,
213
+ },
214
+ tab: 'annotations',
215
+ shader,
216
+ name: toNgLayerName(DataType.OBS_POINTS, layerScope),
217
+ visible: spatialLayerVisible,
218
+ // Options from layerData.neuroglancerOptions
219
+ // like projectionAnnotationSpacing:
220
+ projectionAnnotationSpacing: layerData.neuroglancerOptions
221
+ ?.projectionAnnotationSpacing ?? 1.0,
222
+ },
223
+ ],
224
+
225
+ // TODO: is this needed?
226
+ // The selected layer here will overwrite anything
227
+ // that was previously specified.
228
+ selectedLayer: {
229
+ // size: ? // TODO: is this needed?
230
+ layer: toNgLayerName(DataType.OBS_POINTS, layerScope),
231
+ },
232
+ };
233
+ }
234
+ });
235
+ return result;
236
+ }, {
237
+ theme,
238
+ segmentationLayerScopes,
239
+ segmentationChannelScopesByLayer,
240
+ segmentationLayerCoordination,
241
+ segmentationChannelCoordination,
242
+ obsSegmentationsUrls,
243
+ obsSegmentationsData,
244
+ pointLayerScopes,
245
+ pointLayerCoordination,
246
+ obsPointsUrls,
247
+ obsPointsData,
248
+ pointMultiIndicesData,
249
+ }, customIsEqualForInitialViewerState);
250
+
251
+ return viewerState;
152
252
  }