@alleninstitute/vis-scatterbrain 0.0.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/LICENSE.md ADDED
@@ -0,0 +1,11 @@
1
+ Copyright 2024 Allen Institute
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+
7
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
+
11
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/dist/main.js ADDED
@@ -0,0 +1,721 @@
1
+ import {Box2D as $8Lb3L$Box2D, Vec3 as $8Lb3L$Vec3, Box3D as $8Lb3L$Box3D, visitBFSMaybe as $8Lb3L$visitBFSMaybe, Vec2 as $8Lb3L$Vec2} from "@alleninstitute/vis-geometry";
2
+ import $8Lb3L$lodashkeys from "lodash/keys";
3
+ import $8Lb3L$lodashreduce from "lodash/reduce";
4
+ import {object as $8Lb3L$object, string as $8Lb3L$string, number as $8Lb3L$number, union as $8Lb3L$union, literal as $8Lb3L$literal, array as $8Lb3L$array, undefined as $8Lb3L$undefined} from "zod";
5
+ import * as $8Lb3L$lodash from "lodash";
6
+
7
+
8
+ // lets help the compiler to know that these two types are related:
9
+ const $2d5c1a80b3fa73ab$export$6dd94e6ad11e18f7 = {
10
+ uint8: Uint8Array,
11
+ uint16: Uint16Array,
12
+ uint32: Uint32Array,
13
+ int8: Int8Array,
14
+ int16: Int16Array,
15
+ int32: Int32Array,
16
+ float: Float32Array
17
+ };
18
+ const $2d5c1a80b3fa73ab$var$SizeInBytes = {
19
+ uint8: 1,
20
+ uint16: 2,
21
+ uint32: 4,
22
+ int8: 1,
23
+ int16: 2,
24
+ int32: 4,
25
+ float: 4
26
+ };
27
+ function $2d5c1a80b3fa73ab$export$cc6acfd7a782614d(type) {
28
+ return $2d5c1a80b3fa73ab$var$SizeInBytes[type];
29
+ }
30
+ function $2d5c1a80b3fa73ab$export$5aaeb619be470f70(type, buffer) {
31
+ // note that TS is not smart enough to realize the mapping here, so we have 7 identical, spoonfed
32
+ // cases....
33
+ switch(type){
34
+ case 'uint8':
35
+ return {
36
+ type: type,
37
+ data: new $2d5c1a80b3fa73ab$export$6dd94e6ad11e18f7[type](buffer)
38
+ };
39
+ case 'int8':
40
+ return {
41
+ type: type,
42
+ data: new $2d5c1a80b3fa73ab$export$6dd94e6ad11e18f7[type](buffer)
43
+ };
44
+ case 'uint16':
45
+ return {
46
+ type: type,
47
+ data: new $2d5c1a80b3fa73ab$export$6dd94e6ad11e18f7[type](buffer)
48
+ };
49
+ case 'int16':
50
+ return {
51
+ type: type,
52
+ data: new $2d5c1a80b3fa73ab$export$6dd94e6ad11e18f7[type](buffer)
53
+ };
54
+ case 'uint32':
55
+ return {
56
+ type: type,
57
+ data: new $2d5c1a80b3fa73ab$export$6dd94e6ad11e18f7[type](buffer)
58
+ };
59
+ case 'int32':
60
+ return {
61
+ type: type,
62
+ data: new $2d5c1a80b3fa73ab$export$6dd94e6ad11e18f7[type](buffer)
63
+ };
64
+ case 'float':
65
+ return {
66
+ type: type,
67
+ data: new $2d5c1a80b3fa73ab$export$6dd94e6ad11e18f7[type](buffer)
68
+ };
69
+ default:
70
+ {
71
+ // will be a compile error if we ever add any basic types
72
+ const unreachable = type;
73
+ throw new Error(`unsupported type requested: ${unreachable}`);
74
+ }
75
+ }
76
+ }
77
+
78
+
79
+
80
+
81
+
82
+
83
+
84
+ // adapted from Potree createChildAABB
85
+ // note that if you do not do indexing in precisely the same order
86
+ // as potree octrees, this will not work correctly at all
87
+ function $80d9f621dbaf60a0$var$getChildBoundsUsingPotreeIndexing(parentBounds, index) {
88
+ const min = parentBounds.minCorner;
89
+ const size = (0, $8Lb3L$Vec3).scale((0, $8Lb3L$Box3D).size(parentBounds), 0.5);
90
+ const offset = [
91
+ (index & 4) > 0 ? size[0] : 0,
92
+ (index & 2) > 0 ? size[1] : 0,
93
+ (index & 1) > 0 ? size[2] : 0
94
+ ];
95
+ const newMin = (0, $8Lb3L$Vec3).add(min, offset);
96
+ return {
97
+ minCorner: newMin,
98
+ maxCorner: (0, $8Lb3L$Vec3).add(newMin, size)
99
+ };
100
+ }
101
+ function $80d9f621dbaf60a0$var$children(node) {
102
+ return node.children ?? [];
103
+ }
104
+ function $80d9f621dbaf60a0$var$sanitizeName(fileName) {
105
+ return fileName.replace('.bin', '');
106
+ }
107
+ function $80d9f621dbaf60a0$var$bounds(rootBounds, path) {
108
+ // path is a name like r01373 - indicating a path through the tree, each character is a child index
109
+ let bounds = rootBounds;
110
+ for(let i = 1; i < path.length; i++){
111
+ const ci = Number(path[i]);
112
+ bounds = $80d9f621dbaf60a0$var$getChildBoundsUsingPotreeIndexing(bounds, ci);
113
+ }
114
+ return bounds;
115
+ }
116
+ function $80d9f621dbaf60a0$var$dropZ(box) {
117
+ return {
118
+ minCorner: (0, $8Lb3L$Vec3).xy(box.minCorner),
119
+ maxCorner: (0, $8Lb3L$Vec3).xy(box.maxCorner)
120
+ };
121
+ }
122
+ function $80d9f621dbaf60a0$var$getVisibleItemsInTree(dataset, camera, limit) {
123
+ const { root: root, boundingBox: boundingBox } = dataset;
124
+ const hits = [];
125
+ const rootBounds = (0, $8Lb3L$Box3D).create([
126
+ boundingBox.lx,
127
+ boundingBox.ly,
128
+ boundingBox.lz
129
+ ], [
130
+ boundingBox.ux,
131
+ boundingBox.uy,
132
+ boundingBox.uz
133
+ ]);
134
+ (0, $8Lb3L$visitBFSMaybe)(root, $80d9f621dbaf60a0$var$children, (t)=>{
135
+ const B = $80d9f621dbaf60a0$var$dropZ($80d9f621dbaf60a0$var$bounds(rootBounds, $80d9f621dbaf60a0$var$sanitizeName(t.file)));
136
+ if ((0, $8Lb3L$Box2D).intersection(B, camera.view) && (0, $8Lb3L$Box2D).size(B)[0] > limit) {
137
+ // this node is big enough to render - that means we should check its children as well
138
+ hits.push({
139
+ node: t,
140
+ bounds: B
141
+ });
142
+ return true;
143
+ }
144
+ return false;
145
+ });
146
+ return hits;
147
+ }
148
+ function $80d9f621dbaf60a0$export$a878a9dcecc4099(dataset, camera, visibilitySizeThreshold) {
149
+ // determine
150
+ if (dataset.type === 'normal') return $80d9f621dbaf60a0$var$getVisibleItemsInTree(dataset.metadata, camera, visibilitySizeThreshold);
151
+ // by default, if we pass NO layout info
152
+ const size = [
153
+ dataset.metadata.spatialUnit.maxX - dataset.metadata.spatialUnit.minX,
154
+ dataset.metadata.spatialUnit.maxY - dataset.metadata.spatialUnit.minY
155
+ ];
156
+ // then it means we want to draw all the slides on top of each other
157
+ const defaultVisibility = camera.layout === undefined;
158
+ const hits = [];
159
+ for (const slide of dataset.metadata.slides){
160
+ if (!defaultVisibility && camera.layout?.[slide.featureTypeValueReferenceId] === undefined) continue;
161
+ const grid = camera.layout?.[slide.featureTypeValueReferenceId] ?? [
162
+ 0,
163
+ 0
164
+ ];
165
+ const offset = (0, $8Lb3L$Vec2).mul(grid, size);
166
+ // offset the camera by the opposite of the offset
167
+ hits.push(...$80d9f621dbaf60a0$var$getVisibleItemsInTree(slide.tree, {
168
+ ...camera,
169
+ view: (0, $8Lb3L$Box2D).translate(camera.view, offset)
170
+ }, visibilitySizeThreshold));
171
+ }
172
+ return hits;
173
+ }
174
+ const $80d9f621dbaf60a0$var$pointAttrSchema = $8Lb3L$object({
175
+ name: $8Lb3L$string(),
176
+ size: $8Lb3L$number(),
177
+ elements: $8Lb3L$number(),
178
+ elementSize: $8Lb3L$number(),
179
+ type: $8Lb3L$union([
180
+ $8Lb3L$literal('uint8'),
181
+ $8Lb3L$literal('uint16'),
182
+ $8Lb3L$literal('uint32'),
183
+ $8Lb3L$literal('int8'),
184
+ $8Lb3L$literal('int16'),
185
+ $8Lb3L$literal('int32'),
186
+ $8Lb3L$literal('float')
187
+ ]),
188
+ description: $8Lb3L$string()
189
+ });
190
+ const $80d9f621dbaf60a0$var$commonMetadataSchema = $8Lb3L$object({
191
+ geneFileEndpoint: $8Lb3L$string(),
192
+ metadataFileEndpoint: $8Lb3L$string(),
193
+ visualizationReferenceId: $8Lb3L$string(),
194
+ spatialColumn: $8Lb3L$string(),
195
+ pointAttributes: $8Lb3L$array($80d9f621dbaf60a0$var$pointAttrSchema).transform((attrs)=>{
196
+ return (0, $8Lb3L$lodashreduce)(attrs, (acc, attr)=>({
197
+ ...acc,
198
+ [attr.name]: attr
199
+ }), {});
200
+ })
201
+ });
202
+ const $80d9f621dbaf60a0$var$bbSchema = $8Lb3L$object({
203
+ lx: $8Lb3L$number(),
204
+ ly: $8Lb3L$number(),
205
+ lz: $8Lb3L$number(),
206
+ ux: $8Lb3L$number(),
207
+ uy: $8Lb3L$number(),
208
+ uz: $8Lb3L$number()
209
+ });
210
+ const $80d9f621dbaf60a0$var$treeNodeSchema = $8Lb3L$object({
211
+ file: $8Lb3L$string(),
212
+ numSpecimens: $8Lb3L$number(),
213
+ get children () {
214
+ return $8Lb3L$union([
215
+ $8Lb3L$undefined(),
216
+ $8Lb3L$array($80d9f621dbaf60a0$var$treeNodeSchema)
217
+ ]);
218
+ }
219
+ });
220
+ const $80d9f621dbaf60a0$var$treeSchema = $8Lb3L$object({
221
+ points: $8Lb3L$number(),
222
+ boundingBox: $80d9f621dbaf60a0$var$bbSchema,
223
+ tightBoundingBox: $80d9f621dbaf60a0$var$bbSchema,
224
+ root: $80d9f621dbaf60a0$var$treeNodeSchema
225
+ });
226
+ const $80d9f621dbaf60a0$var$scatterbrainMetadataSchema = $8Lb3L$object({
227
+ ...$80d9f621dbaf60a0$var$treeSchema.shape,
228
+ ...$80d9f621dbaf60a0$var$commonMetadataSchema.shape
229
+ });
230
+ const $80d9f621dbaf60a0$var$slideSchema = $8Lb3L$object({
231
+ featureTypeValueReferenceId: $8Lb3L$string(),
232
+ tree: $80d9f621dbaf60a0$var$treeSchema
233
+ });
234
+ const $80d9f621dbaf60a0$var$spatialRefFrameSchema = $8Lb3L$object({
235
+ anatomicalOrigin: $8Lb3L$string(),
236
+ direction: $8Lb3L$string(),
237
+ unit: $8Lb3L$string(),
238
+ minX: $8Lb3L$number(),
239
+ maxX: $8Lb3L$number(),
240
+ minY: $8Lb3L$number(),
241
+ maxY: $8Lb3L$number()
242
+ });
243
+ const $80d9f621dbaf60a0$var$slideviewMetadataSchema = $8Lb3L$object({
244
+ ...$80d9f621dbaf60a0$var$commonMetadataSchema.shape,
245
+ slides: $8Lb3L$array($80d9f621dbaf60a0$var$slideSchema),
246
+ spatialUnit: $80d9f621dbaf60a0$var$spatialRefFrameSchema
247
+ });
248
+ function $80d9f621dbaf60a0$export$f002011cfa1fc363(raw) {
249
+ if (typeof raw !== 'object' || !raw) return undefined;
250
+ if (raw.slides) {
251
+ const metadata = $80d9f621dbaf60a0$var$slideviewMetadataSchema.safeParse(raw);
252
+ return metadata.success ? {
253
+ type: 'slideview',
254
+ metadata: metadata.data
255
+ } : undefined;
256
+ }
257
+ const metadata = $80d9f621dbaf60a0$var$scatterbrainMetadataSchema.safeParse(raw);
258
+ return metadata.success ? {
259
+ type: 'normal',
260
+ metadata: metadata.data
261
+ } : undefined;
262
+ }
263
+
264
+
265
+ /** biome-ignore-all lint/style/noUnusedTemplateLiteral: not at all helpful*/
266
+
267
+ const { keys: $4ed749098ad6d58d$var$keys, mapValues: $4ed749098ad6d58d$var$mapValues, reduce: $4ed749098ad6d58d$var$reduce } = $8Lb3L$lodash;
268
+ class $4ed749098ad6d58d$export$579776e8aad6da48 {
269
+ constructor(buffer){
270
+ this.buffer = buffer;
271
+ }
272
+ destroy() {
273
+ this.buffer.buffer.destroy();
274
+ }
275
+ sizeInBytes() {
276
+ return this.buffer.bytes;
277
+ }
278
+ }
279
+ function $4ed749098ad6d58d$var$buildVertexShader(utils) {
280
+ return /*glsl*/ `
281
+ precision highp float;
282
+ // attribs //
283
+ ${utils.attributes}
284
+ // uniforms //
285
+ ${utils.uniforms}
286
+
287
+ // utility functions //
288
+ ${utils.commonUtilsGLSL}
289
+
290
+ // per-point interface functions //
291
+
292
+ float isHovered(){
293
+ ${utils.isHovered}
294
+ }
295
+ vec3 getDataPosition(){
296
+ ${utils.getDataPosition}
297
+ }
298
+ float isFilteredIn(){
299
+ ${utils.isFilteredIn}
300
+ }
301
+ // the primary per-point functions, called directly //
302
+ vec4 getClipPosition(){
303
+ ${utils.getClipPosition}
304
+ }
305
+ float getPointSize(){
306
+ ${utils.getPointSize}
307
+ }
308
+ vec4 getColor(){
309
+ ${utils.getColor}
310
+ }
311
+ varying vec4 color;
312
+ void main(){
313
+ color = getColor();
314
+ gl_PointSize = getPointSize();
315
+ gl_Position = getClipPosition();
316
+ }
317
+ `;
318
+ // note that only getColor, getPointSize, and getPosition are called
319
+ // that should make clear that the other fns are indended to simply be useful
320
+ // concepts that the other main fns can call
321
+ }
322
+ function $4ed749098ad6d58d$export$86f4795fff5157c0(config) {
323
+ return {
324
+ vs: $4ed749098ad6d58d$var$buildVertexShader($4ed749098ad6d58d$export$80d376111cc09ad7(config)),
325
+ fs: /*glsl*/ `
326
+ precision highp float;
327
+ varying vec4 color;
328
+ void main(){
329
+ gl_FragColor = color;
330
+ }
331
+ `
332
+ };
333
+ }
334
+ function $4ed749098ad6d58d$var$rangeFor(col) {
335
+ return `${col}_range`;
336
+ }
337
+ function $4ed749098ad6d58d$export$93c8d19583452ece(config, regl) {
338
+ const prop = (p)=>regl.prop(p);
339
+ const { quantitativeColumns: quantitativeColumns, categoricalColumns: categoricalColumns, categoricalTable: categoricalTable, gradientTable: gradientTable, positionColumn: positionColumn } = config;
340
+ const ranges = $4ed749098ad6d58d$var$reduce(quantitativeColumns, (unis, col)=>({
341
+ ...unis,
342
+ [$4ed749098ad6d58d$var$rangeFor(col)]: prop($4ed749098ad6d58d$var$rangeFor(col))
343
+ }), {});
344
+ const { vs: vs, fs: fs } = $4ed749098ad6d58d$export$86f4795fff5157c0(config);
345
+ const uniforms = {
346
+ [categoricalTable]: prop('categoricalLookupTable'),
347
+ [gradientTable]: prop('gradient'),
348
+ ...ranges,
349
+ spatialFilterBox: prop('spatialFilterBox'),
350
+ filteredOutColor: prop('filteredOutColor'),
351
+ view: prop('view'),
352
+ hoveredValue: prop('hoveredValue'),
353
+ screenSize: prop('screenSize'),
354
+ offset: prop('offset')
355
+ };
356
+ const cmd = regl({
357
+ vert: vs,
358
+ frag: fs,
359
+ attributes: [
360
+ positionColumn,
361
+ ...categoricalColumns,
362
+ ...quantitativeColumns
363
+ ].reduce((attribs, col)=>({
364
+ ...attribs,
365
+ [col]: regl.prop(col)
366
+ }), {}),
367
+ uniforms: uniforms,
368
+ blend: {
369
+ enable: false
370
+ },
371
+ primitive: 'points',
372
+ framebuffer: prop('target'),
373
+ count: prop('count')
374
+ });
375
+ return (props)=>{
376
+ const { target: target, hoveredValue: hoveredValue, spatialFilterBox: spatialFilterBox, filteredOutColor: filteredOutColor, gradient: gradient, camera: camera, offset: offset, quantitativeRangeFilters: quantitativeRangeFilters, categoricalLookupTable: categoricalLookupTable, item: item } = props;
377
+ const filterRanges = $4ed749098ad6d58d$var$reduce($4ed749098ad6d58d$var$keys(quantitativeRangeFilters), (acc, cur)=>({
378
+ ...acc,
379
+ [$4ed749098ad6d58d$var$rangeFor(cur)]: quantitativeRangeFilters[cur]
380
+ }), {});
381
+ const { view: view, screenResolution: screenResolution } = camera;
382
+ const { count: count, columnData: columnData } = item;
383
+ const rawBuffers = $4ed749098ad6d58d$var$mapValues(columnData, (vbo)=>vbo.buffer.buffer);
384
+ cmd({
385
+ target: target,
386
+ gradient: gradient,
387
+ hoveredValue: hoveredValue,
388
+ filteredOutColor: filteredOutColor,
389
+ spatialFilterBox: (0, $8Lb3L$Box2D).toFlatArray(spatialFilterBox),
390
+ categoricalLookupTable: categoricalLookupTable,
391
+ offset: offset,
392
+ count: count,
393
+ view: (0, $8Lb3L$Box2D).toFlatArray(view),
394
+ screenSize: screenResolution,
395
+ ...filterRanges,
396
+ ...rawBuffers
397
+ });
398
+ };
399
+ }
400
+ function $4ed749098ad6d58d$var$rangeFilterExpression(quantitativeColumns) {
401
+ return quantitativeColumns.map((attrib)=>/*glsl*/ `within(${attrib},${$4ed749098ad6d58d$var$rangeFor(attrib)})`).join(' * ');
402
+ }
403
+ function $4ed749098ad6d58d$var$categoricalFilterExpression(categoricalColumns, tableSize, tableName) {
404
+ // categorical columns are in order - this array will have the same order as the col in the texture
405
+ const [w, h] = tableSize;
406
+ return categoricalColumns.map((attrib, i)=>/*glsl*/ `step(0.01,texture2D(${tableName},vec2(${i.toFixed(0)}.5,${attrib}+0.5)/vec2(${w.toFixed(1)},${h.toFixed(1)})).a)`).join(' * ');
407
+ }
408
+ function $4ed749098ad6d58d$export$80d376111cc09ad7(config) {
409
+ const { mode: mode, quantitativeColumns: quantitativeColumns, categoricalColumns: categoricalColumns, categoricalTable: categoricalTable, tableSize: tableSize, gradientTable: gradientTable, positionColumn: positionColumn, colorByColumn: colorByColumn } = config;
410
+ const catFilter = $4ed749098ad6d58d$var$categoricalFilterExpression(categoricalColumns, tableSize, categoricalTable);
411
+ const rangeFilter = $4ed749098ad6d58d$var$rangeFilterExpression(quantitativeColumns);
412
+ const uniforms = /*glsl*/ `
413
+ uniform vec4 view;
414
+ uniform vec2 screenSize;
415
+ uniform vec2 offset;
416
+ uniform vec4 spatialFilterBox;
417
+ uniform vec4 filteredOutColor;
418
+ uniform float hoveredValue;
419
+
420
+ uniform sampler2D ${gradientTable};
421
+ uniform sampler2D ${categoricalTable};
422
+ // quantitative columns each need a range value - its the min,max in a vec2
423
+ ${quantitativeColumns.map((col)=>/*glsl*/ `uniform vec2 ${$4ed749098ad6d58d$var$rangeFor(col)};`).join('\n')}
424
+ `;
425
+ const attributes = /*glsl*/ `
426
+ attribute vec2 ${positionColumn};
427
+ ${categoricalColumns.map((col)=>/*glsl*/ `attribute float ${col};`).join('\n')}
428
+ ${quantitativeColumns.map((col)=>/*glsl*/ `attribute float ${col};`).join('\n')}
429
+ `;
430
+ const commonUtilsGLSL = /*glsl*/ `
431
+ vec4 applyCamera(vec3 dataPos){
432
+ vec2 size = view.zw-view.xy;
433
+ vec2 unit = (dataPos.xy-view.xy)/size;
434
+ return vec4((unit*2.0)-1.0,0.0,1.0);
435
+ }
436
+ float rangeParameter(float v, vec2 range){
437
+ return (v-range.x)/(range.y-range.x);
438
+ }
439
+ float within(float v, vec2 range){
440
+ return step(range.x,v)*step(v,range.y);
441
+ }
442
+ `;
443
+ const categoryColumnIndex = categoricalColumns.indexOf(colorByColumn);
444
+ const isCategoricalColor = categoryColumnIndex > -1;
445
+ const hoverCategoryExpr = /*glsl*/ `1.0-step(0.1,abs(${colorByColumn}-hoveredValue))`;
446
+ const isHovered = /*glsl*/ `
447
+ return ${isCategoricalColor ? hoverCategoryExpr : '0.0'};`;
448
+ const isFilteredIn = /*glsl*/ `
449
+ vec3 p = getDataPosition();
450
+ return within(p.x,spatialFilterBox.xz)*within(p.y,spatialFilterBox.yw)
451
+ * ${catFilter.length > 0 ? catFilter : '1.0'}
452
+ * ${rangeFilter.length > 0 ? rangeFilter : '1.0'};
453
+ `;
454
+ const getDataPosition = /*glsl*/ `return vec3(${positionColumn}+offset,0.0);`;
455
+ const getClipPosition = /*glsl*/ `return applyCamera(getDataPosition());`;
456
+ const getPointSize = /*glsl*/ `return mix(2.0,6.0,isHovered());`; // todo!
457
+ // todo - use config options!
458
+ // if the colorByColumn is a categorical column, generate that
459
+ // else, use a range-colorby
460
+ const [w, h] = tableSize;
461
+ const colorByCategorical = /*glsl*/ `
462
+ vec4(texture2D(${categoricalTable},vec2(${categoryColumnIndex.toFixed(0)}.5,${colorByColumn}+0.5)/vec2(${w.toFixed(1)},${h.toFixed(1)})).rgb,1.0)`;
463
+ const colorByQuantitative = /*glsl*/ `
464
+ texture2D(${gradientTable},vec2(rangeParameter(${colorByColumn},${$4ed749098ad6d58d$var$rangeFor(colorByColumn)}),0.5))
465
+ `;
466
+ const colorize = categoryColumnIndex !== -1 ? colorByCategorical : colorByQuantitative;
467
+ const colorByCategoricalId = /*glsl*/ `
468
+ float G = mod(${colorByColumn},256.0);
469
+ float R = mod(${colorByColumn}/256.0,256.0);
470
+ return vec4(R/255.0,G/255.0,0,1);
471
+ `;
472
+ const colorByQuantitativeValue = /*glsl*/ `
473
+ return vec4(0,rangeParameter(${colorByColumn},${$4ed749098ad6d58d$var$rangeFor(colorByColumn)}),0,1);
474
+ `;
475
+ const getColor = mode === 'color' ? /*glsl*/ `
476
+ return mix(filteredOutColor,${colorize},isFilteredIn());
477
+ ` : categoryColumnIndex === -1 ? colorByQuantitativeValue : colorByCategoricalId;
478
+ return {
479
+ attributes: attributes,
480
+ uniforms: uniforms,
481
+ commonUtilsGLSL: commonUtilsGLSL,
482
+ getClipPosition: getClipPosition,
483
+ getColor: getColor,
484
+ getDataPosition: getDataPosition,
485
+ getPointSize: getPointSize,
486
+ isFilteredIn: isFilteredIn,
487
+ isHovered: isHovered
488
+ };
489
+ }
490
+ function $4ed749098ad6d58d$export$7aba14d004f689d1(settings) {
491
+ // given settings that make sense to a caller (stuff about the data we want to visualize)
492
+ // produce an object that can be used to set up some internal config of the shader that would
493
+ // do the visualization
494
+ const { dataset: dataset, categoricalFilters: categoricalFilters, quantitativeFilters: quantitativeFilters, colorBy: colorBy, mode: mode } = settings;
495
+ // figure out the columns we care about
496
+ // assign them names that are safe to use in the shader (A,B,C, whatever)
497
+ const categories = $4ed749098ad6d58d$var$keys(categoricalFilters).toSorted();
498
+ const numCategories = categories.length;
499
+ const longestCategory = $4ed749098ad6d58d$var$reduce($4ed749098ad6d58d$var$keys(categoricalFilters), (highest, cur)=>Math.max(highest, categoricalFilters[cur]), 0);
500
+ // the goal here is to associate column names with shader-safe names
501
+ const initialQuantitativeAttrs = colorBy.kind === 'metadata' ? {} : {
502
+ [colorBy.column]: 'COLOR_BY_MEASURE'
503
+ };
504
+ const initialCategoricalAttrs = colorBy.kind === 'metadata' ? {
505
+ [colorBy.column]: 'COLOR_BY_CATEGORY'
506
+ } : {};
507
+ // we map each quantitative filter name to the shader-safe attribute name: MEASURE_{i}
508
+ const qAttrs = $4ed749098ad6d58d$var$reduce(quantitativeFilters.toSorted(), (quantAttrs, quantFilter, i)=>({
509
+ ...quantAttrs,
510
+ [quantFilter]: `MEASURE_${i.toFixed(0)}`
511
+ }), initialQuantitativeAttrs);
512
+ // we map each categorical filter's name to the shader-safe attribute name: CATEGORY_{i}
513
+ const cAttrs = $4ed749098ad6d58d$var$reduce(categories, (catAttrs, categoricalFilter, i)=>({
514
+ ...catAttrs,
515
+ [categoricalFilter]: `CATEGORY_${i.toFixed(0)}`
516
+ }), initialCategoricalAttrs);
517
+ const colToAttribute = {
518
+ ...qAttrs,
519
+ ...cAttrs,
520
+ [dataset.metadata.spatialColumn]: 'position'
521
+ };
522
+ const config = {
523
+ categoricalColumns: $4ed749098ad6d58d$var$keys(cAttrs).map((columnName)=>colToAttribute[columnName]),
524
+ quantitativeColumns: $4ed749098ad6d58d$var$keys(qAttrs).map((columnName)=>colToAttribute[columnName]),
525
+ categoricalTable: 'lookup',
526
+ gradientTable: 'gradient',
527
+ colorByColumn: colToAttribute[colorBy.column],
528
+ mode: mode,
529
+ positionColumn: 'position',
530
+ tableSize: [
531
+ Math.max(numCategories, 1),
532
+ Math.max(1, longestCategory)
533
+ ]
534
+ };
535
+ return {
536
+ config: config,
537
+ columnNameToShaderName: colToAttribute
538
+ };
539
+ }
540
+
541
+
542
+ function $3ec637361f7d2e55$export$c982972466d431d3(allNeededColumns, regl, cache, onDataArrived) {
543
+ const client = cache.registerClient({
544
+ cacheKeys: (item)=>{
545
+ const { dataset: dataset, node: node, columns: columns } = item;
546
+ return (0, $8Lb3L$lodashreduce)(columns, (acc, col, key)=>({
547
+ ...acc,
548
+ [key]: `${dataset.metadata.metadataFileEndpoint}/${node.file}/${col.name}`
549
+ }), {});
550
+ },
551
+ fetch: (item)=>{
552
+ const { dataset: dataset, node: node, columns: columns } = item;
553
+ const attrs = dataset.metadata.pointAttributes;
554
+ const getColumnUrl = (columnName)=>`${dataset.metadata.metadataFileEndpoint}${columnName}/${dataset.metadata.visualizationReferenceId}/${node.file}`;
555
+ const getGeneUrl = (columnName)=>`${dataset.metadata.geneFileEndpoint}${columnName}/${dataset.metadata.visualizationReferenceId}/${node.file}`;
556
+ const getColumnInfo = (col)=>col.type === 'QUANTITATIVE' ? {
557
+ url: getGeneUrl(col.name),
558
+ elements: 1,
559
+ type: 'float'
560
+ } : {
561
+ url: getColumnUrl(col.name),
562
+ elements: attrs[col.name].elements,
563
+ type: attrs[col.name].type
564
+ };
565
+ const proms = (0, $8Lb3L$lodashreduce)(columns, (getters, col, key)=>{
566
+ const { url: url, type: type } = getColumnInfo(col);
567
+ return {
568
+ ...getters,
569
+ [key]: (signal)=>fetch(url, {
570
+ signal: signal
571
+ }).then((b)=>b.arrayBuffer().then((buff)=>{
572
+ const typed = (0, $2d5c1a80b3fa73ab$export$5aaeb619be470f70)(type, buff);
573
+ return new (0, $4ed749098ad6d58d$export$579776e8aad6da48)({
574
+ buffer: regl.buffer({
575
+ type: type,
576
+ data: typed.data
577
+ }),
578
+ bytes: buff.byteLength,
579
+ type: 'buffer'
580
+ });
581
+ }))
582
+ };
583
+ }, {});
584
+ return proms;
585
+ },
586
+ isValue: (v)=>{
587
+ for (const column of allNeededColumns){
588
+ if (!(column in v)) return false;
589
+ }
590
+ return true;
591
+ },
592
+ onDataArrived: onDataArrived
593
+ });
594
+ return client;
595
+ }
596
+ function $3ec637361f7d2e55$var$columnsForItem(config, col2shader, dataset) {
597
+ const columns = {};
598
+ const s2c = (0, $8Lb3L$lodashreduce)((0, $8Lb3L$lodashkeys)(col2shader), (acc, col)=>({
599
+ ...acc,
600
+ [col2shader[col]]: col
601
+ }), {});
602
+ for (const c of config.categoricalColumns)columns[c] = {
603
+ type: 'METADATA',
604
+ name: s2c[c]
605
+ };
606
+ for (const m of config.quantitativeColumns)columns[m] = {
607
+ type: 'QUANTITATIVE',
608
+ name: s2c[m]
609
+ };
610
+ columns[config.positionColumn] = {
611
+ type: 'METADATA',
612
+ name: dataset.metadata.spatialColumn
613
+ };
614
+ return (item)=>{
615
+ return {
616
+ ...item,
617
+ dataset: dataset,
618
+ columns: columns
619
+ };
620
+ };
621
+ }
622
+ function $3ec637361f7d2e55$export$5a96dbb3dec0dd9(categories, texture) {
623
+ const categoryKeys = (0, $8Lb3L$lodashkeys)(categories).toSorted();
624
+ const columns = categoryKeys.length;
625
+ const rows = (0, $8Lb3L$lodashreduce)(categoryKeys, (highest, category)=>Math.max(highest, (0, $8Lb3L$lodashkeys)(categories[category]).length), 1);
626
+ const data = new Uint8Array(columns * rows * 4);
627
+ const rgbf = [
628
+ 0,
629
+ 0,
630
+ 0,
631
+ 0
632
+ ];
633
+ const empty = [
634
+ 0,
635
+ 0,
636
+ 0,
637
+ 0
638
+ ];
639
+ // write the rgb of the color, and encode the filter boolean into the alpha channel
640
+ for(let columnIndex = 0; columnIndex < columns; columnIndex += 1){
641
+ const category = categories[categoryKeys[columnIndex]];
642
+ const nRows = (0, $8Lb3L$lodashkeys)(category).length;
643
+ for(let rowIndex = 0; rowIndex < nRows; rowIndex += 1){
644
+ const color = category[rowIndex]?.color ?? empty;
645
+ const filtered = category[rowIndex]?.filteredIn ?? false;
646
+ rgbf[0] = color[0] * 255;
647
+ rgbf[1] = color[1] * 255;
648
+ rgbf[2] = color[2] * 255;
649
+ rgbf[3] = filtered ? 255 : 0;
650
+ data.set(rgbf, rowIndex * columns * 4 + columnIndex * 4);
651
+ }
652
+ }
653
+ // calling a texture as a function is REGL shorthand for total re-init of this texture, capable of resizing if needed
654
+ // warning - this is not likely to be fast
655
+ texture({
656
+ data: data,
657
+ width: columns,
658
+ height: rows
659
+ });
660
+ }
661
+ function $3ec637361f7d2e55$export$7369a54665652fef(categories, update, texture) {
662
+ const { category: category, row: row, color: color, filteredIn: filteredIn } = update;
663
+ const col = categories.toSorted().indexOf(category);
664
+ if (texture.width <= col || texture.height <= row || row < 0 || col < 0) // todo - it might be better to let regl throw the same error... think about it
665
+ throw new Error(`attempted to update metadata lookup table with invalid coordinates: row=${row},col=${col} is not within ${texture.width}, ${texture.height}`);
666
+ const data = new Uint8Array(4);
667
+ data[0] = color[0] * 255;
668
+ data[1] = color[1] * 255;
669
+ data[2] = color[2] * 255;
670
+ data[3] = filteredIn ? 255 : 0;
671
+ texture.subimage(data, col, row);
672
+ }
673
+ function $3ec637361f7d2e55$export$a26a63667597ae6e(regl, settings) {
674
+ const { dataset: dataset } = settings;
675
+ const { config: config, columnNameToShaderName: columnNameToShaderName } = (0, $4ed749098ad6d58d$export$7aba14d004f689d1)(settings);
676
+ const prepareQtCell = $3ec637361f7d2e55$var$columnsForItem(config, columnNameToShaderName, dataset);
677
+ const drawQtCell = (0, $4ed749098ad6d58d$export$93c8d19583452ece)(config, regl);
678
+ const render = (props)=>{
679
+ const { camera: camera, dataset: dataset, client: client, visibilityThresholdPx: visibilityThresholdPx } = props;
680
+ // compute the size of a screen pixel in data-space units
681
+ const visibilityThreshold = visibilityThresholdPx * (0, $8Lb3L$Box2D).size(camera.view)[0] / camera.screenResolution[0]; // (units*pixel)/pixel ==> units
682
+ const visibleQtNodes = (0, $80d9f621dbaf60a0$export$a878a9dcecc4099)(dataset, camera, visibilityThreshold).map(prepareQtCell);
683
+ client.setPriorities(visibleQtNodes, []);
684
+ for (const node of visibleQtNodes)if (client.has(node)) {
685
+ const drawable = client.get(node);
686
+ if (drawable) drawQtCell({
687
+ ...props,
688
+ item: {
689
+ columnData: drawable,
690
+ count: node.node.numSpecimens
691
+ }
692
+ });
693
+ }
694
+ };
695
+ const connectToCache = (cache, onDataArrived)=>{
696
+ const allColumns = [
697
+ ...config.categoricalColumns,
698
+ ...config.quantitativeColumns,
699
+ config.positionColumn
700
+ ];
701
+ const client = $3ec637361f7d2e55$export$c982972466d431d3(allColumns, regl, cache, onDataArrived);
702
+ return client;
703
+ };
704
+ return {
705
+ render: render,
706
+ connectToCache: connectToCache
707
+ };
708
+ }
709
+
710
+
711
+ var $36431511c6945406$exports = {};
712
+ /// Types describing the metadata that gets loaded from scatterbrain.json files ///
713
+ // there are 2 variants, slideview and regular - they are distinguished at runtime
714
+ // by checking the parsed metadata for the 'slides' field
715
+
716
+
717
+
718
+
719
+
720
+ export {$3ec637361f7d2e55$export$a26a63667597ae6e as buildScatterbrainRenderFn, $3ec637361f7d2e55$export$c982972466d431d3 as buildScatterbrainCacheClient, $3ec637361f7d2e55$export$5a96dbb3dec0dd9 as setCategoricalLookupTableValues, $3ec637361f7d2e55$export$7369a54665652fef as updateCategoricalValue, $80d9f621dbaf60a0$export$a878a9dcecc4099 as getVisibleItems, $80d9f621dbaf60a0$export$f002011cfa1fc363 as loadScatterbrainDataset};
721
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"mappings":"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","sources":["packages/scatterbrain/src/index.ts","packages/scatterbrain/src/renderer.ts","packages/scatterbrain/src/typed-array.ts","packages/scatterbrain/src/dataset.ts","packages/scatterbrain/src/shader.ts","packages/scatterbrain/src/types.ts"],"sourcesContent":["export {\n buildRenderFrameFn as buildScatterbrainRenderFn,\n buildScatterbrainCacheClient,\n setCategoricalLookupTableValues,\n updateCategoricalValue,\n} from './renderer';\nexport * from './types';\nexport { getVisibleItems, loadDataset as loadScatterbrainDataset } from './dataset';\n","import type { SharedPriorityCache } from '@alleninstitute/vis-core';\nimport type REGL from 'regl';\nimport type { ColumnRequest, ScatterbrainDataset, SlideviewScatterbrainDataset, TreeNode } from './types';\nimport { Box2D, type box2D, type vec4 } from '@alleninstitute/vis-geometry';\nimport { MakeTaggedBufferView } from './typed-array';\nimport keys from 'lodash/keys';\nimport reduce from 'lodash/reduce';\nimport { getVisibleItems, type NodeWithBounds } from './dataset';\nimport { buildScatterbrainRenderCommand, type Config, configureShader, type ShaderSettings, VBO } from './shader';\nexport type Item = Readonly<{\n dataset: SlideviewScatterbrainDataset | ScatterbrainDataset;\n node: TreeNode;\n bounds: box2D;\n columns: Record<string, ColumnRequest>;\n}>;\ntype Content = Record<string, VBO>;\n\nexport function buildScatterbrainCacheClient(\n allNeededColumns: readonly string[],\n regl: REGL.Regl,\n cache: SharedPriorityCache,\n onDataArrived: () => void,\n) {\n const client = cache.registerClient<Item, Content>({\n cacheKeys: (item) => {\n const { dataset, node, columns } = item;\n return reduce<Record<string, ColumnRequest>, Record<string, string>>(\n columns,\n (acc, col, key) => ({\n ...acc,\n [key]: `${dataset.metadata.metadataFileEndpoint}/${node.file}/${col.name}`,\n }),\n {},\n );\n },\n fetch: (item) => {\n const { dataset, node, columns } = item;\n const attrs = dataset.metadata.pointAttributes;\n const getColumnUrl = (columnName: string) =>\n `${dataset.metadata.metadataFileEndpoint}${columnName}/${dataset.metadata.visualizationReferenceId}/${node.file}`;\n const getGeneUrl = (columnName: string) =>\n `${dataset.metadata.geneFileEndpoint}${columnName}/${dataset.metadata.visualizationReferenceId}/${node.file}`;\n const getColumnInfo = (col: ColumnRequest) =>\n col.type === 'QUANTITATIVE'\n ? ({ url: getGeneUrl(col.name), elements: 1, type: 'float' } as const)\n : { url: getColumnUrl(col.name), elements: attrs[col.name].elements, type: attrs[col.name].type };\n\n const proms = reduce<Record<string, ColumnRequest>, Record<string, (signal: AbortSignal) => Promise<VBO>>>(\n columns,\n (getters, col, key) => {\n const { url, type } = getColumnInfo(col);\n return {\n ...getters,\n [key]: (signal) =>\n fetch(url, { signal }).then((b) =>\n b.arrayBuffer().then((buff) => {\n const typed = MakeTaggedBufferView(type, buff);\n return new VBO({\n buffer: regl.buffer({ type: type, data: typed.data }),\n bytes: buff.byteLength,\n type: 'buffer',\n });\n }),\n ),\n };\n },\n {},\n );\n return proms;\n },\n isValue: (v): v is Content => {\n for (const column of allNeededColumns) {\n if (!(column in v)) {\n return false;\n }\n }\n return true;\n },\n onDataArrived,\n });\n return client;\n}\n\nfunction columnsForItem<T extends object>(\n config: Config,\n col2shader: Record<string, string>,\n dataset: ScatterbrainDataset | SlideviewScatterbrainDataset,\n) {\n const columns: Record<string, ColumnRequest> = {};\n const s2c = reduce(\n keys(col2shader),\n (acc, col) => ({ ...acc, [col2shader[col]]: col }),\n {} as Record<string, string>,\n );\n\n for (const c of config.categoricalColumns) {\n columns[c] = { type: 'METADATA', name: s2c[c] };\n }\n for (const m of config.quantitativeColumns) {\n columns[m] = { type: 'QUANTITATIVE', name: s2c[m] };\n }\n columns[config.positionColumn] = { type: 'METADATA', name: dataset.metadata.spatialColumn };\n return (item: T) => {\n return { ...item, dataset, columns };\n };\n}\n\n/**\n * a helper function that MUTATES ALL the values in the given @param texture\n * to set them to the color and filter status as given in the categories record\n * note that the texture's maping to categories is based on a lexical sorting of the names of the\n * categories\n * @param categories\n * @param regl\n * @param texture\n */\nexport function setCategoricalLookupTableValues(\n categories: Record<string, Record<number, { color: vec4; filteredIn: boolean }>>,\n texture: REGL.Texture2D,\n) {\n const categoryKeys = keys(categories).toSorted();\n const columns = categoryKeys.length;\n const rows = reduce(categoryKeys, (highest, category) => Math.max(highest, keys(categories[category]).length), 1);\n const data = new Uint8Array(columns * rows * 4);\n const rgbf = [0, 0, 0, 0];\n const empty = [0, 0, 0, 0] as const;\n // write the rgb of the color, and encode the filter boolean into the alpha channel\n for (let columnIndex = 0; columnIndex < columns; columnIndex += 1) {\n const category = categories[categoryKeys[columnIndex]];\n const nRows = keys(category).length;\n for (let rowIndex = 0; rowIndex < nRows; rowIndex += 1) {\n const color = category[rowIndex]?.color ?? empty;\n const filtered = category[rowIndex]?.filteredIn ?? false;\n rgbf[0] = color[0] * 255;\n rgbf[1] = color[1] * 255;\n rgbf[2] = color[2] * 255;\n rgbf[3] = filtered ? 255 : 0;\n data.set(rgbf, rowIndex * columns * 4 + columnIndex * 4);\n }\n }\n // calling a texture as a function is REGL shorthand for total re-init of this texture, capable of resizing if needed\n // warning - this is not likely to be fast\n texture({ data, width: columns, height: rows });\n}\n/**\n * same as setCategoricalLookupTableValues, except it only writes a single value update to the texture.\n * note that the list of categories given must match those used to construct the texture, and are needed here\n * due to the lexical sorting order determining the column order of the @param texture\n * @param categories\n * @param update\n * @param regl\n * @param texture\n */\nexport function updateCategoricalValue(\n categories: readonly string[],\n update: { category: string; row: number; color: vec4; filteredIn: boolean },\n texture: REGL.Texture2D,\n) {\n const { category, row, color, filteredIn } = update;\n const col = categories.toSorted().indexOf(category);\n if (texture.width <= col || texture.height <= row || row < 0 || col < 0) {\n // todo - it might be better to let regl throw the same error... think about it\n throw new Error(\n `attempted to update metadata lookup table with invalid coordinates: row=${row},col=${col} is not within ${texture.width}, ${texture.height}`,\n );\n }\n const data = new Uint8Array(4);\n data[0] = color[0] * 255;\n data[1] = color[1] * 255;\n data[2] = color[2] * 255;\n data[3] = filteredIn ? 255 : 0;\n texture.subimage(data, col, row);\n}\n\ntype ScatterbrainRenderProps = Omit<Parameters<ReturnType<typeof buildScatterbrainRenderCommand>>[0], 'item'> & {\n visibilityThresholdPx: number;\n dataset: ScatterbrainDataset | SlideviewScatterbrainDataset;\n client: ReturnType<typeof buildScatterbrainCacheClient>;\n};\n/**\n *\n * @param regl a regl context\n * @param settings settings describing the data and how it should be rendered\n * @returns a pair of functions:\n * render: when called with renderable data, will determine the set of visible data, request that data from the client, and then draw all currently available data\n * connectToCache - called to produce a cacheClient, which must be passed to the render function\n */\nexport function buildRenderFrameFn(regl: REGL.Regl, settings: ShaderSettings) {\n const { dataset } = settings;\n const { config, columnNameToShaderName } = configureShader(settings);\n\n const prepareQtCell = columnsForItem<NodeWithBounds>(config, columnNameToShaderName, dataset);\n const drawQtCell = buildScatterbrainRenderCommand(config, regl);\n\n const render = (props: ScatterbrainRenderProps) => {\n const { camera, dataset, client, visibilityThresholdPx } = props;\n // compute the size of a screen pixel in data-space units\n const visibilityThreshold = (visibilityThresholdPx * Box2D.size(camera.view)[0]) / camera.screenResolution[0]; // (units*pixel)/pixel ==> units\n const visibleQtNodes = getVisibleItems(dataset, camera, visibilityThreshold).map(prepareQtCell);\n client.setPriorities(visibleQtNodes, []);\n for (const node of visibleQtNodes) {\n if (client.has(node)) {\n const drawable = client.get(node);\n if (drawable) {\n drawQtCell({\n ...props,\n item: {\n columnData: drawable,\n count: node.node.numSpecimens,\n },\n });\n }\n }\n }\n };\n const connectToCache = (cache: SharedPriorityCache, onDataArrived: () => void) => {\n const allColumns = [...config.categoricalColumns, ...config.quantitativeColumns, config.positionColumn];\n const client = buildScatterbrainCacheClient(allColumns, regl, cache, onDataArrived);\n return client;\n };\n return { render, connectToCache };\n}\n","// lets help the compiler to know that these two types are related:\nexport type TaggedFloat32Array = {\n type: 'float';\n data: Float32Array;\n};\n\nexport type TaggedUint32Array = {\n type: 'uint32';\n data: Uint32Array;\n};\nexport type TaggedInt32Array = {\n type: 'int32';\n data: Int32Array;\n};\n\nexport type TaggedUint16Array = {\n type: 'uint16';\n data: Uint16Array;\n};\nexport type TaggedInt16Array = {\n type: 'int16';\n data: Int16Array;\n};\n\nexport type TaggedUint8Array = {\n type: 'uint8';\n data: Uint8Array;\n};\nexport type TaggedInt8Array = {\n type: 'int8';\n data: Int8Array;\n};\nexport type TaggedTypedArray =\n | TaggedFloat32Array\n | TaggedUint32Array\n | TaggedInt32Array\n | TaggedUint16Array\n | TaggedInt16Array\n | TaggedUint8Array\n | TaggedInt8Array;\nexport type WebGLSafeBasicType = TaggedTypedArray['type'];\n\nexport const BufferConstructors = {\n uint8: Uint8Array,\n uint16: Uint16Array,\n uint32: Uint32Array,\n int8: Int8Array,\n int16: Int16Array,\n int32: Int32Array,\n float: Float32Array,\n} as const;\n\nconst SizeInBytes = {\n uint8: 1,\n uint16: 2,\n uint32: 4,\n int8: 1,\n int16: 2,\n int32: 4,\n float: 4,\n} as const;\nexport function sizeInBytes(type: WebGLSafeBasicType) {\n return SizeInBytes[type];\n}\nexport function MakeTaggedBufferView(type: WebGLSafeBasicType, buffer: ArrayBuffer): TaggedTypedArray {\n // note that TS is not smart enough to realize the mapping here, so we have 7 identical, spoonfed\n // cases....\n switch (type) {\n case 'uint8':\n return { type, data: new BufferConstructors[type](buffer) };\n case 'int8':\n return { type, data: new BufferConstructors[type](buffer) };\n case 'uint16':\n return { type, data: new BufferConstructors[type](buffer) };\n case 'int16':\n return { type, data: new BufferConstructors[type](buffer) };\n case 'uint32':\n return { type, data: new BufferConstructors[type](buffer) };\n case 'int32':\n return { type, data: new BufferConstructors[type](buffer) };\n case 'float':\n return { type, data: new BufferConstructors[type](buffer) };\n default: {\n // will be a compile error if we ever add any basic types\n const unreachable: never = type;\n throw new Error(`unsupported type requested: ${unreachable}`);\n }\n }\n}\n","import {\n Box2D,\n type box2D,\n type box3D,\n Box3D,\n Vec2,\n type vec2,\n type vec3,\n Vec3,\n visitBFSMaybe,\n} from '@alleninstitute/vis-geometry';\nimport type { PointAttribute, ScatterbrainDataset, SlideviewScatterbrainDataset, TreeNode, volumeBound } from './types';\nimport reduce from 'lodash/reduce';\nimport * as z from 'zod';\n\ntype Dataset = ScatterbrainDataset | SlideviewScatterbrainDataset;\n// figure out that path through the tree, given a TreeNode name\n// these names are structured data - so it should always be possible\nexport type NodeWithBounds = { node: TreeNode; bounds: box2D };\n\n// adapted from Potree createChildAABB\n// note that if you do not do indexing in precisely the same order\n// as potree octrees, this will not work correctly at all\nfunction getChildBoundsUsingPotreeIndexing(parentBounds: box3D, index: number) {\n const min = parentBounds.minCorner;\n const size = Vec3.scale(Box3D.size(parentBounds), 0.5);\n const offset: vec3 = [\n (index & 0b0100) > 0 ? size[0] : 0,\n (index & 0b0010) > 0 ? size[1] : 0,\n (index & 0b0001) > 0 ? size[2] : 0,\n ];\n const newMin = Vec3.add(min, offset);\n return {\n minCorner: newMin,\n maxCorner: Vec3.add(newMin, size),\n };\n}\nfunction children(node: TreeNode) {\n return node.children ?? [];\n}\nfunction sanitizeName(fileName: string) {\n return fileName.replace('.bin', '');\n}\nfunction bounds(rootBounds: box3D, path: string) {\n // path is a name like r01373 - indicating a path through the tree, each character is a child index\n let bounds = rootBounds;\n for (let i = 1; i < path.length; i++) {\n const ci = Number(path[i]);\n bounds = getChildBoundsUsingPotreeIndexing(bounds, ci);\n }\n return bounds;\n}\nfunction dropZ(box: box3D) {\n return {\n minCorner: Vec3.xy(box.minCorner),\n maxCorner: Vec3.xy(box.maxCorner),\n };\n}\n\nfunction getVisibleItemsInTree(\n dataset: { root: TreeNode; boundingBox: volumeBound },\n camera: { view: box2D; screenResolution: vec2 },\n limit: number,\n) {\n const { root, boundingBox } = dataset;\n const hits: { node: TreeNode; bounds: box2D }[] = [];\n const rootBounds = Box3D.create(\n [boundingBox.lx, boundingBox.ly, boundingBox.lz],\n [boundingBox.ux, boundingBox.uy, boundingBox.uz],\n );\n visitBFSMaybe<TreeNode>(root, children, (t) => {\n const B = dropZ(bounds(rootBounds, sanitizeName(t.file)));\n if (Box2D.intersection(B, camera.view) && Box2D.size(B)[0] > limit) {\n // this node is big enough to render - that means we should check its children as well\n hits.push({ node: t, bounds: B });\n return true;\n }\n return false;\n });\n return hits;\n}\n\nexport function getVisibleItems(\n dataset: Dataset,\n camera: { view: box2D; screenResolution: vec2; layout?: Record<string, vec2> },\n visibilitySizeThreshold: number,\n) {\n // determine\n if (dataset.type === 'normal') {\n return getVisibleItemsInTree(dataset.metadata, camera, visibilitySizeThreshold);\n }\n // by default, if we pass NO layout info\n const size: vec2 = [\n dataset.metadata.spatialUnit.maxX - dataset.metadata.spatialUnit.minX,\n dataset.metadata.spatialUnit.maxY - dataset.metadata.spatialUnit.minY,\n ];\n // then it means we want to draw all the slides on top of each other\n const defaultVisibility = camera.layout === undefined;\n const hits: NodeWithBounds[] = [];\n for (const slide of dataset.metadata.slides) {\n if (!defaultVisibility && camera.layout?.[slide.featureTypeValueReferenceId] === undefined) {\n // the camera has a layout, but this slide isn't in it - dont draw it\n continue;\n }\n const grid = camera.layout?.[slide.featureTypeValueReferenceId] ?? [0, 0];\n\n const offset = Vec2.mul(grid, size);\n // offset the camera by the opposite of the offset\n hits.push(\n ...getVisibleItemsInTree(\n slide.tree,\n { ...camera, view: Box2D.translate(camera.view, offset) },\n visibilitySizeThreshold,\n ),\n );\n }\n return hits;\n}\nconst pointAttrSchema = z.object({\n name: z.string(),\n size: z.number(),\n elements: z.number(), // values per point (so a vector xy would have 2)\n elementSize: z.number(), // size of an element, given in bytes (for example float would have 4)\n type: z.union([\n z.literal('uint8'),\n z.literal('uint16'),\n z.literal('uint32'),\n z.literal('int8'),\n z.literal('int16'),\n z.literal('int32'),\n z.literal('float'),\n ]),\n description: z.string(),\n});\nconst commonMetadataSchema = z.object({\n geneFileEndpoint: z.string(),\n metadataFileEndpoint: z.string(),\n visualizationReferenceId: z.string(),\n spatialColumn: z.string(),\n pointAttributes: z.array(pointAttrSchema).transform((attrs) => {\n return reduce<PointAttribute, Record<string, PointAttribute>>(\n attrs,\n (acc, attr) => ({ ...acc, [attr.name]: attr }),\n {},\n );\n }),\n});\nconst bbSchema = z.object({\n lx: z.number(),\n ly: z.number(),\n lz: z.number(),\n ux: z.number(),\n uy: z.number(),\n uz: z.number(),\n});\nconst treeNodeSchema = z.object({\n file: z.string(),\n numSpecimens: z.number(),\n get children() {\n return z.union([z.undefined(), z.array(treeNodeSchema)]);\n },\n});\nconst treeSchema = z.object({\n points: z.number(),\n boundingBox: bbSchema,\n tightBoundingBox: bbSchema,\n root: treeNodeSchema,\n});\nconst scatterbrainMetadataSchema = z.object({\n ...treeSchema.shape,\n ...commonMetadataSchema.shape,\n});\nconst slideSchema = z.object({\n featureTypeValueReferenceId: z.string(),\n tree: treeSchema,\n});\nconst spatialRefFrameSchema = z.object({\n anatomicalOrigin: z.string(),\n direction: z.string(),\n unit: z.string(),\n minX: z.number(),\n maxX: z.number(),\n minY: z.number(),\n maxY: z.number(),\n});\nconst slideviewMetadataSchema = z.object({\n ...commonMetadataSchema.shape,\n slides: z.array(slideSchema),\n spatialUnit: spatialRefFrameSchema,\n});\n\n// biome-ignore lint/suspicious/noExplicitAny: this fn is intended to accept the return value of JSON.parse() - any is appropriate here\nexport function loadDataset(raw: any): Dataset | undefined {\n if (typeof raw !== 'object' || !raw) return undefined;\n\n if (raw.slides) {\n const metadata = slideviewMetadataSchema.safeParse(raw);\n return metadata.success ? { type: 'slideview', metadata: metadata.data } : undefined;\n }\n const metadata = scatterbrainMetadataSchema.safeParse(raw);\n return metadata.success ? { type: 'normal', metadata: metadata.data } : undefined;\n}\n","/** biome-ignore-all lint/style/noUnusedTemplateLiteral: not at all helpful*/\n\nimport type REGL from 'regl';\nimport type { ScatterbrainDataset, SlideviewScatterbrainDataset } from './types';\nimport type { Cacheable, CachedVertexBuffer } from '@alleninstitute/vis-core';\nimport { Box2D, type box2D, type Interval, type vec2, type vec4 } from '@alleninstitute/vis-geometry';\nimport * as lodash from 'lodash';\nconst { keys, mapValues, reduce } = lodash;\n\n// the set of columns and what to do with them can vary\n// there might be 3 categorical columns and 2 range columns\n// each range column (a vertex attrib) uses a uniform vec2 as its filter range\n// due to a variety of limitations in WebGL / GLSL 1 - this is about as general as we can\n// get without a great deal of extra performance cost\n\n// scatterbrain does scatterplot rendering\n// its main claim to fame is handling complex filtering\n// when generating shaders, most of the variable parts flow through these\n// utilities - this type's fields are the names of GLSL functions - the contents\n// of the string must be the body of that function. All functions should take NO arguments\n// and simply refer to global uniforms / attribs directly.\n// as a quick reminder - recursion of any kind is forbidden in GLSL - so be careful\n// as it will not be possible to detect this until runtime.\n// its not unreasonable for some of these utils to call each other - just be sure to\n// avoid recursion!\n// note - you dont have to use these! these are just kinda like guide-rails for\n// patterns we've seen in our shaders so far! you could easily generate your own\n// totally custom shaders!\n\ntype ScatterbrainShaderUtils = {\n uniforms: string; // the GLSL declarations of the uniforms for this shader\n attributes: string; // the GLSL declarations of the vertex attributes for this shader\n commonUtilsGLSL: string; // prepend any GLSL to the final vertex shader\n isFilteredIn: string; // ()->float\n isHovered: string; // ()->float\n getColor: string; // ()-> vec4\n getDataPosition: string; // ()-> vec3 // the position of the point in data-space\n getClipPosition: string; // ()-> vec4 // the position of the point in clip space - (hint - apply the camera to data-space)\n getPointSize: string; // ()->float\n};\nexport class VBO implements Cacheable {\n buffer: CachedVertexBuffer;\n constructor(buffer: CachedVertexBuffer) {\n this.buffer = buffer;\n }\n destroy() {\n this.buffer.buffer.destroy();\n }\n sizeInBytes() {\n return this.buffer.bytes;\n }\n}\n\nfunction buildVertexShader(utils: ScatterbrainShaderUtils) {\n return /*glsl*/ `\n precision highp float;\n // attribs //\n ${utils.attributes}\n // uniforms //\n ${utils.uniforms}\n \n // utility functions //\n ${utils.commonUtilsGLSL}\n\n // per-point interface functions //\n \n float isHovered(){\n ${utils.isHovered}\n }\n vec3 getDataPosition(){\n ${utils.getDataPosition}\n }\n float isFilteredIn(){\n ${utils.isFilteredIn}\n }\n // the primary per-point functions, called directly //\n vec4 getClipPosition(){\n ${utils.getClipPosition}\n }\n float getPointSize(){\n ${utils.getPointSize}\n }\n vec4 getColor(){\n ${utils.getColor}\n }\n varying vec4 color;\n void main(){\n color = getColor();\n gl_PointSize = getPointSize();\n gl_Position = getClipPosition();\n }\n `;\n // note that only getColor, getPointSize, and getPosition are called\n // that should make clear that the other fns are indended to simply be useful\n // concepts that the other main fns can call\n}\nexport function buildShaders(config: Config) {\n return {\n vs: buildVertexShader(generate(config)),\n fs: /*glsl*/ `\n precision highp float;\n varying vec4 color;\n void main(){\n gl_FragColor = color;\n }\n `,\n };\n}\nexport type Config = {\n mode: 'color' | 'info';\n quantitativeColumns: string[];\n categoricalColumns: string[];\n categoricalTable: string;\n tableSize: vec2;\n gradientTable: string;\n positionColumn: string;\n colorByColumn: string;\n};\nfunction rangeFor(col: string) {\n return `${col}_range`;\n}\nexport type RenderProps = {\n target: REGL.Framebuffer2D | null;\n categoricalLookupTable: REGL.Texture2D;\n gradient: REGL.Texture2D;\n camera: { view: box2D; screenResolution: vec2 };\n offset: vec2;\n filteredOutColor: vec4;\n spatialFilterBox: box2D;\n quantitativeRangeFilters: Record<string, vec2>;\n hoveredValue: number;\n item: {\n count: number;\n columnData: Record<string, VBO>;\n };\n};\nexport function buildScatterbrainRenderCommand(config: Config, regl: REGL.Regl) {\n const prop = (p: string) => regl.prop<any, string>(p);\n const { quantitativeColumns, categoricalColumns, categoricalTable, gradientTable, positionColumn } = config;\n const ranges = reduce(\n quantitativeColumns,\n (unis, col) => ({ ...unis, [rangeFor(col)]: prop(rangeFor(col)) }),\n {} as Record<string, REGL.DynamicVariable<any>>,\n );\n const { vs, fs } = buildShaders(config);\n const uniforms = {\n [categoricalTable]: prop('categoricalLookupTable'),\n [gradientTable]: prop('gradient'),\n ...ranges,\n spatialFilterBox: prop('spatialFilterBox'),\n filteredOutColor: prop('filteredOutColor'),\n view: prop('view'),\n hoveredValue: prop('hoveredValue'),\n screenSize: prop('screenSize'),\n offset: prop('offset'),\n };\n const cmd = regl({\n vert: vs,\n frag: fs,\n attributes: [positionColumn, ...categoricalColumns, ...quantitativeColumns].reduce(\n (attribs, col) => ({ ...attribs, [col]: regl.prop<any, string>(col) }),\n {},\n ),\n uniforms,\n blend: {\n enable: false,\n },\n primitive: 'points',\n framebuffer: prop('target'),\n count: prop('count'),\n });\n\n return (props: RenderProps) => {\n const {\n target,\n hoveredValue,\n spatialFilterBox,\n filteredOutColor,\n gradient,\n camera,\n offset,\n quantitativeRangeFilters,\n categoricalLookupTable,\n item,\n } = props;\n const filterRanges = reduce(\n keys(quantitativeRangeFilters),\n (acc, cur) => ({ ...acc, [rangeFor(cur)]: quantitativeRangeFilters[cur] }),\n {},\n );\n const { view, screenResolution } = camera;\n const { count, columnData } = item;\n const rawBuffers = mapValues(columnData, (vbo) => vbo.buffer.buffer);\n cmd({\n target,\n gradient,\n hoveredValue,\n filteredOutColor,\n spatialFilterBox: Box2D.toFlatArray(spatialFilterBox),\n categoricalLookupTable,\n offset,\n count,\n view: Box2D.toFlatArray(view),\n screenSize: screenResolution,\n ...filterRanges,\n ...rawBuffers,\n });\n };\n}\n\nfunction rangeFilterExpression(quantitativeColumns: readonly string[]) {\n return quantitativeColumns.map((attrib) => /*glsl*/ `within(${attrib},${rangeFor(attrib)})`).join(' * ');\n}\nfunction categoricalFilterExpression(categoricalColumns: readonly string[], tableSize: vec2, tableName: string) {\n // categorical columns are in order - this array will have the same order as the col in the texture\n const [w, h] = tableSize;\n return categoricalColumns\n .map(\n (attrib, i) =>\n /*glsl*/ `step(0.01,texture2D(${tableName},vec2(${i.toFixed(0)}.5,${attrib}+0.5)/vec2(${w.toFixed(1)},${h.toFixed(1)})).a)`,\n )\n .join(' * ');\n}\n\nexport function generate(config: Config): ScatterbrainShaderUtils {\n const {\n mode,\n quantitativeColumns,\n categoricalColumns,\n categoricalTable,\n tableSize,\n gradientTable,\n positionColumn,\n colorByColumn,\n } = config;\n const catFilter = categoricalFilterExpression(categoricalColumns, tableSize, categoricalTable);\n const rangeFilter = rangeFilterExpression(quantitativeColumns);\n const uniforms = /*glsl*/ `\n uniform vec4 view;\n uniform vec2 screenSize;\n uniform vec2 offset;\n uniform vec4 spatialFilterBox;\n uniform vec4 filteredOutColor;\n uniform float hoveredValue;\n\n uniform sampler2D ${gradientTable};\n uniform sampler2D ${categoricalTable};\n // quantitative columns each need a range value - its the min,max in a vec2\n ${quantitativeColumns.map((col) => /*glsl*/ `uniform vec2 ${rangeFor(col)};`).join('\\n')}\n `;\n\n const attributes = /*glsl*/ `\n attribute vec2 ${positionColumn};\n ${categoricalColumns.map((col) => /*glsl*/ `attribute float ${col};`).join('\\n')}\n ${quantitativeColumns.map((col) => /*glsl*/ `attribute float ${col};`).join('\\n')}\n `;\n\n const commonUtilsGLSL = /*glsl*/ `\n vec4 applyCamera(vec3 dataPos){\n vec2 size = view.zw-view.xy;\n vec2 unit = (dataPos.xy-view.xy)/size;\n return vec4((unit*2.0)-1.0,0.0,1.0);\n }\n float rangeParameter(float v, vec2 range){\n return (v-range.x)/(range.y-range.x);\n }\n float within(float v, vec2 range){\n return step(range.x,v)*step(v,range.y);\n }\n `;\n const categoryColumnIndex = categoricalColumns.indexOf(colorByColumn);\n const isCategoricalColor = categoryColumnIndex > -1;\n const hoverCategoryExpr = /*glsl*/ `1.0-step(0.1,abs(${colorByColumn}-hoveredValue))`;\n const isHovered = /*glsl*/ `\n return ${isCategoricalColor ? hoverCategoryExpr : '0.0'};`;\n const isFilteredIn = /*glsl*/ `\n vec3 p = getDataPosition();\n return within(p.x,spatialFilterBox.xz)*within(p.y,spatialFilterBox.yw)\n * ${catFilter.length > 0 ? catFilter : '1.0'}\n * ${rangeFilter.length > 0 ? rangeFilter : '1.0'};\n `;\n\n const getDataPosition = /*glsl*/ `return vec3(${positionColumn}+offset,0.0);`;\n const getClipPosition = /*glsl*/ `return applyCamera(getDataPosition());`;\n const getPointSize = /*glsl*/ `return mix(2.0,6.0,isHovered());`; // todo!\n // todo - use config options!\n // if the colorByColumn is a categorical column, generate that\n // else, use a range-colorby\n const [w, h] = tableSize;\n const colorByCategorical = /*glsl*/ `\n vec4(texture2D(${categoricalTable},vec2(${categoryColumnIndex.toFixed(0)}.5,${colorByColumn}+0.5)/vec2(${w.toFixed(1)},${h.toFixed(1)})).rgb,1.0)`;\n\n const colorByQuantitative = /*glsl*/ `\n texture2D(${gradientTable},vec2(rangeParameter(${colorByColumn},${rangeFor(colorByColumn)}),0.5))\n `;\n const colorize = categoryColumnIndex !== -1 ? colorByCategorical : colorByQuantitative;\n\n const colorByCategoricalId = /*glsl*/ ` \n float G = mod(${colorByColumn},256.0);\n float R = mod(${colorByColumn}/256.0,256.0);\n return vec4(R/255.0,G/255.0,0,1);\n `;\n const colorByQuantitativeValue = /*glsl*/ ` \n return vec4(0,rangeParameter(${colorByColumn},${rangeFor(colorByColumn)}),0,1);\n `;\n const getColor =\n mode === 'color'\n ? /*glsl*/ `\n return mix(filteredOutColor,${colorize},isFilteredIn());\n `\n : categoryColumnIndex === -1\n ? colorByQuantitativeValue\n : colorByCategoricalId;\n return {\n attributes,\n uniforms,\n commonUtilsGLSL,\n getClipPosition,\n getColor,\n getDataPosition,\n getPointSize,\n isFilteredIn,\n isHovered,\n };\n}\n\n// these settings impact how the shader is generated -\n// that means changing them may require re-building the renderer (and the shader beneath it)\nexport type ShaderSettings = {\n dataset: ScatterbrainDataset | SlideviewScatterbrainDataset;\n categoricalFilters: Record<string, number>; // category name -> maximum # of distinct values in that category\n quantitativeFilters: readonly string[]; // the names of quantitative variables\n mode: 'color' | 'info';\n colorBy:\n | { kind: 'metadata'; column: string }\n | { kind: 'quantitative'; column: string; gradient: 'viridis' | 'inferno'; range: Interval };\n};\n\nexport function configureShader(settings: ShaderSettings): {\n config: Config;\n columnNameToShaderName: Record<string, string>;\n} {\n // given settings that make sense to a caller (stuff about the data we want to visualize)\n // produce an object that can be used to set up some internal config of the shader that would\n // do the visualization\n const { dataset, categoricalFilters, quantitativeFilters, colorBy, mode } = settings;\n // figure out the columns we care about\n // assign them names that are safe to use in the shader (A,B,C, whatever)\n const categories = keys(categoricalFilters).toSorted();\n const numCategories = categories.length;\n const longestCategory = reduce(\n keys(categoricalFilters),\n (highest, cur) => Math.max(highest, categoricalFilters[cur]),\n 0,\n );\n\n // the goal here is to associate column names with shader-safe names\n const initialQuantitativeAttrs: Record<string, string> =\n colorBy.kind === 'metadata' ? {} : { [colorBy.column]: 'COLOR_BY_MEASURE' };\n const initialCategoricalAttrs: Record<string, string> =\n colorBy.kind === 'metadata' ? { [colorBy.column]: 'COLOR_BY_CATEGORY' } : {};\n // we map each quantitative filter name to the shader-safe attribute name: MEASURE_{i}\n const qAttrs = reduce(\n quantitativeFilters.toSorted(),\n (quantAttrs, quantFilter, i) => ({ ...quantAttrs, [quantFilter]: `MEASURE_${i.toFixed(0)}` }),\n initialQuantitativeAttrs,\n );\n // we map each categorical filter's name to the shader-safe attribute name: CATEGORY_{i}\n const cAttrs = reduce(\n categories,\n (catAttrs, categoricalFilter, i) => ({ ...catAttrs, [categoricalFilter]: `CATEGORY_${i.toFixed(0)}` }),\n initialCategoricalAttrs,\n );\n\n const colToAttribute = { ...qAttrs, ...cAttrs, [dataset.metadata.spatialColumn]: 'position' };\n\n const config: Config = {\n categoricalColumns: keys(cAttrs).map((columnName) => colToAttribute[columnName]),\n quantitativeColumns: keys(qAttrs).map((columnName) => colToAttribute[columnName]),\n categoricalTable: 'lookup',\n gradientTable: 'gradient',\n colorByColumn: colToAttribute[colorBy.column],\n mode,\n positionColumn: 'position',\n tableSize: [Math.max(numCategories, 1), Math.max(1, longestCategory)],\n };\n return { config, columnNameToShaderName: colToAttribute };\n}\n","/// Types describing the metadata that gets loaded from scatterbrain.json files ///\n// there are 2 variants, slideview and regular - they are distinguished at runtime\n// by checking the parsed metadata for the 'slides' field\nexport type WebGLSafeBasicType = 'uint8' | 'uint16' | 'int8' | 'int16' | 'uint32' | 'int32' | 'float';\n\nexport type volumeBound = {\n lx: number;\n ly: number;\n lz: number;\n ux: number;\n uy: number;\n uz: number;\n};\nexport type PointAttribute = {\n name: string;\n size: number; // elements * elementSize - todo ask Peter to remove\n elements: number; // values per point (so a vector xy would have 2)\n elementSize: number; // size of an element, given in bytes (for example float would have 4)\n type: WebGLSafeBasicType;\n description: string;\n};\nexport type TreeNode = {\n file: string;\n numSpecimens: number;\n children: undefined | TreeNode[];\n};\n\ntype MetadataColumn = {\n type: 'METADATA';\n name: string;\n};\ntype QuantitativeColumn = {\n type: 'QUANTITATIVE';\n name: string;\n};\nexport type ColumnRequest = MetadataColumn | QuantitativeColumn;\ntype CommonMetadata = {\n geneFileEndpoint: string;\n metadataFileEndpoint: string;\n visualizationReferenceId: string;\n spatialColumn: string;\n pointAttributes: Record<string, PointAttribute>;\n};\n// scatterbrain distinguishes 2 kinds of datasets - those arranged at the topmost level into slides\n// and 'regular' - which is just a simple, 2D point cloud\nexport type ScatterbrainMetadata = CommonMetadata & {\n points: number;\n boundingBox: volumeBound;\n tightBoundingBox: volumeBound;\n root: TreeNode;\n};\n\n// slideview variant:\ntype Slide = {\n featureTypeValueReferenceId: string;\n tree: {\n root: TreeNode;\n points: number;\n boundingBox: volumeBound;\n tightBoundingBox: volumeBound;\n };\n};\ntype SpatialReferenceFrame = {\n anatomicalOrigin: string;\n direction: string;\n unit: string;\n minX: number;\n maxX: number;\n minY: number;\n maxY: number;\n};\n\nexport type SlideviewMetadata = CommonMetadata & {\n slides: Slide[];\n spatialUnit: SpatialReferenceFrame;\n};\n\n/// Types related to the rendering of scatterbrain datasets ///\n\n// a Dataset is the top level entity\n// an Item is a chunk of that dataset - esentially a singular, loadable, thing\n\nexport type SlideviewScatterbrainDataset = { type: 'slideview'; metadata: SlideviewMetadata };\nexport type ScatterbrainDataset = { type: 'normal'; metadata: ScatterbrainMetadata };\n"],"names":[],"version":3,"file":"main.js.map"}
@@ -0,0 +1,196 @@
1
+ import { box2D, vec2, Interval, vec4 } from "@alleninstitute/vis-geometry";
2
+ import REGL from "regl";
3
+ import { Cacheable, CachedVertexBuffer, SharedPriorityCache, CacheInterface } from "@alleninstitute/vis-core";
4
+ export type WebGLSafeBasicType = 'uint8' | 'uint16' | 'int8' | 'int16' | 'uint32' | 'int32' | 'float';
5
+ export type volumeBound = {
6
+ lx: number;
7
+ ly: number;
8
+ lz: number;
9
+ ux: number;
10
+ uy: number;
11
+ uz: number;
12
+ };
13
+ export type PointAttribute = {
14
+ name: string;
15
+ size: number;
16
+ elements: number;
17
+ elementSize: number;
18
+ type: WebGLSafeBasicType;
19
+ description: string;
20
+ };
21
+ export type TreeNode = {
22
+ file: string;
23
+ numSpecimens: number;
24
+ children: undefined | TreeNode[];
25
+ };
26
+ type MetadataColumn = {
27
+ type: 'METADATA';
28
+ name: string;
29
+ };
30
+ type QuantitativeColumn = {
31
+ type: 'QUANTITATIVE';
32
+ name: string;
33
+ };
34
+ export type ColumnRequest = MetadataColumn | QuantitativeColumn;
35
+ type CommonMetadata = {
36
+ geneFileEndpoint: string;
37
+ metadataFileEndpoint: string;
38
+ visualizationReferenceId: string;
39
+ spatialColumn: string;
40
+ pointAttributes: Record<string, PointAttribute>;
41
+ };
42
+ export type ScatterbrainMetadata = CommonMetadata & {
43
+ points: number;
44
+ boundingBox: volumeBound;
45
+ tightBoundingBox: volumeBound;
46
+ root: TreeNode;
47
+ };
48
+ type Slide = {
49
+ featureTypeValueReferenceId: string;
50
+ tree: {
51
+ root: TreeNode;
52
+ points: number;
53
+ boundingBox: volumeBound;
54
+ tightBoundingBox: volumeBound;
55
+ };
56
+ };
57
+ type SpatialReferenceFrame = {
58
+ anatomicalOrigin: string;
59
+ direction: string;
60
+ unit: string;
61
+ minX: number;
62
+ maxX: number;
63
+ minY: number;
64
+ maxY: number;
65
+ };
66
+ export type SlideviewMetadata = CommonMetadata & {
67
+ slides: Slide[];
68
+ spatialUnit: SpatialReferenceFrame;
69
+ };
70
+ export type SlideviewScatterbrainDataset = {
71
+ type: 'slideview';
72
+ metadata: SlideviewMetadata;
73
+ };
74
+ export type ScatterbrainDataset = {
75
+ type: 'normal';
76
+ metadata: ScatterbrainMetadata;
77
+ };
78
+ type Dataset = ScatterbrainDataset | SlideviewScatterbrainDataset;
79
+ export function getVisibleItems(dataset: Dataset, camera: {
80
+ view: box2D;
81
+ screenResolution: vec2;
82
+ layout?: Record<string, vec2>;
83
+ }, visibilitySizeThreshold: number): {
84
+ node: TreeNode;
85
+ bounds: box2D;
86
+ }[];
87
+ export function loadScatterbrainDataset(raw: any): Dataset | undefined;
88
+ declare class VBO implements Cacheable {
89
+ buffer: CachedVertexBuffer;
90
+ constructor(buffer: CachedVertexBuffer);
91
+ destroy(): void;
92
+ sizeInBytes(): number;
93
+ }
94
+ type Config = {
95
+ mode: 'color' | 'info';
96
+ quantitativeColumns: string[];
97
+ categoricalColumns: string[];
98
+ categoricalTable: string;
99
+ tableSize: vec2;
100
+ gradientTable: string;
101
+ positionColumn: string;
102
+ colorByColumn: string;
103
+ };
104
+ type RenderProps = {
105
+ target: REGL.Framebuffer2D | null;
106
+ categoricalLookupTable: REGL.Texture2D;
107
+ gradient: REGL.Texture2D;
108
+ camera: {
109
+ view: box2D;
110
+ screenResolution: vec2;
111
+ };
112
+ offset: vec2;
113
+ filteredOutColor: vec4;
114
+ spatialFilterBox: box2D;
115
+ quantitativeRangeFilters: Record<string, vec2>;
116
+ hoveredValue: number;
117
+ item: {
118
+ count: number;
119
+ columnData: Record<string, VBO>;
120
+ };
121
+ };
122
+ declare function buildScatterbrainRenderCommand(config: Config, regl: REGL.Regl): (props: RenderProps) => void;
123
+ type ShaderSettings = {
124
+ dataset: ScatterbrainDataset | SlideviewScatterbrainDataset;
125
+ categoricalFilters: Record<string, number>;
126
+ quantitativeFilters: readonly string[];
127
+ mode: 'color' | 'info';
128
+ colorBy: {
129
+ kind: 'metadata';
130
+ column: string;
131
+ } | {
132
+ kind: 'quantitative';
133
+ column: string;
134
+ gradient: 'viridis' | 'inferno';
135
+ range: Interval;
136
+ };
137
+ };
138
+ type Content = Record<string, VBO>;
139
+ export function buildScatterbrainCacheClient(allNeededColumns: readonly string[], regl: REGL.Regl, cache: SharedPriorityCache, onDataArrived: () => void): CacheInterface<Readonly<{
140
+ dataset: SlideviewScatterbrainDataset | ScatterbrainDataset;
141
+ node: TreeNode;
142
+ bounds: box2D;
143
+ columns: Record<string, ColumnRequest>;
144
+ }>, Content>;
145
+ /**
146
+ * a helper function that MUTATES ALL the values in the given @param texture
147
+ * to set them to the color and filter status as given in the categories record
148
+ * note that the texture's maping to categories is based on a lexical sorting of the names of the
149
+ * categories
150
+ * @param categories
151
+ * @param regl
152
+ * @param texture
153
+ */
154
+ export function setCategoricalLookupTableValues(categories: Record<string, Record<number, {
155
+ color: vec4;
156
+ filteredIn: boolean;
157
+ }>>, texture: REGL.Texture2D): void;
158
+ /**
159
+ * same as setCategoricalLookupTableValues, except it only writes a single value update to the texture.
160
+ * note that the list of categories given must match those used to construct the texture, and are needed here
161
+ * due to the lexical sorting order determining the column order of the @param texture
162
+ * @param categories
163
+ * @param update
164
+ * @param regl
165
+ * @param texture
166
+ */
167
+ export function updateCategoricalValue(categories: readonly string[], update: {
168
+ category: string;
169
+ row: number;
170
+ color: vec4;
171
+ filteredIn: boolean;
172
+ }, texture: REGL.Texture2D): void;
173
+ type ScatterbrainRenderProps = Omit<Parameters<ReturnType<typeof buildScatterbrainRenderCommand>>[0], 'item'> & {
174
+ visibilityThresholdPx: number;
175
+ dataset: ScatterbrainDataset | SlideviewScatterbrainDataset;
176
+ client: ReturnType<typeof buildScatterbrainCacheClient>;
177
+ };
178
+ /**
179
+ *
180
+ * @param regl a regl context
181
+ * @param settings settings describing the data and how it should be rendered
182
+ * @returns a pair of functions:
183
+ * render: when called with renderable data, will determine the set of visible data, request that data from the client, and then draw all currently available data
184
+ * connectToCache - called to produce a cacheClient, which must be passed to the render function
185
+ */
186
+ export function buildScatterbrainRenderFn(regl: REGL.Regl, settings: ShaderSettings): {
187
+ render: (props: ScatterbrainRenderProps) => void;
188
+ connectToCache: (cache: SharedPriorityCache, onDataArrived: () => void) => CacheInterface<Readonly<{
189
+ dataset: SlideviewScatterbrainDataset | ScatterbrainDataset;
190
+ node: TreeNode;
191
+ bounds: box2D;
192
+ columns: Record<string, ColumnRequest>;
193
+ }>, Content>;
194
+ };
195
+
196
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"mappings":"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","sources":["packages/scatterbrain/src/src/types.ts","packages/scatterbrain/src/src/typed-array.ts","packages/scatterbrain/src/src/dataset.ts","packages/scatterbrain/src/src/shader.ts","packages/scatterbrain/src/src/renderer.ts","packages/scatterbrain/src/src/index.ts","packages/scatterbrain/src/index.ts"],"sourcesContent":[null,null,null,null,null,null,"export {\n buildRenderFrameFn as buildScatterbrainRenderFn,\n buildScatterbrainCacheClient,\n setCategoricalLookupTableValues,\n updateCategoricalValue,\n} from './renderer';\nexport * from './types';\nexport { getVisibleItems, loadDataset as loadScatterbrainDataset } from './dataset';\n"],"names":[],"version":3,"file":"types.d.ts.map"}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@alleninstitute/vis-scatterbrain",
3
+ "version": "0.0.2",
4
+ "contributors": [
5
+ {
6
+ "name": "Lane Sawyer",
7
+ "email": "lane.sawyer@alleninstitute.org"
8
+ },
9
+ {
10
+ "name": "James Gerstenberger",
11
+ "email": "james.gerstenberger@alleninstitute.org"
12
+ },
13
+ {
14
+ "name": "Noah Shepard",
15
+ "email": "noah.shepard@alleninstitute.org"
16
+ },
17
+ {
18
+ "name": "Skyler Moosman",
19
+ "email": "skyler.moosman@alleninstitute.org"
20
+ },
21
+ {
22
+ "name": "Su Li",
23
+ "email": "su.li@alleninstitute.org"
24
+ },
25
+ {
26
+ "name": "Joel Arbuckle",
27
+ "email": "joel.arbuckle@alleninstitute.org"
28
+ }
29
+ ],
30
+ "license": "BSD-3-Clause",
31
+ "source": "src/index.ts",
32
+ "main": "dist/main.js",
33
+ "types": "dist/types.d.ts",
34
+ "type": "module",
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/AllenInstitute/vis.git"
41
+ },
42
+ "dependencies": {
43
+ "lodash": "4.17.23",
44
+ "regl": "2.1.0",
45
+ "ts-pattern": "5.9.0",
46
+ "zod": "4.3.6",
47
+ "@alleninstitute/vis-geometry": "0.0.8",
48
+ "@alleninstitute/vis-core": "0.0.6"
49
+ },
50
+ "publishConfig": {
51
+ "registry": "https://registry.npmjs.org",
52
+ "access": "public"
53
+ },
54
+ "devDependencies": {
55
+ "@types/lodash": "4.17.24"
56
+ },
57
+ "scripts": {
58
+ "typecheck": "tsc --noEmit",
59
+ "build": "parcel build --no-cache",
60
+ "dev": "parcel watch --port 1239",
61
+ "demo": "vite",
62
+ "test": "vitest --watch",
63
+ "test:ci": "vitest run",
64
+ "coverage": "vitest run --coverage",
65
+ "changelog": "git-cliff -o changelog.md"
66
+ }
67
+ }
package/readme.md ADDED
@@ -0,0 +1,23 @@
1
+
2
+ # Scatterbrain
3
+
4
+ rendering utilities to render scatter plots, with specific support for 'scatterbrain' style (quad-tree spatial indexed) data sources as used for years now in ABC-atlas
5
+
6
+ ## Why? ##
7
+
8
+ We wrote the ABC-atlas version of Scatterbrain rendering in a hot hot hurry a few years ago. At the time, we were preparing for a lot of variation along certain paths of development,
9
+ and as is often the case, those guesses were a bit off the mark. As a result, the flexibility points we built into that version are not helping us much. For example, we take great pains to
10
+ generate shaders with readable "column names" as users select different filter settings. However, for reasons, the names are just referenceIds from the backend, and we really never bother to debug the shaders in that way - they either fail up-front, or work fine, or are broken in more subtle ways that tend to have nothing to do with the names of the data.
11
+
12
+ ## What is the goal here? ##
13
+
14
+ The goal is to modernize and simplify the WebGL powered features of ABC-atlas. We'd like to be able to understand the management of rendering resources, have more comprehensible interop
15
+ with the rest of the React UI system, and in general reduce the confusingly generic (and needlessly generic) nature of various parts, as well as having less verbose and weird areas around
16
+ the actual features we did build (but didn't really prepare for) like:
17
+
18
+ hovering to report cell-info to the rest of the system
19
+ multiple gene coloring / filtering
20
+ slide-view / regular view
21
+
22
+ to this end, we are gonna stick to the Renderer<> interface as given in packages/core/src/abstract/types.ts. We've seen that work with both the somewhat Ill-fated renderServer, as well as the new shared-cache
23
+ stuff, so it seems like it might be ok.