Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@alleninstitute/vis-core",
"version": "0.0.6",
"version": "0.0.7",
"contributors": [
{
"name": "Lane Sawyer",
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/shared-priority-cache/shared-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type CacheInterface<Item, ItemContent extends Record<string, Cacheable>> = {
};

export type ClientSpec<Item, ItemContent extends Record<string, Cacheable>> = {
isValue: (v: Record<string, Cacheable | undefined>) => v is ItemContent;
isValue: (v: Record<string, Cacheable | undefined>, item: Item) => v is ItemContent;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the api change - its non-breaking. All prior cache-clients that pass an isValue method that ignores the new argument are valid, will work at runtime, and will pass typechecking still.

cacheKeys: (item: Item) => { [k in keyof ItemContent]: string };
onDataArrived?: (cacheKey: string, result: FetchResult) => void;
fetch: (item: Item) => { [k in keyof ItemContent]: (abort: AbortSignal) => Promise<Cacheable> };
Expand Down Expand Up @@ -109,7 +109,7 @@ export class SharedPriorityCache {
get: (k: Item) => {
const keys = spec.cacheKeys(k);
const v = mapFields<Record<string, string>, Cacheable | undefined>(keys, (k) => this.cache.get(k));
return spec.isValue(v) ? v : undefined;
return spec.isValue(v, k) ? v : undefined;
},
has: (k: Item) => {
const atLeastOneMissing = Object.values(spec.cacheKeys(k)).some((ck) => !this.cache.has(ck));
Expand Down
2 changes: 1 addition & 1 deletion packages/scatterbrain/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@alleninstitute/vis-scatterbrain",
"version": "0.0.2",
"version": "0.0.3",
"contributors": [
{
"name": "Lane Sawyer",
Expand Down
64 changes: 64 additions & 0 deletions packages/scatterbrain/src/cache-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { Cacheable, SharedPriorityCache } from '@alleninstitute/vis-core';
import reduce from 'lodash/reduce';
import type { WebGLSafeBasicType } from './typed-array';
import type { ColumnRequest, Item } from './types';

type Content<V extends Cacheable> = Record<string, V>;

export function buildScatterbrainCacheClient<V extends Cacheable>(
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nearly identical to the version that has been deleted from ./renderer.ts - this version is simply slightly more generic.
it does not need to know about regl, nor does it rely on a closure over the set of requested columns to validate its contents prior to serving up data.

cache: SharedPriorityCache,
toCacheValue: (buffer: ArrayBuffer, type: WebGLSafeBasicType) => V,
onDataArrived: () => void,
) {
const client = cache.registerClient<Item, Content<V>>({
cacheKeys: (item) => {
const { dataset, node, columns } = item;
return reduce<Record<string, ColumnRequest>, Record<string, string>>(
columns,
(acc, col, key) => ({
...acc,
[key]: `${dataset.metadata.metadataFileEndpoint}/${node.file}/${col.name}`,
}),
{},
);
},
fetch: (item) => {
const { dataset, node, columns } = item;
const attrs = dataset.metadata.pointAttributes;
const getColumnUrl = (columnName: string) =>
`${dataset.metadata.metadataFileEndpoint}${columnName}/${dataset.metadata.visualizationReferenceId}/${node.file}`;
const getGeneUrl = (columnName: string) =>
`${dataset.metadata.geneFileEndpoint}${columnName}/${dataset.metadata.visualizationReferenceId}/${node.file}`;
const getColumnInfo = (col: ColumnRequest) =>
col.type === 'QUANTITATIVE'
? ({ url: getGeneUrl(col.name), elements: 1, type: 'float' } as const)
: { url: getColumnUrl(col.name), elements: attrs[col.name].elements, type: attrs[col.name].type };

const proms = reduce<Record<string, ColumnRequest>, Record<string, (signal: AbortSignal) => Promise<V>>>(
columns,
(getters, col, key) => {
const { url, type } = getColumnInfo(col);
return {
...getters,
[key]: (signal) =>
fetch(url, { signal }).then((b) =>
b.arrayBuffer().then((buff) => toCacheValue(buff, type)),
),
};
},
{},
);
return proms;
},
isValue: (v, item: Item): v is Content<V> => {
for (const column of Object.keys(item.columns)) {
if (!(column in v)) {
return false;
}
}
return true;
},
onDataArrived,
});
return client;
}
4 changes: 2 additions & 2 deletions packages/scatterbrain/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export { buildScatterbrainCacheClient } from './cache-client';
export { getVisibleItems, loadDataset as loadScatterbrainDataset } from './dataset';
export {
buildRenderFrameFn as buildScatterbrainRenderFn,
buildScatterbrainCacheClient,
setCategoricalLookupTableValues,
updateCategoricalValue,
} from './renderer';
export * from './types';
export { getVisibleItems, loadDataset as loadScatterbrainDataset } from './dataset';
91 changes: 18 additions & 73 deletions packages/scatterbrain/src/renderer.ts
Original file line number Diff line number Diff line change
@@ -1,85 +1,20 @@
import type { SharedPriorityCache } from '@alleninstitute/vis-core';
import type REGL from 'regl';
import type { ColumnRequest, ScatterbrainDataset, SlideviewScatterbrainDataset, TreeNode } from './types';
import { Box2D, type box2D, type vec4 } from '@alleninstitute/vis-geometry';
import { MakeTaggedBufferView } from './typed-array';
import keys from 'lodash/keys';
import reduce from 'lodash/reduce';
import type REGL from 'regl';
import { buildScatterbrainCacheClient } from './cache-client';
import { getVisibleItems, type NodeWithBounds } from './dataset';
import { buildScatterbrainRenderCommand, type Config, configureShader, type ShaderSettings, VBO } from './shader';
import { MakeTaggedBufferView } from './typed-array';
import type { ColumnRequest, ScatterbrainDataset, SlideviewScatterbrainDataset, TreeNode } from './types';

export type Item = Readonly<{
dataset: SlideviewScatterbrainDataset | ScatterbrainDataset;
node: TreeNode;
bounds: box2D;
columns: Record<string, ColumnRequest>;
}>;
type Content = Record<string, VBO>;

export function buildScatterbrainCacheClient(
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see cache-client.ts

allNeededColumns: readonly string[],
regl: REGL.Regl,
cache: SharedPriorityCache,
onDataArrived: () => void,
) {
const client = cache.registerClient<Item, Content>({
cacheKeys: (item) => {
const { dataset, node, columns } = item;
return reduce<Record<string, ColumnRequest>, Record<string, string>>(
columns,
(acc, col, key) => ({
...acc,
[key]: `${dataset.metadata.metadataFileEndpoint}/${node.file}/${col.name}`,
}),
{},
);
},
fetch: (item) => {
const { dataset, node, columns } = item;
const attrs = dataset.metadata.pointAttributes;
const getColumnUrl = (columnName: string) =>
`${dataset.metadata.metadataFileEndpoint}${columnName}/${dataset.metadata.visualizationReferenceId}/${node.file}`;
const getGeneUrl = (columnName: string) =>
`${dataset.metadata.geneFileEndpoint}${columnName}/${dataset.metadata.visualizationReferenceId}/${node.file}`;
const getColumnInfo = (col: ColumnRequest) =>
col.type === 'QUANTITATIVE'
? ({ url: getGeneUrl(col.name), elements: 1, type: 'float' } as const)
: { url: getColumnUrl(col.name), elements: attrs[col.name].elements, type: attrs[col.name].type };

const proms = reduce<Record<string, ColumnRequest>, Record<string, (signal: AbortSignal) => Promise<VBO>>>(
columns,
(getters, col, key) => {
const { url, type } = getColumnInfo(col);
return {
...getters,
[key]: (signal) =>
fetch(url, { signal }).then((b) =>
b.arrayBuffer().then((buff) => {
const typed = MakeTaggedBufferView(type, buff);
return new VBO({
buffer: regl.buffer({ type: type, data: typed.data }),
bytes: buff.byteLength,
type: 'buffer',
});
}),
),
};
},
{},
);
return proms;
},
isValue: (v): v is Content => {
for (const column of allNeededColumns) {
if (!(column in v)) {
return false;
}
}
return true;
},
onDataArrived,
});
return client;
}

function columnsForItem<T extends object>(
config: Config,
Expand Down Expand Up @@ -175,7 +110,7 @@ export function updateCategoricalValue(
type ScatterbrainRenderProps = Omit<Parameters<ReturnType<typeof buildScatterbrainRenderCommand>>[0], 'item'> & {
visibilityThresholdPx: number;
dataset: ScatterbrainDataset | SlideviewScatterbrainDataset;
client: ReturnType<typeof buildScatterbrainCacheClient>;
client: ReturnType<typeof buildScatterbrainCacheClient<VBO>>;
};
/**
*
Expand Down Expand Up @@ -214,8 +149,18 @@ export function buildRenderFrameFn(regl: REGL.Regl, settings: ShaderSettings) {
}
};
const connectToCache = (cache: SharedPriorityCache, onDataArrived: () => void) => {
const allColumns = [...config.categoricalColumns, ...config.quantitativeColumns, config.positionColumn];
const client = buildScatterbrainCacheClient(allColumns, regl, cache, onDataArrived);
const client = buildScatterbrainCacheClient(
cache,
(buff, type) => {
const typed = MakeTaggedBufferView(type, buff);
return new VBO({
buffer: regl.buffer({ type: type, data: typed.data }),
bytes: buff.byteLength,
type: 'buffer',
});
},
onDataArrived,
);
return client;
};
return { render, connectToCache };
Expand Down
12 changes: 12 additions & 0 deletions packages/scatterbrain/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/// Types describing the metadata that gets loaded from scatterbrain.json files ///
// there are 2 variants, slideview and regular - they are distinguished at runtime

import type { box2D } from '@alleninstitute/vis-geometry';

// by checking the parsed metadata for the 'slides' field
export type WebGLSafeBasicType = 'uint8' | 'uint16' | 'int8' | 'int16' | 'uint32' | 'int32' | 'float';

Expand Down Expand Up @@ -82,3 +85,12 @@ export type SlideviewMetadata = CommonMetadata & {

export type SlideviewScatterbrainDataset = { type: 'slideview'; metadata: SlideviewMetadata };
export type ScatterbrainDataset = { type: 'normal'; metadata: ScatterbrainMetadata };

// renderer-specific types:

export type Item = Readonly<{
dataset: SlideviewScatterbrainDataset | ScatterbrainDataset;
node: TreeNode;
bounds: box2D;
columns: Record<string, ColumnRequest>;
}>;
Loading