@vitessce/neuroglancer 3.9.4 → 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.
- package/dist/{ReactNeuroglancer-BCg93QGV.js → ReactNeuroglancer-BSLfuCt9.js} +1 -1
- package/dist/{index-Wdrc02VW.js → index-DvhFVdN_.js} +15782 -10346
- package/dist/index.js +1 -1
- package/dist-tsc/NeuroglancerSubscriber.d.ts.map +1 -1
- package/dist-tsc/NeuroglancerSubscriber.js +182 -43
- package/dist-tsc/data-hook-ng-utils.d.ts +18 -20
- package/dist-tsc/data-hook-ng-utils.d.ts.map +1 -1
- package/dist-tsc/data-hook-ng-utils.js +136 -68
- package/dist-tsc/shader-utils.d.ts +126 -0
- package/dist-tsc/shader-utils.d.ts.map +1 -0
- package/dist-tsc/shader-utils.js +547 -0
- package/dist-tsc/shader-utils.test.d.ts +2 -0
- package/dist-tsc/shader-utils.test.d.ts.map +1 -0
- package/dist-tsc/shader-utils.test.js +364 -0
- package/dist-tsc/use-memo-custom-comparison.d.ts +14 -0
- package/dist-tsc/use-memo-custom-comparison.d.ts.map +1 -0
- package/dist-tsc/use-memo-custom-comparison.js +149 -0
- package/package.json +9 -8
- package/src/NeuroglancerSubscriber.js +320 -69
- package/src/README.md +28 -0
- package/src/data-hook-ng-utils.js +178 -78
- package/src/shader-utils.js +653 -0
- package/src/shader-utils.test.js +432 -0
- package/src/use-memo-custom-comparison.js +189 -0
- package/dist-tsc/data-hook-ng-utils.test.d.ts +0 -2
- package/dist-tsc/data-hook-ng-utils.test.d.ts.map +0 -1
- package/dist-tsc/data-hook-ng-utils.test.js +0 -35
- package/src/data-hook-ng-utils.test.js +0 -52
|
@@ -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
|
-
|
|
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 {
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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
|
}
|