@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.
- 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
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
// Utilities for constructing shaders that
|
|
2
|
+
// handle the coloring of Neuroglancer point annotation layers.
|
|
3
|
+
// References:
|
|
4
|
+
// - https://github.com/vitessce/vitessce/issues/2359#issuecomment-3572906947
|
|
5
|
+
// - https://chanzuckerberg.github.io/cryoet-data-portal/stable/neuroglancer_quickstart.html
|
|
6
|
+
import { PALETTE, getDefaultColor } from '@vitessce/utils';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Normalize an RGB color array from [0, 255] to [0, 1].
|
|
11
|
+
* @param {[number, number, number]} rgbColor
|
|
12
|
+
* @returns {[number, number, number]}
|
|
13
|
+
*/
|
|
14
|
+
function normalizeColor(rgbColor) {
|
|
15
|
+
return rgbColor.map(c => c / 255);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Format a normalized color as a GLSL vec3 literal.
|
|
20
|
+
* @param {[number, number, number]} normalizedColor
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
23
|
+
function toVec3(normalizedColor) {
|
|
24
|
+
return `vec3(${normalizedColor.join(', ')})`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Format a normalized color as a GLSL vec4 literal.
|
|
29
|
+
* @param {[number, number, number]} normalizedColor
|
|
30
|
+
* @param {number} alpha
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
function toVec4(normalizedColor, alpha) {
|
|
34
|
+
return `vec4(${normalizedColor.join(', ')}, ${alpha})`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ============================================================
|
|
38
|
+
// Case 1: spatialLayerColor
|
|
39
|
+
// ============================================================
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generate a shader for spatialLayerColor encoding with no feature selection.
|
|
43
|
+
* All points get the static color.
|
|
44
|
+
* @param {[number, number, number]} staticColor RGB (0-255).
|
|
45
|
+
* @param {number} opacity Opacity (0-1).
|
|
46
|
+
* @returns {string} A GLSL shader string.
|
|
47
|
+
*/
|
|
48
|
+
export function getSpatialLayerColorShader(staticColor, opacity) {
|
|
49
|
+
const norm = normalizeColor(staticColor);
|
|
50
|
+
// lang: glsl
|
|
51
|
+
return `
|
|
52
|
+
void main() {
|
|
53
|
+
setColor(${toVec4(norm, opacity)});
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Generate a shader for spatialLayerColor encoding with feature selection.
|
|
61
|
+
* Selected features get the static color; unselected get the default color.
|
|
62
|
+
* @param {[number, number, number]} staticColor RGB (0-255).
|
|
63
|
+
* @param {number} opacity Opacity (0-1).
|
|
64
|
+
* @param {number[]} featureIndices Numeric indices of selected features.
|
|
65
|
+
* @param {[number, number, number]} defaultColor RGB (0-255) for unselected points.
|
|
66
|
+
* @param {string} featureIndexProp The property name for the feature index in the shader.
|
|
67
|
+
* @returns {string} A GLSL shader string.
|
|
68
|
+
*/
|
|
69
|
+
export function getSpatialLayerColorWithSelectionShader(
|
|
70
|
+
staticColor, opacity, featureIndices, defaultColor, featureIndexProp,
|
|
71
|
+
) {
|
|
72
|
+
const normStatic = normalizeColor(staticColor);
|
|
73
|
+
const normDefault = normalizeColor(defaultColor);
|
|
74
|
+
const numFeatures = featureIndices.length;
|
|
75
|
+
const indicesArr = `int selectedIndices[${numFeatures}] = int[${numFeatures}](${featureIndices.join(', ')});`;
|
|
76
|
+
|
|
77
|
+
// lang: glsl
|
|
78
|
+
return `
|
|
79
|
+
void main() {
|
|
80
|
+
int geneIndex = prop_${featureIndexProp}();
|
|
81
|
+
${indicesArr}
|
|
82
|
+
bool isSelected = false;
|
|
83
|
+
for (int i = 0; i < ${numFeatures}; ++i) {
|
|
84
|
+
if (geneIndex == selectedIndices[i]) {
|
|
85
|
+
isSelected = true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (isSelected) {
|
|
89
|
+
setColor(${toVec4(normStatic, opacity)});
|
|
90
|
+
} else {
|
|
91
|
+
setColor(${toVec4(normDefault, opacity)});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Generate a shader for spatialLayerColor encoding with feature selection
|
|
100
|
+
* and featureFilterMode='featureSelection' (hide unselected points).
|
|
101
|
+
* @param {[number, number, number]} staticColor RGB (0-255).
|
|
102
|
+
* @param {number} opacity Opacity (0-1).
|
|
103
|
+
* @param {number[]} featureIndices Numeric indices of selected features.
|
|
104
|
+
* @param {string} featureIndexProp The property name for the feature index in the shader.
|
|
105
|
+
* @returns {string} A GLSL shader string.
|
|
106
|
+
*/
|
|
107
|
+
export function getSpatialLayerColorFilteredShader(
|
|
108
|
+
staticColor, opacity, featureIndices, featureIndexProp,
|
|
109
|
+
) {
|
|
110
|
+
const normStatic = normalizeColor(staticColor);
|
|
111
|
+
const numFeatures = featureIndices.length;
|
|
112
|
+
const indicesArr = `int selectedIndices[${numFeatures}] = int[${numFeatures}](${featureIndices.join(', ')});`;
|
|
113
|
+
|
|
114
|
+
// lang: glsl
|
|
115
|
+
return `
|
|
116
|
+
void main() {
|
|
117
|
+
int geneIndex = prop_${featureIndexProp}();
|
|
118
|
+
${indicesArr}
|
|
119
|
+
bool isSelected = false;
|
|
120
|
+
for (int i = 0; i < ${numFeatures}; ++i) {
|
|
121
|
+
if (geneIndex == selectedIndices[i]) {
|
|
122
|
+
isSelected = true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!isSelected) {
|
|
126
|
+
discard;
|
|
127
|
+
}
|
|
128
|
+
setColor(${toVec4(normStatic, opacity)});
|
|
129
|
+
}
|
|
130
|
+
`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ============================================================
|
|
134
|
+
// Case 2: geneSelection
|
|
135
|
+
// ============================================================
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Generate a shader for geneSelection encoding with no feature selection.
|
|
139
|
+
* All points get the static color (since no features are selected to
|
|
140
|
+
* determine per-feature colors).
|
|
141
|
+
* @param {[number, number, number]} staticColor RGB (0-255).
|
|
142
|
+
* @param {number} opacity Opacity (0-1).
|
|
143
|
+
* @returns {string} A GLSL shader string.
|
|
144
|
+
*/
|
|
145
|
+
export function getGeneSelectionNoSelectionShader(staticColor, opacity) {
|
|
146
|
+
const norm = normalizeColor(staticColor);
|
|
147
|
+
// lang: glsl
|
|
148
|
+
return `
|
|
149
|
+
void main() {
|
|
150
|
+
setColor(${toVec4(norm, opacity)});
|
|
151
|
+
}
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Generate a shader for geneSelection encoding with feature selection.
|
|
157
|
+
* Each selected feature gets its color from featureColor; unselected
|
|
158
|
+
* points get the default color.
|
|
159
|
+
* @param {number[]} featureIndices Numeric indices of selected features.
|
|
160
|
+
* @param {[number, number, number][]} featureColors RGB (0-255) for each
|
|
161
|
+
* selected feature, in the same order as featureIndices.
|
|
162
|
+
* @param {[number, number, number]} staticColor Fallback RGB (0-255)
|
|
163
|
+
* for selected features without a specified color.
|
|
164
|
+
* @param {[number, number, number]} defaultColor RGB (0-255) for
|
|
165
|
+
* unselected points.
|
|
166
|
+
* @param {number} opacity Opacity (0-1).
|
|
167
|
+
* @param {string} featureIndexProp The property name for the feature index in the shader.
|
|
168
|
+
* @returns {string} A GLSL shader string.
|
|
169
|
+
*/
|
|
170
|
+
export function getGeneSelectionWithSelectionShader(
|
|
171
|
+
featureIndices, featureColors, staticColor, defaultColor, opacity, featureIndexProp,
|
|
172
|
+
) {
|
|
173
|
+
const numFeatures = featureIndices.length;
|
|
174
|
+
const normDefault = normalizeColor(defaultColor);
|
|
175
|
+
const normColors = featureColors.map(c => normalizeColor(c));
|
|
176
|
+
const normStatic = normalizeColor(staticColor);
|
|
177
|
+
|
|
178
|
+
const colorArr = normColors.map(
|
|
179
|
+
c => (c ? toVec3(c) : toVec3(normStatic)),
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const indicesDecl = `int selectedIndices[${numFeatures}] = int[${numFeatures}](${featureIndices.join(', ')});`;
|
|
183
|
+
const colorsDecl = `vec3 featureColors[${numFeatures}] = vec3[${numFeatures}](${colorArr.join(', ')});`;
|
|
184
|
+
// lang: glsl
|
|
185
|
+
return `
|
|
186
|
+
void main() {
|
|
187
|
+
int geneIndex = prop_${featureIndexProp}();
|
|
188
|
+
${indicesDecl}
|
|
189
|
+
${colorsDecl}
|
|
190
|
+
vec4 color = ${toVec4(normDefault, opacity)};
|
|
191
|
+
for (int i = 0; i < ${numFeatures}; ++i) {
|
|
192
|
+
if (geneIndex == selectedIndices[i]) {
|
|
193
|
+
color = vec4(featureColors[i], ${opacity});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
setColor(color);
|
|
197
|
+
}
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Generate a shader for geneSelection encoding with feature selection
|
|
203
|
+
* and featureFilterMode='featureSelection' (hide unselected points).
|
|
204
|
+
* @param {number[]} featureIndices Numeric indices of selected features.
|
|
205
|
+
* @param {[number, number, number][]} featureColors RGB (0-255) for each
|
|
206
|
+
* selected feature.
|
|
207
|
+
* @param {[number, number, number]} staticColor Fallback RGB (0-255).
|
|
208
|
+
* @param {number} opacity Opacity (0-1).
|
|
209
|
+
* @param {string} featureIndexProp The property name for the feature index in the shader.
|
|
210
|
+
* @returns {string} A GLSL shader string.
|
|
211
|
+
*/
|
|
212
|
+
export function getGeneSelectionFilteredShader(
|
|
213
|
+
featureIndices, featureColors, staticColor, opacity, featureIndexProp,
|
|
214
|
+
) {
|
|
215
|
+
const numFeatures = featureIndices.length;
|
|
216
|
+
const normColors = featureColors.map(c => normalizeColor(c));
|
|
217
|
+
const normStatic = normalizeColor(staticColor);
|
|
218
|
+
|
|
219
|
+
const colorArr = normColors.map(
|
|
220
|
+
c => (c ? toVec3(c) : toVec3(normStatic)),
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const indicesDecl = `int selectedIndices[${numFeatures}] = int[${numFeatures}](${featureIndices.join(', ')});`;
|
|
224
|
+
const colorsDecl = `vec3 featureColors[${numFeatures}] = vec3[${numFeatures}](${colorArr.join(', ')});`;
|
|
225
|
+
|
|
226
|
+
// lang: glsl
|
|
227
|
+
return `
|
|
228
|
+
void main() {
|
|
229
|
+
int geneIndex = prop_${featureIndexProp}();
|
|
230
|
+
${indicesDecl}
|
|
231
|
+
${colorsDecl}
|
|
232
|
+
bool isSelected = false;
|
|
233
|
+
vec3 matchedColor = vec3(0.0);
|
|
234
|
+
for (int i = 0; i < ${numFeatures}; ++i) {
|
|
235
|
+
if (geneIndex == selectedIndices[i]) {
|
|
236
|
+
isSelected = true;
|
|
237
|
+
matchedColor = featureColors[i];
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (!isSelected) {
|
|
241
|
+
discard;
|
|
242
|
+
}
|
|
243
|
+
setColor(vec4(matchedColor, ${opacity}));
|
|
244
|
+
}
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ============================================================
|
|
249
|
+
// Case 3: randomByFeature
|
|
250
|
+
// ============================================================
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Generate a shader for randomByFeature encoding with no feature selection.
|
|
254
|
+
* Each feature gets a deterministic color from PALETTE based on its index.
|
|
255
|
+
* @param {number} opacity Opacity (0-1).
|
|
256
|
+
* @param {string} featureIndexProp The property name for the feature index in the shader.
|
|
257
|
+
* @returns {string} A GLSL shader string.
|
|
258
|
+
*/
|
|
259
|
+
export function getRandomByFeatureShader(opacity, featureIndexProp) {
|
|
260
|
+
const paletteSize = PALETTE.length;
|
|
261
|
+
const normPalette = PALETTE.map(c => normalizeColor(c));
|
|
262
|
+
const paletteDecl = `vec3 palette[${paletteSize}] = vec3[${paletteSize}](${normPalette.map(c => toVec3(c)).join(', ')});`;
|
|
263
|
+
|
|
264
|
+
// lang: glsl
|
|
265
|
+
return `
|
|
266
|
+
void main() {
|
|
267
|
+
int geneIndex = prop_${featureIndexProp}();
|
|
268
|
+
${paletteDecl}
|
|
269
|
+
int colorIdx = geneIndex - (geneIndex / ${paletteSize}) * ${paletteSize};
|
|
270
|
+
if (colorIdx < 0) { colorIdx = -colorIdx; }
|
|
271
|
+
vec3 color = palette[colorIdx];
|
|
272
|
+
setColor(vec4(color, ${opacity}));
|
|
273
|
+
}
|
|
274
|
+
`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Generate a shader for randomByFeature encoding with feature selection.
|
|
279
|
+
* Selected features get their deterministic palette color; unselected
|
|
280
|
+
* points get the default color.
|
|
281
|
+
* @param {number[]} featureIndices Numeric indices of selected features.
|
|
282
|
+
* @param {[number, number, number]} defaultColor RGB (0-255) for unselected.
|
|
283
|
+
* @param {number} opacity Opacity (0-1).
|
|
284
|
+
* @param {string} featureIndexProp The property name for the feature index in the shader.
|
|
285
|
+
* @returns {string} A GLSL shader string.
|
|
286
|
+
*/
|
|
287
|
+
export function getRandomByFeatureWithSelectionShader(
|
|
288
|
+
featureIndices, defaultColor, opacity, featureIndexProp,
|
|
289
|
+
) {
|
|
290
|
+
const paletteSize = PALETTE.length;
|
|
291
|
+
const normPalette = PALETTE.map(c => normalizeColor(c));
|
|
292
|
+
const normDefault = normalizeColor(defaultColor);
|
|
293
|
+
const numFeatures = featureIndices.length;
|
|
294
|
+
|
|
295
|
+
const paletteDecl = `vec3 palette[${paletteSize}] = vec3[${paletteSize}](${normPalette.map(c => toVec3(c)).join(', ')});`;
|
|
296
|
+
const indicesDecl = `int selectedIndices[${numFeatures}] = int[${numFeatures}](${featureIndices.join(', ')});`;
|
|
297
|
+
|
|
298
|
+
// lang: glsl
|
|
299
|
+
return `
|
|
300
|
+
void main() {
|
|
301
|
+
int geneIndex = prop_${featureIndexProp}();
|
|
302
|
+
${paletteDecl}
|
|
303
|
+
${indicesDecl}
|
|
304
|
+
bool isSelected = false;
|
|
305
|
+
for (int i = 0; i < ${numFeatures}; ++i) {
|
|
306
|
+
if (geneIndex == selectedIndices[i]) {
|
|
307
|
+
isSelected = true;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (isSelected) {
|
|
311
|
+
int colorIdx = geneIndex - (geneIndex / ${paletteSize}) * ${paletteSize};
|
|
312
|
+
if (colorIdx < 0) { colorIdx = -colorIdx; }
|
|
313
|
+
setColor(vec4(palette[colorIdx], ${opacity}));
|
|
314
|
+
} else {
|
|
315
|
+
setColor(${toVec4(normDefault, opacity)});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
`;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Generate a shader for randomByFeature encoding with feature selection
|
|
323
|
+
* and featureFilterMode='featureSelection' (hide unselected points).
|
|
324
|
+
* @param {number[]} featureIndices Numeric indices of selected features.
|
|
325
|
+
* @param {number} opacity Opacity (0-1).
|
|
326
|
+
* @param {string} featureIndexProp The property name for the feature index in the shader.
|
|
327
|
+
* @returns {string} A GLSL shader string.
|
|
328
|
+
*/
|
|
329
|
+
export function getRandomByFeatureFilteredShader(featureIndices, opacity, featureIndexProp) {
|
|
330
|
+
const paletteSize = PALETTE.length;
|
|
331
|
+
const normPalette = PALETTE.map(c => normalizeColor(c));
|
|
332
|
+
const numFeatures = featureIndices.length;
|
|
333
|
+
|
|
334
|
+
const paletteDecl = `vec3 palette[${paletteSize}] = vec3[${paletteSize}](${normPalette.map(c => toVec3(c)).join(', ')});`;
|
|
335
|
+
const indicesDecl = `int selectedIndices[${numFeatures}] = int[${numFeatures}](${featureIndices.join(', ')});`;
|
|
336
|
+
|
|
337
|
+
// lang: glsl
|
|
338
|
+
return `
|
|
339
|
+
void main() {
|
|
340
|
+
int geneIndex = prop_${featureIndexProp}();
|
|
341
|
+
${paletteDecl}
|
|
342
|
+
${indicesDecl}
|
|
343
|
+
bool isSelected = false;
|
|
344
|
+
for (int i = 0; i < ${numFeatures}; ++i) {
|
|
345
|
+
if (geneIndex == selectedIndices[i]) {
|
|
346
|
+
isSelected = true;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (!isSelected) {
|
|
350
|
+
discard;
|
|
351
|
+
}
|
|
352
|
+
int colorIdx = geneIndex - (geneIndex / ${paletteSize}) * ${paletteSize};
|
|
353
|
+
if (colorIdx < 0) { colorIdx = -colorIdx; }
|
|
354
|
+
setColor(vec4(palette[colorIdx], ${opacity}));
|
|
355
|
+
}
|
|
356
|
+
`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ============================================================
|
|
360
|
+
// Case 4: random (per point)
|
|
361
|
+
// ============================================================
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* GLSL helper function that produces a pseudo-random float in [0, 1]
|
|
365
|
+
* from an integer value and a seed. Shared across random-per-point shaders.
|
|
366
|
+
* @returns {string} GLSL function source.
|
|
367
|
+
*/
|
|
368
|
+
function hashToFloatGlsl() {
|
|
369
|
+
return `
|
|
370
|
+
float hashToFloat(int v, int seed) {
|
|
371
|
+
int h = v ^ (seed * 16777619);
|
|
372
|
+
h = h * 747796405 + 2891336453;
|
|
373
|
+
h = ((h >> 16) ^ h) * 2654435769;
|
|
374
|
+
h = ((h >> 16) ^ h);
|
|
375
|
+
return float(h & 0x7FFFFFFF) / float(0x7FFFFFFF);
|
|
376
|
+
}
|
|
377
|
+
`;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Generate a shader for random-per-point encoding with no feature selection.
|
|
382
|
+
* Each point gets a pseudo-random color based on its index.
|
|
383
|
+
* @param {number} opacity Opacity (0-1).
|
|
384
|
+
* @param {string} featureIndexProp The property name for the feature index in the shader.
|
|
385
|
+
* @param {string} pointIndexProp The property name for the point index in the shader.
|
|
386
|
+
* @returns {string} A GLSL shader string.
|
|
387
|
+
*/
|
|
388
|
+
export function getRandomPerPointShader(opacity, featureIndexProp, pointIndexProp) {
|
|
389
|
+
// lang: glsl
|
|
390
|
+
return `
|
|
391
|
+
${hashToFloatGlsl()}
|
|
392
|
+
void main() {
|
|
393
|
+
int geneIndex = prop_${featureIndexProp}();
|
|
394
|
+
int pointIndex = prop_${pointIndexProp}();
|
|
395
|
+
float r = hashToFloat(pointIndex, 0);
|
|
396
|
+
float g = hashToFloat(pointIndex, 1);
|
|
397
|
+
float b = hashToFloat(pointIndex, 2);
|
|
398
|
+
setColor(vec4(r, g, b, ${opacity}));
|
|
399
|
+
}
|
|
400
|
+
`;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Generate a shader for random-per-point encoding with feature selection.
|
|
405
|
+
* Selected points get a pseudo-random color; unselected get the default color.
|
|
406
|
+
* @param {number[]} featureIndices Numeric indices of selected features.
|
|
407
|
+
* @param {[number, number, number]} defaultColor RGB (0-255) for unselected.
|
|
408
|
+
* @param {number} opacity Opacity (0-1).
|
|
409
|
+
* @param {string} featureIndexProp The property name for the feature index in the shader.
|
|
410
|
+
* @param {string} pointIndexProp The property name for the point index in the shader.
|
|
411
|
+
* @returns {string} A GLSL shader string.
|
|
412
|
+
*/
|
|
413
|
+
export function getRandomPerPointWithSelectionShader(
|
|
414
|
+
featureIndices, defaultColor, opacity, featureIndexProp, pointIndexProp,
|
|
415
|
+
) {
|
|
416
|
+
const normDefault = normalizeColor(defaultColor);
|
|
417
|
+
const numFeatures = featureIndices.length;
|
|
418
|
+
const indicesDecl = `int selectedIndices[${numFeatures}] = int[${numFeatures}](${featureIndices.join(', ')});`;
|
|
419
|
+
|
|
420
|
+
// lang: glsl
|
|
421
|
+
return `
|
|
422
|
+
${hashToFloatGlsl()}
|
|
423
|
+
void main() {
|
|
424
|
+
int geneIndex = prop_${featureIndexProp}();
|
|
425
|
+
int pointIndex = prop_${pointIndexProp}();
|
|
426
|
+
${indicesDecl}
|
|
427
|
+
bool isSelected = false;
|
|
428
|
+
for (int i = 0; i < ${numFeatures}; ++i) {
|
|
429
|
+
if (geneIndex == selectedIndices[i]) {
|
|
430
|
+
isSelected = true;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (isSelected) {
|
|
434
|
+
float r = hashToFloat(pointIndex, 0);
|
|
435
|
+
float g = hashToFloat(pointIndex, 1);
|
|
436
|
+
float b = hashToFloat(pointIndex, 2);
|
|
437
|
+
setColor(vec4(r, g, b, ${opacity}));
|
|
438
|
+
} else {
|
|
439
|
+
setColor(${toVec4(normDefault, opacity)});
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
`;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Generate a shader for random-per-point encoding with feature selection
|
|
447
|
+
* and featureFilterMode='featureSelection' (hide unselected points).
|
|
448
|
+
* @param {number[]} featureIndices Numeric indices of selected features.
|
|
449
|
+
* @param {number} opacity Opacity (0-1).
|
|
450
|
+
* @param {string} featureIndexProp The property name for the feature index in the shader.
|
|
451
|
+
* @param {string} pointIndexProp The property name for the point index in the shader.
|
|
452
|
+
* @returns {string} A GLSL shader string.
|
|
453
|
+
*/
|
|
454
|
+
export function getRandomPerPointFilteredShader(
|
|
455
|
+
featureIndices, opacity, featureIndexProp, pointIndexProp,
|
|
456
|
+
) {
|
|
457
|
+
const numFeatures = featureIndices.length;
|
|
458
|
+
const indicesDecl = `int selectedIndices[${numFeatures}] = int[${numFeatures}](${featureIndices.join(', ')});`;
|
|
459
|
+
|
|
460
|
+
// lang: glsl
|
|
461
|
+
return `
|
|
462
|
+
${hashToFloatGlsl()}
|
|
463
|
+
void main() {
|
|
464
|
+
int geneIndex = prop_${featureIndexProp}();
|
|
465
|
+
int pointIndex = prop_${pointIndexProp}();
|
|
466
|
+
${indicesDecl}
|
|
467
|
+
bool isSelected = false;
|
|
468
|
+
for (int i = 0; i < ${numFeatures}; ++i) {
|
|
469
|
+
if (geneIndex == selectedIndices[i]) {
|
|
470
|
+
isSelected = true;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (!isSelected) {
|
|
474
|
+
discard;
|
|
475
|
+
}
|
|
476
|
+
float r = hashToFloat(pointIndex, 0);
|
|
477
|
+
float g = hashToFloat(pointIndex, 1);
|
|
478
|
+
float b = hashToFloat(pointIndex, 2);
|
|
479
|
+
setColor(vec4(r, g, b, ${opacity}));
|
|
480
|
+
}
|
|
481
|
+
`;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
export function getPointsShader(layerCoordination) {
|
|
486
|
+
const {
|
|
487
|
+
theme,
|
|
488
|
+
featureIndex,
|
|
489
|
+
spatialLayerOpacity,
|
|
490
|
+
obsColorEncoding,
|
|
491
|
+
spatialLayerColor,
|
|
492
|
+
featureSelection,
|
|
493
|
+
featureFilterMode,
|
|
494
|
+
featureColor,
|
|
495
|
+
|
|
496
|
+
featureIndexProp,
|
|
497
|
+
pointIndexProp,
|
|
498
|
+
} = layerCoordination;
|
|
499
|
+
|
|
500
|
+
const defaultColor = getDefaultColor(theme);
|
|
501
|
+
const opacity = spatialLayerOpacity ?? 1.0;
|
|
502
|
+
const staticColor = (
|
|
503
|
+
Array.isArray(spatialLayerColor) && spatialLayerColor.length === 3
|
|
504
|
+
? spatialLayerColor
|
|
505
|
+
: defaultColor
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
const hasFeatureSelection = (
|
|
509
|
+
Array.isArray(featureSelection) && featureSelection.length > 0
|
|
510
|
+
);
|
|
511
|
+
const isFiltered = featureFilterMode === 'featureSelection';
|
|
512
|
+
|
|
513
|
+
// Resolve selected feature names to numeric indices.
|
|
514
|
+
let featureIndices = [];
|
|
515
|
+
if (hasFeatureSelection && Array.isArray(featureIndex)) {
|
|
516
|
+
featureIndices = featureSelection
|
|
517
|
+
.map(name => featureIndex.indexOf(name))
|
|
518
|
+
.filter(i => i >= 0);
|
|
519
|
+
}
|
|
520
|
+
const hasResolvedIndices = featureIndices.length > 0;
|
|
521
|
+
|
|
522
|
+
// Resolve per-feature colors (in the same order as featureIndices).
|
|
523
|
+
const resolvedFeatureColors = hasResolvedIndices
|
|
524
|
+
? featureSelection
|
|
525
|
+
.filter(name => featureIndex?.indexOf(name) >= 0)
|
|
526
|
+
.map((name) => {
|
|
527
|
+
const match = Array.isArray(featureColor)
|
|
528
|
+
? featureColor.find(fc => fc.name === name)?.color
|
|
529
|
+
: null;
|
|
530
|
+
return match || staticColor;
|
|
531
|
+
})
|
|
532
|
+
: [];
|
|
533
|
+
|
|
534
|
+
// Points coloring cases:
|
|
535
|
+
// (See `createPointLayer` in spatial-beta/Spatial.js for more background.)
|
|
536
|
+
|
|
537
|
+
// Coloring cases:
|
|
538
|
+
// - spatialLayerColor: one color for all points.
|
|
539
|
+
// consider all as selected when featureSelection is null.
|
|
540
|
+
// - spatialLayerColor with featureSelection: one color for
|
|
541
|
+
// all selected points, default color for unselected points
|
|
542
|
+
// - spatialLayerColor with featureSelection and
|
|
543
|
+
// featureFilterMode 'featureSelection': one color for selected points,
|
|
544
|
+
// do not show unselected points
|
|
545
|
+
|
|
546
|
+
// - geneSelection: use colors from "featureColor".
|
|
547
|
+
// consider all as selected when featureSelection is null.
|
|
548
|
+
// - geneSelection with featureSelection: use colors from
|
|
549
|
+
// "featureColor" (array of { name, color: [r, g, b] }) for
|
|
550
|
+
// selected features, default color for unselected points
|
|
551
|
+
// - geneSelection with featureFilterMode 'featureSelection': use colors
|
|
552
|
+
// from "featureColor" for selected features,
|
|
553
|
+
// do not show unselected points
|
|
554
|
+
|
|
555
|
+
// - randomByFeature: random color for each feature
|
|
556
|
+
// (deterministic based on feature index).
|
|
557
|
+
// consider all as selected when featureSelection is null.
|
|
558
|
+
// - randomByFeature with preferFeatureColor: use colors from
|
|
559
|
+
// "featureColor" (array of { name, color: [r, g, b] }) where
|
|
560
|
+
// available, and random colors otherwise.
|
|
561
|
+
// - randomByFeature with featureSelection: random color for
|
|
562
|
+
// selected features, default color for unselected points
|
|
563
|
+
// - randomByFeature with featureSelection and
|
|
564
|
+
// featureFilterMode 'featureSelection': random color for
|
|
565
|
+
// selected features, do not show unselected points
|
|
566
|
+
|
|
567
|
+
// - random: random color for each point
|
|
568
|
+
// (deterministic based on point index).
|
|
569
|
+
// consider all as selected when featureSelection is null.
|
|
570
|
+
// - random with preferFeatureColor: use colors from "featureColor"
|
|
571
|
+
// (array of { name, color: [r, g, b] }) where available,
|
|
572
|
+
// and random colors otherwise.
|
|
573
|
+
// - random with featureSelection: random color for selected points,
|
|
574
|
+
// default color for unselected points
|
|
575
|
+
// - random with featureSelection and
|
|
576
|
+
// featureFilterMode 'featureSelection': random color for selected
|
|
577
|
+
// points, do not show unselected points
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
// ---- spatialLayerColor ----
|
|
581
|
+
if (obsColorEncoding === 'spatialLayerColor') {
|
|
582
|
+
if (!hasFeatureSelection || !hasResolvedIndices) {
|
|
583
|
+
return getSpatialLayerColorShader(staticColor, opacity);
|
|
584
|
+
}
|
|
585
|
+
if (isFiltered) {
|
|
586
|
+
return getSpatialLayerColorFilteredShader(
|
|
587
|
+
staticColor, opacity, featureIndices, featureIndexProp,
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
return getSpatialLayerColorWithSelectionShader(
|
|
591
|
+
staticColor, opacity, featureIndices, defaultColor, featureIndexProp,
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// ---- geneSelection ----
|
|
596
|
+
if (obsColorEncoding === 'geneSelection') {
|
|
597
|
+
if (!featureIndexProp) {
|
|
598
|
+
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
|
+
}
|
|
600
|
+
if (!hasFeatureSelection || !hasResolvedIndices) {
|
|
601
|
+
return getGeneSelectionNoSelectionShader(staticColor, opacity);
|
|
602
|
+
}
|
|
603
|
+
if (isFiltered) {
|
|
604
|
+
return getGeneSelectionFilteredShader(
|
|
605
|
+
featureIndices, resolvedFeatureColors,
|
|
606
|
+
staticColor, opacity, featureIndexProp,
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
return getGeneSelectionWithSelectionShader(
|
|
610
|
+
featureIndices, resolvedFeatureColors,
|
|
611
|
+
staticColor, defaultColor, opacity, featureIndexProp,
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// ---- randomByFeature ----
|
|
616
|
+
if (obsColorEncoding === 'randomByFeature') {
|
|
617
|
+
if (!featureIndexProp) {
|
|
618
|
+
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
|
+
}
|
|
620
|
+
if (!hasFeatureSelection || !hasResolvedIndices) {
|
|
621
|
+
return getRandomByFeatureShader(opacity, featureIndexProp);
|
|
622
|
+
}
|
|
623
|
+
if (isFiltered) {
|
|
624
|
+
return getRandomByFeatureFilteredShader(
|
|
625
|
+
featureIndices, opacity, featureIndexProp,
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
return getRandomByFeatureWithSelectionShader(
|
|
629
|
+
featureIndices, defaultColor, opacity, featureIndexProp,
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// ---- random (per point) ----
|
|
634
|
+
if (obsColorEncoding === 'random') {
|
|
635
|
+
if (!pointIndexProp) {
|
|
636
|
+
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
|
+
}
|
|
638
|
+
if (!hasFeatureSelection || !hasResolvedIndices) {
|
|
639
|
+
return getRandomPerPointShader(opacity, featureIndexProp, pointIndexProp);
|
|
640
|
+
}
|
|
641
|
+
if (isFiltered) {
|
|
642
|
+
return getRandomPerPointFilteredShader(
|
|
643
|
+
featureIndices, opacity, featureIndexProp, pointIndexProp,
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
return getRandomPerPointWithSelectionShader(
|
|
647
|
+
featureIndices, defaultColor, opacity, featureIndexProp, pointIndexProp,
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Fallback: static color.
|
|
652
|
+
return getSpatialLayerColorShader(staticColor, opacity);
|
|
653
|
+
}
|