@vitessce/neuroglancer 3.9.7 → 3.9.8
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-pv4bM8Yp.js → ReactNeuroglancer-Bxe4YcLF.js} +64 -9
- package/dist/{index-BEPd2Tds.js → index-anGvS-pL.js} +120 -46
- package/dist/index.js +1 -1
- package/dist-tsc/Neuroglancer.d.ts.map +1 -1
- package/dist-tsc/Neuroglancer.js +2 -2
- package/dist-tsc/NeuroglancerSubscriber.d.ts.map +1 -1
- package/dist-tsc/NeuroglancerSubscriber.js +22 -6
- package/dist-tsc/ReactNeuroglancer.d.ts +11 -0
- package/dist-tsc/ReactNeuroglancer.d.ts.map +1 -1
- package/dist-tsc/ReactNeuroglancer.js +60 -5
- package/dist-tsc/data-hook-ng-utils.d.ts +1 -1
- package/dist-tsc/data-hook-ng-utils.d.ts.map +1 -1
- package/dist-tsc/data-hook-ng-utils.js +18 -4
- package/dist-tsc/shader-utils.d.ts +12 -12
- package/dist-tsc/shader-utils.d.ts.map +1 -1
- package/dist-tsc/shader-utils.js +51 -26
- package/dist-tsc/shader-utils.test.js +20 -0
- package/dist-tsc/use-memo-custom-comparison.d.ts.map +1 -1
- package/dist-tsc/use-memo-custom-comparison.js +5 -0
- package/package.json +9 -9
- package/src/Neuroglancer.js +2 -1
- package/src/NeuroglancerSubscriber.js +28 -4
- package/src/ReactNeuroglancer.js +67 -5
- package/src/data-hook-ng-utils.js +21 -2
- package/src/shader-utils.js +79 -26
- package/src/shader-utils.test.js +20 -0
- package/src/use-memo-custom-comparison.js +6 -0
package/src/ReactNeuroglancer.js
CHANGED
|
@@ -67,6 +67,10 @@ let viewerNoKey;
|
|
|
67
67
|
* @property {() => void} onSelectionDetailsStateChanged
|
|
68
68
|
* A function of the form `() => {}` to respond to selection changes in the viewer.
|
|
69
69
|
* @property {() => void} onViewerStateChanged
|
|
70
|
+
* @property {(isLoaded: boolean) => void} onLayerLoadingChange
|
|
71
|
+
* A function of the form `(isLoaded) => {}`, called when layer loading state changes.
|
|
72
|
+
* The `isLoaded` argument will be `true` when all segmentation layers have finished loading
|
|
73
|
+
* their data sources, or `false` when layers are still loading.
|
|
70
74
|
*
|
|
71
75
|
* @property {Array<Object>} callbacks
|
|
72
76
|
* // ngServer: string,
|
|
@@ -411,6 +415,7 @@ export default class Neuroglancer extends React.Component {
|
|
|
411
415
|
onVisibleChanged: null,
|
|
412
416
|
onSelectionDetailsStateChanged: null,
|
|
413
417
|
onViewerStateChanged: null,
|
|
418
|
+
onLayerLoadingChange: null,
|
|
414
419
|
key: null,
|
|
415
420
|
callbacks: [],
|
|
416
421
|
ngServer: 'https://neuroglancer-demo.appspot.com/',
|
|
@@ -488,7 +493,7 @@ export default class Neuroglancer extends React.Component {
|
|
|
488
493
|
// "obsSegmentations-init_A_obsSegmentations_0-init_A_obsSegmentations_0"
|
|
489
494
|
const layerScope = Object.keys(cellColorMappingByLayer).find(scope => layer.name?.includes(scope));
|
|
490
495
|
|
|
491
|
-
const selected = { ...(cellColorMappingByLayer[layerScope] || {}) };
|
|
496
|
+
const selected = { ...(cellColorMappingByLayer[layerScope]?.colors || {}) };
|
|
492
497
|
|
|
493
498
|
// Track all known IDs for this layer scope
|
|
494
499
|
if (!this.allKnownIdsByLayer) this.allKnownIdsByLayer = {};
|
|
@@ -625,7 +630,41 @@ export default class Neuroglancer extends React.Component {
|
|
|
625
630
|
viewerNoKey = this.viewer;
|
|
626
631
|
}
|
|
627
632
|
|
|
628
|
-
|
|
633
|
+
const { visibleChunksChanged } = this.viewer.chunkQueueManager;
|
|
634
|
+
let firstChunkLoaded = false;
|
|
635
|
+
this.disposers.push(visibleChunksChanged.add(() => {
|
|
636
|
+
if (!firstChunkLoaded) {
|
|
637
|
+
for (const layer of this.viewer.layerManager.managedLayers) {
|
|
638
|
+
if (layer.layer instanceof SegmentationUserLayer) {
|
|
639
|
+
const hasVisibleChunk = layer.layer.renderLayers?.some((rl) => {
|
|
640
|
+
const {
|
|
641
|
+
numVisibleChunksAvailable,
|
|
642
|
+
numVisibleChunksNeeded,
|
|
643
|
+
} = rl.layerChunkProgressInfo || {};
|
|
644
|
+
if (!numVisibleChunksNeeded || !numVisibleChunksAvailable) return false;
|
|
645
|
+
// Neuroglancer only shows chunks when a certain % is loaded.
|
|
646
|
+
// The 0.25 is from testing different values, can be reduced to 0.2 to shorten loader time
|
|
647
|
+
return (numVisibleChunksAvailable / numVisibleChunksNeeded) > 0.25;
|
|
648
|
+
});
|
|
649
|
+
if (hasVisibleChunk) {
|
|
650
|
+
firstChunkLoaded = true;
|
|
651
|
+
// Two frames to avoid flash while the following two happens
|
|
652
|
+
// Neuroglancer issues WebGL draw calls
|
|
653
|
+
requestAnimationFrame(() => {
|
|
654
|
+
// GPU has painted, pixels visible on screen
|
|
655
|
+
requestAnimationFrame(() => {
|
|
656
|
+
this.props.onLayerLoadingChange?.(true);
|
|
657
|
+
});
|
|
658
|
+
});
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}));
|
|
665
|
+
this.disposers.push(() => { firstChunkLoaded = false; });
|
|
666
|
+
|
|
667
|
+
// TODO: This is purely for debugging - exposes the NG viewer to be tested via console
|
|
629
668
|
// window.viewer = this.viewer;
|
|
630
669
|
}
|
|
631
670
|
|
|
@@ -667,6 +706,27 @@ export default class Neuroglancer extends React.Component {
|
|
|
667
706
|
if (layer.layer instanceof SegmentationUserLayer) {
|
|
668
707
|
const { segmentSelectionState } = layer.layer.displayState;
|
|
669
708
|
segmentSelectionState.set(selectedSegments[layer.name]);
|
|
709
|
+
const layerScope = Object.keys(cellColorMappingByLayer).find(
|
|
710
|
+
scope => layer.name?.includes(scope),
|
|
711
|
+
);
|
|
712
|
+
if (layerScope) {
|
|
713
|
+
const opacity = cellColorMappingByLayer[layerScope]?.opacity ?? 1.0;
|
|
714
|
+
layer.layer.displayState.objectAlpha.value = opacity;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
// Update annotation layer shaders from viewerState config,
|
|
718
|
+
// skipping update if shader is unchanged to avoid costly re-renders
|
|
719
|
+
if (layer.layer instanceof AnnotationUserLayer) {
|
|
720
|
+
const matchingLayer = (viewerState?.layers || []).find(
|
|
721
|
+
l => l.name === layer.name,
|
|
722
|
+
);
|
|
723
|
+
if (matchingLayer?.shader) {
|
|
724
|
+
/* eslint-disable-next-line no-underscore-dangle */
|
|
725
|
+
const currentShader = layer.layer.annotationDisplayState.shader.value_;
|
|
726
|
+
if (currentShader !== matchingLayer.shader) {
|
|
727
|
+
layer.layer.annotationDisplayState.shader.value = matchingLayer.shader;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
670
730
|
}
|
|
671
731
|
}
|
|
672
732
|
|
|
@@ -717,9 +777,11 @@ export default class Neuroglancer extends React.Component {
|
|
|
717
777
|
// If colors changed (but layers didn’t): re-apply colors
|
|
718
778
|
// this was to avid NG randomly assigning colors to the segments by resetting them
|
|
719
779
|
const prevSize = prevProps.cellColorMapping
|
|
720
|
-
? Object.
|
|
780
|
+
? Object.values(prevProps.cellColorMapping)
|
|
781
|
+
.reduce((acc, v) => acc + Object.keys(v?.colors || {}).length, 0) : 0;
|
|
721
782
|
const currSize = cellColorMappingByLayer
|
|
722
|
-
? Object.
|
|
783
|
+
? Object.values(cellColorMappingByLayer)
|
|
784
|
+
.reduce((acc, v) => acc + Object.keys(v?.colors || {}).length, 0) : 0;
|
|
723
785
|
const mappingRefChanged = prevProps.cellColorMapping !== this.props.cellColorMapping;
|
|
724
786
|
if (!this.didLayersChange(prevVS, viewerState)
|
|
725
787
|
&& (mappingRefChanged || prevSize !== currSize)) {
|
|
@@ -732,7 +794,7 @@ export default class Neuroglancer extends React.Component {
|
|
|
732
794
|
// We only restore layers (not pose) when sources change OR on the first time segments appear.
|
|
733
795
|
const stripSegFields = layers => (layers || []).map((l) => {
|
|
734
796
|
if (!l) return l;
|
|
735
|
-
const { segments, segmentColors, ...rest } = l;
|
|
797
|
+
const { segments, segmentColors, objectAlpha, ...rest } = l;
|
|
736
798
|
return rest; // ignore segments + segmentColors for comparison
|
|
737
799
|
});
|
|
738
800
|
|
|
@@ -106,6 +106,7 @@ export function toNgLayerName(dataType, layerScope, channelScope = null) {
|
|
|
106
106
|
*/
|
|
107
107
|
export function useNeuroglancerViewerState(
|
|
108
108
|
theme,
|
|
109
|
+
showAxisLines,
|
|
109
110
|
segmentationLayerScopes,
|
|
110
111
|
segmentationChannelScopesByLayer,
|
|
111
112
|
segmentationLayerCoordination,
|
|
@@ -140,20 +141,35 @@ export function useNeuroglancerViewerState(
|
|
|
140
141
|
const {
|
|
141
142
|
spatialChannelVisible,
|
|
142
143
|
} = channelCoordination || {};
|
|
144
|
+
const { source: ngSource, ...otherNgOptions } = layerData.neuroglancerOptions ?? {};
|
|
145
|
+
|
|
146
|
+
// Build source: if neuroglancerOptions has subsources
|
|
147
|
+
const hasNgSourceOptions = layerData.neuroglancerOptions?.subsources
|
|
148
|
+
|| layerData.neuroglancerOptions?.enableDefaultSubsources !== undefined;
|
|
149
|
+
|
|
150
|
+
const source = hasNgSourceOptions
|
|
151
|
+
? {
|
|
152
|
+
url: toPrecomputedSource(layerUrl),
|
|
153
|
+
subsources: layerData.neuroglancerOptions.subsources,
|
|
154
|
+
enableDefaultSubsources: layerData.neuroglancerOptions.enableDefaultSubsources
|
|
155
|
+
?? false,
|
|
156
|
+
}
|
|
157
|
+
: toPrecomputedSource(layerUrl);
|
|
158
|
+
|
|
143
159
|
result = {
|
|
144
160
|
...result,
|
|
145
161
|
layers: [
|
|
146
162
|
...result.layers,
|
|
147
163
|
{
|
|
148
164
|
type: 'segmentation',
|
|
149
|
-
source
|
|
165
|
+
source,
|
|
150
166
|
segments: [],
|
|
151
167
|
name: toNgLayerName(DataType.OBS_SEGMENTATIONS, layerScope, channelScope),
|
|
152
168
|
visible: spatialLayerVisible && spatialChannelVisible, // Both layer and channel
|
|
153
169
|
// visibility must be true for the layer to be visible.
|
|
154
170
|
// TODO: update this to extract specific properties from
|
|
155
171
|
// neuroglancerOptions as needed.
|
|
156
|
-
...
|
|
172
|
+
...otherNgOptions,
|
|
157
173
|
},
|
|
158
174
|
],
|
|
159
175
|
};
|
|
@@ -180,6 +196,7 @@ export function useNeuroglancerViewerState(
|
|
|
180
196
|
featureSelection,
|
|
181
197
|
featureFilterMode,
|
|
182
198
|
featureColor,
|
|
199
|
+
spatialPointStrokeWidth,
|
|
183
200
|
} = layerCoordination || {};
|
|
184
201
|
|
|
185
202
|
// Dynamically construct the shader based on the color encoding
|
|
@@ -196,6 +213,7 @@ export function useNeuroglancerViewerState(
|
|
|
196
213
|
|
|
197
214
|
featureIndexProp: layerData.neuroglancerOptions?.featureIndexProp,
|
|
198
215
|
pointIndexProp: layerData.neuroglancerOptions?.pointIndexProp,
|
|
216
|
+
pointMarkerBorderWidth: spatialPointStrokeWidth ?? 0.0,
|
|
199
217
|
});
|
|
200
218
|
|
|
201
219
|
result = {
|
|
@@ -235,6 +253,7 @@ export function useNeuroglancerViewerState(
|
|
|
235
253
|
return result;
|
|
236
254
|
}, {
|
|
237
255
|
theme,
|
|
256
|
+
showAxisLines,
|
|
238
257
|
segmentationLayerScopes,
|
|
239
258
|
segmentationChannelScopesByLayer,
|
|
240
259
|
segmentationLayerCoordination,
|
package/src/shader-utils.js
CHANGED
|
@@ -15,6 +15,18 @@ function normalizeColor(rgbColor) {
|
|
|
15
15
|
return rgbColor.map(c => c / 255);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* GLSL call to set point marker border width.
|
|
20
|
+
* Set to 0.0 to remove the outline.
|
|
21
|
+
* @param {number} borderWidth
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
function borderWidthGlsl(borderWidth = 0.0) {
|
|
25
|
+
// must be decimal/float value
|
|
26
|
+
return `setPointMarkerBorderWidth(${borderWidth.toFixed(1)});`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
18
30
|
/**
|
|
19
31
|
* Format a normalized color as a GLSL vec3 literal.
|
|
20
32
|
* @param {[number, number, number]} normalizedColor
|
|
@@ -45,12 +57,13 @@ function toVec4(normalizedColor, alpha) {
|
|
|
45
57
|
* @param {number} opacity Opacity (0-1).
|
|
46
58
|
* @returns {string} A GLSL shader string.
|
|
47
59
|
*/
|
|
48
|
-
export function getSpatialLayerColorShader(staticColor, opacity) {
|
|
60
|
+
export function getSpatialLayerColorShader(staticColor, opacity, borderWidth = 0.0) {
|
|
49
61
|
const norm = normalizeColor(staticColor);
|
|
50
62
|
// lang: glsl
|
|
51
63
|
return `
|
|
52
64
|
void main() {
|
|
53
65
|
setColor(${toVec4(norm, opacity)});
|
|
66
|
+
${borderWidthGlsl(borderWidth)}
|
|
54
67
|
}
|
|
55
68
|
`;
|
|
56
69
|
}
|
|
@@ -67,7 +80,7 @@ export function getSpatialLayerColorShader(staticColor, opacity) {
|
|
|
67
80
|
* @returns {string} A GLSL shader string.
|
|
68
81
|
*/
|
|
69
82
|
export function getSpatialLayerColorWithSelectionShader(
|
|
70
|
-
staticColor, opacity, featureIndices, defaultColor, featureIndexProp,
|
|
83
|
+
staticColor, opacity, featureIndices, defaultColor, featureIndexProp, borderWidth = 0.0,
|
|
71
84
|
) {
|
|
72
85
|
const normStatic = normalizeColor(staticColor);
|
|
73
86
|
const normDefault = normalizeColor(defaultColor);
|
|
@@ -87,8 +100,10 @@ export function getSpatialLayerColorWithSelectionShader(
|
|
|
87
100
|
}
|
|
88
101
|
if (isSelected) {
|
|
89
102
|
setColor(${toVec4(normStatic, opacity)});
|
|
103
|
+
${borderWidthGlsl(borderWidth)}
|
|
90
104
|
} else {
|
|
91
105
|
setColor(${toVec4(normDefault, opacity)});
|
|
106
|
+
${borderWidthGlsl(borderWidth)}
|
|
92
107
|
}
|
|
93
108
|
}
|
|
94
109
|
`;
|
|
@@ -105,7 +120,7 @@ export function getSpatialLayerColorWithSelectionShader(
|
|
|
105
120
|
* @returns {string} A GLSL shader string.
|
|
106
121
|
*/
|
|
107
122
|
export function getSpatialLayerColorFilteredShader(
|
|
108
|
-
staticColor, opacity, featureIndices, featureIndexProp,
|
|
123
|
+
staticColor, opacity, featureIndices, featureIndexProp, borderWidth = 0.0,
|
|
109
124
|
) {
|
|
110
125
|
const normStatic = normalizeColor(staticColor);
|
|
111
126
|
const numFeatures = featureIndices.length;
|
|
@@ -126,6 +141,7 @@ export function getSpatialLayerColorFilteredShader(
|
|
|
126
141
|
discard;
|
|
127
142
|
}
|
|
128
143
|
setColor(${toVec4(normStatic, opacity)});
|
|
144
|
+
${borderWidthGlsl(borderWidth)}
|
|
129
145
|
}
|
|
130
146
|
`;
|
|
131
147
|
}
|
|
@@ -142,12 +158,13 @@ export function getSpatialLayerColorFilteredShader(
|
|
|
142
158
|
* @param {number} opacity Opacity (0-1).
|
|
143
159
|
* @returns {string} A GLSL shader string.
|
|
144
160
|
*/
|
|
145
|
-
export function getGeneSelectionNoSelectionShader(staticColor, opacity) {
|
|
161
|
+
export function getGeneSelectionNoSelectionShader(staticColor, opacity, borderWidth = 0.0) {
|
|
146
162
|
const norm = normalizeColor(staticColor);
|
|
147
163
|
// lang: glsl
|
|
148
164
|
return `
|
|
149
165
|
void main() {
|
|
150
166
|
setColor(${toVec4(norm, opacity)});
|
|
167
|
+
${borderWidthGlsl(borderWidth)}
|
|
151
168
|
}
|
|
152
169
|
`;
|
|
153
170
|
}
|
|
@@ -168,7 +185,13 @@ export function getGeneSelectionNoSelectionShader(staticColor, opacity) {
|
|
|
168
185
|
* @returns {string} A GLSL shader string.
|
|
169
186
|
*/
|
|
170
187
|
export function getGeneSelectionWithSelectionShader(
|
|
171
|
-
featureIndices,
|
|
188
|
+
featureIndices,
|
|
189
|
+
featureColors,
|
|
190
|
+
staticColor,
|
|
191
|
+
defaultColor,
|
|
192
|
+
opacity,
|
|
193
|
+
featureIndexProp,
|
|
194
|
+
borderWidth = 0.0,
|
|
172
195
|
) {
|
|
173
196
|
const numFeatures = featureIndices.length;
|
|
174
197
|
const normDefault = normalizeColor(defaultColor);
|
|
@@ -194,6 +217,7 @@ export function getGeneSelectionWithSelectionShader(
|
|
|
194
217
|
}
|
|
195
218
|
}
|
|
196
219
|
setColor(color);
|
|
220
|
+
${borderWidthGlsl(borderWidth)}
|
|
197
221
|
}
|
|
198
222
|
`;
|
|
199
223
|
}
|
|
@@ -210,7 +234,7 @@ export function getGeneSelectionWithSelectionShader(
|
|
|
210
234
|
* @returns {string} A GLSL shader string.
|
|
211
235
|
*/
|
|
212
236
|
export function getGeneSelectionFilteredShader(
|
|
213
|
-
featureIndices, featureColors, staticColor, opacity, featureIndexProp,
|
|
237
|
+
featureIndices, featureColors, staticColor, opacity, featureIndexProp, borderWidth = 0.0,
|
|
214
238
|
) {
|
|
215
239
|
const numFeatures = featureIndices.length;
|
|
216
240
|
const normColors = featureColors.map(c => normalizeColor(c));
|
|
@@ -241,6 +265,7 @@ export function getGeneSelectionFilteredShader(
|
|
|
241
265
|
discard;
|
|
242
266
|
}
|
|
243
267
|
setColor(vec4(matchedColor, ${opacity}));
|
|
268
|
+
${borderWidthGlsl(borderWidth)}
|
|
244
269
|
}
|
|
245
270
|
`;
|
|
246
271
|
}
|
|
@@ -256,7 +281,7 @@ export function getGeneSelectionFilteredShader(
|
|
|
256
281
|
* @param {string} featureIndexProp The property name for the feature index in the shader.
|
|
257
282
|
* @returns {string} A GLSL shader string.
|
|
258
283
|
*/
|
|
259
|
-
export function getRandomByFeatureShader(opacity, featureIndexProp) {
|
|
284
|
+
export function getRandomByFeatureShader(opacity, featureIndexProp, borderWidth = 0.0) {
|
|
260
285
|
const paletteSize = PALETTE.length;
|
|
261
286
|
const normPalette = PALETTE.map(c => normalizeColor(c));
|
|
262
287
|
const paletteDecl = `vec3 palette[${paletteSize}] = vec3[${paletteSize}](${normPalette.map(c => toVec3(c)).join(', ')});`;
|
|
@@ -270,6 +295,7 @@ export function getRandomByFeatureShader(opacity, featureIndexProp) {
|
|
|
270
295
|
if (colorIdx < 0) { colorIdx = -colorIdx; }
|
|
271
296
|
vec3 color = palette[colorIdx];
|
|
272
297
|
setColor(vec4(color, ${opacity}));
|
|
298
|
+
${borderWidthGlsl(borderWidth)}
|
|
273
299
|
}
|
|
274
300
|
`;
|
|
275
301
|
}
|
|
@@ -285,7 +311,7 @@ export function getRandomByFeatureShader(opacity, featureIndexProp) {
|
|
|
285
311
|
* @returns {string} A GLSL shader string.
|
|
286
312
|
*/
|
|
287
313
|
export function getRandomByFeatureWithSelectionShader(
|
|
288
|
-
featureIndices, defaultColor, opacity, featureIndexProp,
|
|
314
|
+
featureIndices, defaultColor, opacity, featureIndexProp, borderWidth = 0.0,
|
|
289
315
|
) {
|
|
290
316
|
const paletteSize = PALETTE.length;
|
|
291
317
|
const normPalette = PALETTE.map(c => normalizeColor(c));
|
|
@@ -311,8 +337,10 @@ export function getRandomByFeatureWithSelectionShader(
|
|
|
311
337
|
int colorIdx = geneIndex - (geneIndex / ${paletteSize}) * ${paletteSize};
|
|
312
338
|
if (colorIdx < 0) { colorIdx = -colorIdx; }
|
|
313
339
|
setColor(vec4(palette[colorIdx], ${opacity}));
|
|
340
|
+
${borderWidthGlsl(borderWidth)}
|
|
314
341
|
} else {
|
|
315
342
|
setColor(${toVec4(normDefault, opacity)});
|
|
343
|
+
${borderWidthGlsl(borderWidth)}
|
|
316
344
|
}
|
|
317
345
|
}
|
|
318
346
|
`;
|
|
@@ -326,7 +354,12 @@ export function getRandomByFeatureWithSelectionShader(
|
|
|
326
354
|
* @param {string} featureIndexProp The property name for the feature index in the shader.
|
|
327
355
|
* @returns {string} A GLSL shader string.
|
|
328
356
|
*/
|
|
329
|
-
export function getRandomByFeatureFilteredShader(
|
|
357
|
+
export function getRandomByFeatureFilteredShader(
|
|
358
|
+
featureIndices,
|
|
359
|
+
opacity,
|
|
360
|
+
featureIndexProp,
|
|
361
|
+
borderWidth = 0.0,
|
|
362
|
+
) {
|
|
330
363
|
const paletteSize = PALETTE.length;
|
|
331
364
|
const normPalette = PALETTE.map(c => normalizeColor(c));
|
|
332
365
|
const numFeatures = featureIndices.length;
|
|
@@ -352,6 +385,7 @@ export function getRandomByFeatureFilteredShader(featureIndices, opacity, featur
|
|
|
352
385
|
int colorIdx = geneIndex - (geneIndex / ${paletteSize}) * ${paletteSize};
|
|
353
386
|
if (colorIdx < 0) { colorIdx = -colorIdx; }
|
|
354
387
|
setColor(vec4(palette[colorIdx], ${opacity}));
|
|
388
|
+
${borderWidthGlsl(borderWidth)}
|
|
355
389
|
}
|
|
356
390
|
`;
|
|
357
391
|
}
|
|
@@ -385,7 +419,12 @@ function hashToFloatGlsl() {
|
|
|
385
419
|
* @param {string} pointIndexProp The property name for the point index in the shader.
|
|
386
420
|
* @returns {string} A GLSL shader string.
|
|
387
421
|
*/
|
|
388
|
-
export function getRandomPerPointShader(
|
|
422
|
+
export function getRandomPerPointShader(
|
|
423
|
+
opacity,
|
|
424
|
+
featureIndexProp,
|
|
425
|
+
pointIndexProp,
|
|
426
|
+
borderWidth = 0.0,
|
|
427
|
+
) {
|
|
389
428
|
// lang: glsl
|
|
390
429
|
return `
|
|
391
430
|
${hashToFloatGlsl()}
|
|
@@ -396,6 +435,7 @@ export function getRandomPerPointShader(opacity, featureIndexProp, pointIndexPro
|
|
|
396
435
|
float g = hashToFloat(pointIndex, 1);
|
|
397
436
|
float b = hashToFloat(pointIndex, 2);
|
|
398
437
|
setColor(vec4(r, g, b, ${opacity}));
|
|
438
|
+
${borderWidthGlsl(borderWidth)}
|
|
399
439
|
}
|
|
400
440
|
`;
|
|
401
441
|
}
|
|
@@ -411,7 +451,7 @@ export function getRandomPerPointShader(opacity, featureIndexProp, pointIndexPro
|
|
|
411
451
|
* @returns {string} A GLSL shader string.
|
|
412
452
|
*/
|
|
413
453
|
export function getRandomPerPointWithSelectionShader(
|
|
414
|
-
featureIndices, defaultColor, opacity, featureIndexProp, pointIndexProp,
|
|
454
|
+
featureIndices, defaultColor, opacity, featureIndexProp, pointIndexProp, borderWidth = 0.0,
|
|
415
455
|
) {
|
|
416
456
|
const normDefault = normalizeColor(defaultColor);
|
|
417
457
|
const numFeatures = featureIndices.length;
|
|
@@ -435,8 +475,10 @@ export function getRandomPerPointWithSelectionShader(
|
|
|
435
475
|
float g = hashToFloat(pointIndex, 1);
|
|
436
476
|
float b = hashToFloat(pointIndex, 2);
|
|
437
477
|
setColor(vec4(r, g, b, ${opacity}));
|
|
478
|
+
${borderWidthGlsl(borderWidth)}
|
|
438
479
|
} else {
|
|
439
480
|
setColor(${toVec4(normDefault, opacity)});
|
|
481
|
+
${borderWidthGlsl(borderWidth)}
|
|
440
482
|
}
|
|
441
483
|
}
|
|
442
484
|
`;
|
|
@@ -452,7 +494,7 @@ export function getRandomPerPointWithSelectionShader(
|
|
|
452
494
|
* @returns {string} A GLSL shader string.
|
|
453
495
|
*/
|
|
454
496
|
export function getRandomPerPointFilteredShader(
|
|
455
|
-
featureIndices, opacity, featureIndexProp, pointIndexProp,
|
|
497
|
+
featureIndices, opacity, featureIndexProp, pointIndexProp, borderWidth = 0.0,
|
|
456
498
|
) {
|
|
457
499
|
const numFeatures = featureIndices.length;
|
|
458
500
|
const indicesDecl = `int selectedIndices[${numFeatures}] = int[${numFeatures}](${featureIndices.join(', ')});`;
|
|
@@ -477,6 +519,7 @@ export function getRandomPerPointFilteredShader(
|
|
|
477
519
|
float g = hashToFloat(pointIndex, 1);
|
|
478
520
|
float b = hashToFloat(pointIndex, 2);
|
|
479
521
|
setColor(vec4(r, g, b, ${opacity}));
|
|
522
|
+
${borderWidthGlsl(borderWidth)}
|
|
480
523
|
}
|
|
481
524
|
`;
|
|
482
525
|
}
|
|
@@ -492,7 +535,7 @@ export function getPointsShader(layerCoordination) {
|
|
|
492
535
|
featureSelection,
|
|
493
536
|
featureFilterMode,
|
|
494
537
|
featureColor,
|
|
495
|
-
|
|
538
|
+
pointMarkerBorderWidth = 0.0,
|
|
496
539
|
featureIndexProp,
|
|
497
540
|
pointIndexProp,
|
|
498
541
|
} = layerCoordination;
|
|
@@ -580,15 +623,15 @@ export function getPointsShader(layerCoordination) {
|
|
|
580
623
|
// ---- spatialLayerColor ----
|
|
581
624
|
if (obsColorEncoding === 'spatialLayerColor') {
|
|
582
625
|
if (!hasFeatureSelection || !hasResolvedIndices) {
|
|
583
|
-
return getSpatialLayerColorShader(staticColor, opacity);
|
|
626
|
+
return getSpatialLayerColorShader(staticColor, opacity, pointMarkerBorderWidth);
|
|
584
627
|
}
|
|
585
628
|
if (isFiltered) {
|
|
586
629
|
return getSpatialLayerColorFilteredShader(
|
|
587
|
-
staticColor, opacity, featureIndices, featureIndexProp,
|
|
630
|
+
staticColor, opacity, featureIndices, featureIndexProp, pointMarkerBorderWidth,
|
|
588
631
|
);
|
|
589
632
|
}
|
|
590
633
|
return getSpatialLayerColorWithSelectionShader(
|
|
591
|
-
staticColor, opacity, featureIndices, defaultColor, featureIndexProp,
|
|
634
|
+
staticColor, opacity, featureIndices, defaultColor, featureIndexProp, pointMarkerBorderWidth,
|
|
592
635
|
);
|
|
593
636
|
}
|
|
594
637
|
|
|
@@ -598,17 +641,17 @@ export function getPointsShader(layerCoordination) {
|
|
|
598
641
|
throw new Error('In order to use gene-based color encoding for Neuroglancer Points, options.featureIndexProp must be specified for the obsPoints.ng-annotations fileType in the Vitessce configuration.');
|
|
599
642
|
}
|
|
600
643
|
if (!hasFeatureSelection || !hasResolvedIndices) {
|
|
601
|
-
return getGeneSelectionNoSelectionShader(staticColor, opacity);
|
|
644
|
+
return getGeneSelectionNoSelectionShader(staticColor, opacity, pointMarkerBorderWidth);
|
|
602
645
|
}
|
|
603
646
|
if (isFiltered) {
|
|
604
647
|
return getGeneSelectionFilteredShader(
|
|
605
648
|
featureIndices, resolvedFeatureColors,
|
|
606
|
-
staticColor, opacity, featureIndexProp,
|
|
649
|
+
staticColor, opacity, featureIndexProp, pointMarkerBorderWidth,
|
|
607
650
|
);
|
|
608
651
|
}
|
|
609
652
|
return getGeneSelectionWithSelectionShader(
|
|
610
653
|
featureIndices, resolvedFeatureColors,
|
|
611
|
-
staticColor, defaultColor, opacity, featureIndexProp,
|
|
654
|
+
staticColor, defaultColor, opacity, featureIndexProp, pointMarkerBorderWidth,
|
|
612
655
|
);
|
|
613
656
|
}
|
|
614
657
|
|
|
@@ -618,15 +661,15 @@ export function getPointsShader(layerCoordination) {
|
|
|
618
661
|
throw new Error('In order to use gene-based color encoding for Neuroglancer Points, options.featureIndexProp must be specified for the obsPoints.ng-annotations fileType in the Vitessce configuration.');
|
|
619
662
|
}
|
|
620
663
|
if (!hasFeatureSelection || !hasResolvedIndices) {
|
|
621
|
-
return getRandomByFeatureShader(opacity, featureIndexProp);
|
|
664
|
+
return getRandomByFeatureShader(opacity, featureIndexProp, pointMarkerBorderWidth);
|
|
622
665
|
}
|
|
623
666
|
if (isFiltered) {
|
|
624
667
|
return getRandomByFeatureFilteredShader(
|
|
625
|
-
featureIndices, opacity, featureIndexProp,
|
|
668
|
+
featureIndices, opacity, featureIndexProp, pointMarkerBorderWidth,
|
|
626
669
|
);
|
|
627
670
|
}
|
|
628
671
|
return getRandomByFeatureWithSelectionShader(
|
|
629
|
-
featureIndices, defaultColor, opacity, featureIndexProp,
|
|
672
|
+
featureIndices, defaultColor, opacity, featureIndexProp, pointMarkerBorderWidth,
|
|
630
673
|
);
|
|
631
674
|
}
|
|
632
675
|
|
|
@@ -636,18 +679,28 @@ export function getPointsShader(layerCoordination) {
|
|
|
636
679
|
throw new Error('In order to use per-point color encoding for Neuroglancer Points, options.pointIndexProp must be specified for the obsPoints.ng-annotations fileType in the Vitessce configuration.');
|
|
637
680
|
}
|
|
638
681
|
if (!hasFeatureSelection || !hasResolvedIndices) {
|
|
639
|
-
return getRandomPerPointShader(
|
|
682
|
+
return getRandomPerPointShader(
|
|
683
|
+
opacity,
|
|
684
|
+
featureIndexProp,
|
|
685
|
+
pointIndexProp,
|
|
686
|
+
pointMarkerBorderWidth,
|
|
687
|
+
);
|
|
640
688
|
}
|
|
641
689
|
if (isFiltered) {
|
|
642
690
|
return getRandomPerPointFilteredShader(
|
|
643
|
-
featureIndices, opacity, featureIndexProp, pointIndexProp,
|
|
691
|
+
featureIndices, opacity, featureIndexProp, pointIndexProp, pointMarkerBorderWidth,
|
|
644
692
|
);
|
|
645
693
|
}
|
|
646
694
|
return getRandomPerPointWithSelectionShader(
|
|
647
|
-
featureIndices,
|
|
695
|
+
featureIndices,
|
|
696
|
+
defaultColor,
|
|
697
|
+
opacity,
|
|
698
|
+
featureIndexProp,
|
|
699
|
+
pointIndexProp,
|
|
700
|
+
pointMarkerBorderWidth,
|
|
648
701
|
);
|
|
649
702
|
}
|
|
650
703
|
|
|
651
704
|
// Fallback: static color.
|
|
652
|
-
return getSpatialLayerColorShader(staticColor, opacity);
|
|
705
|
+
return getSpatialLayerColorShader(staticColor, opacity, pointMarkerBorderWidth);
|
|
653
706
|
}
|
package/src/shader-utils.test.js
CHANGED
|
@@ -47,6 +47,7 @@ describe('getSpatialLayerColorShader', () => {
|
|
|
47
47
|
const expected = `
|
|
48
48
|
void main() {
|
|
49
49
|
setColor(vec4(1, 0.5019607843137255, 0, 0.8));
|
|
50
|
+
setPointMarkerBorderWidth(0.0);
|
|
50
51
|
}
|
|
51
52
|
`;
|
|
52
53
|
expectShaderEqual(result, expected);
|
|
@@ -57,6 +58,7 @@ void main() {
|
|
|
57
58
|
const expected = `
|
|
58
59
|
void main() {
|
|
59
60
|
setColor(vec4(0, 0, 0, 0));
|
|
61
|
+
setPointMarkerBorderWidth(0.0);
|
|
60
62
|
}
|
|
61
63
|
`;
|
|
62
64
|
expectShaderEqual(result, expected);
|
|
@@ -67,6 +69,7 @@ void main() {
|
|
|
67
69
|
const expected = `
|
|
68
70
|
void main() {
|
|
69
71
|
setColor(vec4(1, 1, 1, 1));
|
|
72
|
+
setPointMarkerBorderWidth(0.0);
|
|
70
73
|
}
|
|
71
74
|
`;
|
|
72
75
|
expectShaderEqual(result, expected);
|
|
@@ -90,8 +93,10 @@ void main() {
|
|
|
90
93
|
}
|
|
91
94
|
if (isSelected) {
|
|
92
95
|
setColor(vec4(1, 0, 0, 0.5));
|
|
96
|
+
setPointMarkerBorderWidth(0.0);
|
|
93
97
|
} else {
|
|
94
98
|
setColor(vec4(0.5019607843137255, 0.5019607843137255, 0.5019607843137255, 0.5));
|
|
99
|
+
setPointMarkerBorderWidth(0.0);
|
|
95
100
|
}
|
|
96
101
|
}
|
|
97
102
|
`;
|
|
@@ -114,8 +119,10 @@ void main() {
|
|
|
114
119
|
}
|
|
115
120
|
if (isSelected) {
|
|
116
121
|
setColor(vec4(0, 1, 0, 1));
|
|
122
|
+
setPointMarkerBorderWidth(0.0);
|
|
117
123
|
} else {
|
|
118
124
|
setColor(vec4(0, 0, 0, 1));
|
|
125
|
+
setPointMarkerBorderWidth(0.0);
|
|
119
126
|
}
|
|
120
127
|
}
|
|
121
128
|
`;
|
|
@@ -142,6 +149,7 @@ void main() {
|
|
|
142
149
|
discard;
|
|
143
150
|
}
|
|
144
151
|
setColor(vec4(0, 0, 1, 0.9));
|
|
152
|
+
setPointMarkerBorderWidth(0.0);
|
|
145
153
|
}
|
|
146
154
|
`;
|
|
147
155
|
expectShaderEqual(result, expected);
|
|
@@ -158,6 +166,7 @@ describe('getGeneSelectionNoSelectionShader', () => {
|
|
|
158
166
|
const expected = `
|
|
159
167
|
void main() {
|
|
160
168
|
setColor(vec4(0.39215686274509803, 0.7843137254901961, 0.19607843137254902, 0.7));
|
|
169
|
+
setPointMarkerBorderWidth(0.0);
|
|
161
170
|
}
|
|
162
171
|
`;
|
|
163
172
|
expectShaderEqual(result, expected);
|
|
@@ -186,6 +195,7 @@ void main() {
|
|
|
186
195
|
}
|
|
187
196
|
}
|
|
188
197
|
setColor(color);
|
|
198
|
+
setPointMarkerBorderWidth(0.0);
|
|
189
199
|
}
|
|
190
200
|
`;
|
|
191
201
|
expectShaderEqual(result, expected);
|
|
@@ -216,6 +226,7 @@ void main() {
|
|
|
216
226
|
}
|
|
217
227
|
}
|
|
218
228
|
setColor(color);
|
|
229
|
+
setPointMarkerBorderWidth(0.0);
|
|
219
230
|
}
|
|
220
231
|
`;
|
|
221
232
|
expectShaderEqual(result, expected);
|
|
@@ -248,6 +259,7 @@ void main() {
|
|
|
248
259
|
discard;
|
|
249
260
|
}
|
|
250
261
|
setColor(vec4(matchedColor, 0.75));
|
|
262
|
+
setPointMarkerBorderWidth(0.0);
|
|
251
263
|
}
|
|
252
264
|
`;
|
|
253
265
|
expectShaderEqual(result, expected);
|
|
@@ -269,6 +281,7 @@ void main() {
|
|
|
269
281
|
if (colorIdx < 0) { colorIdx = -colorIdx; }
|
|
270
282
|
vec3 color = palette[colorIdx];
|
|
271
283
|
setColor(vec4(color, 0.5));
|
|
284
|
+
setPointMarkerBorderWidth(0.0);
|
|
272
285
|
}
|
|
273
286
|
`;
|
|
274
287
|
expectShaderEqual(result, expected);
|
|
@@ -295,8 +308,10 @@ void main() {
|
|
|
295
308
|
int colorIdx = geneIndex - (geneIndex / 3) * 3;
|
|
296
309
|
if (colorIdx < 0) { colorIdx = -colorIdx; }
|
|
297
310
|
setColor(vec4(palette[colorIdx], 0.8));
|
|
311
|
+
setPointMarkerBorderWidth(0.0);
|
|
298
312
|
} else {
|
|
299
313
|
setColor(vec4(0.19607843137254902, 0.19607843137254902, 0.19607843137254902, 0.8));
|
|
314
|
+
setPointMarkerBorderWidth(0.0);
|
|
300
315
|
}
|
|
301
316
|
}
|
|
302
317
|
`;
|
|
@@ -324,6 +339,7 @@ void main() {
|
|
|
324
339
|
int colorIdx = geneIndex - (geneIndex / 3) * 3;
|
|
325
340
|
if (colorIdx < 0) { colorIdx = -colorIdx; }
|
|
326
341
|
setColor(vec4(palette[colorIdx], 1));
|
|
342
|
+
setPointMarkerBorderWidth(0.0);
|
|
327
343
|
}
|
|
328
344
|
`;
|
|
329
345
|
expectShaderEqual(result, expected);
|
|
@@ -352,6 +368,7 @@ void main() {
|
|
|
352
368
|
float g = hashToFloat(pointIndex, 1);
|
|
353
369
|
float b = hashToFloat(pointIndex, 2);
|
|
354
370
|
setColor(vec4(r, g, b, 0.9));
|
|
371
|
+
setPointMarkerBorderWidth(0.0);
|
|
355
372
|
}
|
|
356
373
|
`;
|
|
357
374
|
expectShaderEqual(result, expected);
|
|
@@ -386,8 +403,10 @@ void main() {
|
|
|
386
403
|
float g = hashToFloat(pointIndex, 1);
|
|
387
404
|
float b = hashToFloat(pointIndex, 2);
|
|
388
405
|
setColor(vec4(r, g, b, 0.5));
|
|
406
|
+
setPointMarkerBorderWidth(0.0);
|
|
389
407
|
} else {
|
|
390
408
|
setColor(vec4(0.39215686274509803, 0.39215686274509803, 0.39215686274509803, 0.5));
|
|
409
|
+
setPointMarkerBorderWidth(0.0);
|
|
391
410
|
}
|
|
392
411
|
}
|
|
393
412
|
`;
|
|
@@ -425,6 +444,7 @@ void main() {
|
|
|
425
444
|
float g = hashToFloat(pointIndex, 1);
|
|
426
445
|
float b = hashToFloat(pointIndex, 2);
|
|
427
446
|
setColor(vec4(r, g, b, 1));
|
|
447
|
+
setPointMarkerBorderWidth(0.0);
|
|
428
448
|
}
|
|
429
449
|
`;
|
|
430
450
|
expectShaderEqual(result, expected);
|
|
@@ -101,6 +101,7 @@ export function customIsEqualForCellColors(prevDeps, nextDeps) {
|
|
|
101
101
|
'obsSetSelection',
|
|
102
102
|
'additionalObsSets',
|
|
103
103
|
'spatialChannelColor',
|
|
104
|
+
'spatialChannelOpacity',
|
|
104
105
|
])
|
|
105
106
|
) {
|
|
106
107
|
forceUpdate = true;
|
|
@@ -128,6 +129,10 @@ export function customIsEqualForInitialViewerState(prevDeps, nextDeps) {
|
|
|
128
129
|
const curriedShallowDiffByChannelCoordination = (depName, layerScope, channelScope) => shallowDiffByChannelCoordination(prevDeps, nextDeps, depName, layerScope, channelScope);
|
|
129
130
|
const curriedShallowDiffByChannelCoordinationWithKeys = (depName, layerScope, channelScope, keys) => shallowDiffByChannelCoordinationWithKeys(prevDeps, nextDeps, depName, layerScope, channelScope, keys);
|
|
130
131
|
|
|
132
|
+
if (['theme', 'showAxisLines'].some(curriedShallowDiff)) {
|
|
133
|
+
forceUpdate = true;
|
|
134
|
+
}
|
|
135
|
+
|
|
131
136
|
// Segmentation layers/channels.
|
|
132
137
|
if (['segmentationLayerScopes', 'segmentationChannelScopesByLayer'].some(curriedShallowDiff)) {
|
|
133
138
|
// Force update for all layers since the layerScopes array changed.
|
|
@@ -172,6 +177,7 @@ export function customIsEqualForInitialViewerState(prevDeps, nextDeps) {
|
|
|
172
177
|
'featureSelection',
|
|
173
178
|
'featureFilterMode',
|
|
174
179
|
'featureColor',
|
|
180
|
+
'spatialPointStrokeWidth',
|
|
175
181
|
])
|
|
176
182
|
// For opacity, use an epsilon comparison to avoid too many re-renders, as it affects performance.
|
|
177
183
|
|| (
|