@vitessce/gl 2.0.0-beta.0
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 +21 -0
- package/dist/BitmaskLayer.js +132 -0
- package/dist/HeatmapBitmapLayer.js +123 -0
- package/dist/HeatmapCompositeTextLayer.js +129 -0
- package/dist/PaddedExpressionHeatmapBitmapLayer.js +129 -0
- package/dist/PixelatedBitmapLayer.js +26 -0
- package/dist/ScaledExpressionExtension/ScaledExpressionExtension.js +105 -0
- package/dist/ScaledExpressionExtension/index.js +2 -0
- package/dist/ScaledExpressionExtension/shader-module.js +32 -0
- package/dist/SelectionExtension/SelectionExtension.js +31 -0
- package/dist/SelectionExtension/index.js +2 -0
- package/dist/SelectionExtension/shader-module.js +20 -0
- package/dist/SelectionLayer.js +167 -0
- package/dist/bitmask-layer-shaders.js +90 -0
- package/dist/constants.js +11 -0
- package/dist/deck.js +4 -0
- package/dist/glsl/index.js +340 -0
- package/dist/heatmap-bitmap-layer-shaders.js +109 -0
- package/dist/heatmap-constants.js +27 -0
- package/dist/index.js +17 -0
- package/dist/layer-controller.js +109 -0
- package/dist/luma.js +2 -0
- package/dist/padded-expression-heatmap-bitmap-layer-shaders.js +143 -0
- package/dist/selection-utils.js +92 -0
- package/dist/spatial.js +281 -0
- package/dist/spatial.test.js +8 -0
- package/dist/viv.js +9 -0
- package/package.json +62 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { colormaps } from './glsl';
|
|
2
|
+
/**
|
|
3
|
+
* No change to the vertex shader from the base BitmapLayer.
|
|
4
|
+
* Reference: https://github.com/visgl/deck.gl/blob/8.2-release/modules/layers/src/bitmap-layer/bitmap-layer-vertex.js
|
|
5
|
+
*/
|
|
6
|
+
export const vertexShader = `
|
|
7
|
+
#define SHADER_NAME heatmap-bitmap-layer-vertex-shader
|
|
8
|
+
|
|
9
|
+
attribute vec2 texCoords;
|
|
10
|
+
attribute vec3 positions;
|
|
11
|
+
attribute vec3 positions64Low;
|
|
12
|
+
|
|
13
|
+
varying vec2 vTexCoord;
|
|
14
|
+
|
|
15
|
+
const vec3 pickingColor = vec3(1.0, 0.0, 0.0);
|
|
16
|
+
|
|
17
|
+
void main(void) {
|
|
18
|
+
geometry.worldPosition = positions;
|
|
19
|
+
geometry.uv = texCoords;
|
|
20
|
+
geometry.pickingColor = pickingColor;
|
|
21
|
+
|
|
22
|
+
gl_Position = project_position_to_clipspace(positions, positions64Low, vec3(0.0), geometry.position);
|
|
23
|
+
DECKGL_FILTER_GL_POSITION(gl_Position, geometry);
|
|
24
|
+
|
|
25
|
+
vTexCoord = texCoords;
|
|
26
|
+
|
|
27
|
+
vec4 color = vec4(0.0);
|
|
28
|
+
DECKGL_FILTER_COLOR(color, geometry);
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
/**
|
|
32
|
+
* Fragment shader adapted to perform aggregation and
|
|
33
|
+
* take color scale functions + sliders into account.
|
|
34
|
+
* Reference: https://github.com/visgl/deck.gl/blob/8.2-release/modules/layers/src/bitmap-layer/bitmap-layer-fragment.js
|
|
35
|
+
* Reference: https://github.com/hms-dbmi/viv/blob/06231ae02cac1ff57ba458c71e9bc59ed2fc4f8b/src/layers/XRLayer/xr-layer-fragment-colormap.webgl1.glsl
|
|
36
|
+
*/
|
|
37
|
+
export const fragmentShader = `
|
|
38
|
+
#define SHADER_NAME heatmap-bitmap-layer-fragment-shader
|
|
39
|
+
|
|
40
|
+
#ifdef GL_ES
|
|
41
|
+
precision mediump float;
|
|
42
|
+
#endif
|
|
43
|
+
|
|
44
|
+
${colormaps}
|
|
45
|
+
|
|
46
|
+
// The texture (GL.LUMINANCE & Uint8Array).
|
|
47
|
+
uniform sampler2D uBitmapTexture;
|
|
48
|
+
|
|
49
|
+
// height x width of the data matrix (i.e x and y are flipped compared to the graphics convention)
|
|
50
|
+
uniform vec2 uOrigDataSize;
|
|
51
|
+
uniform vec2 uReshapedDataSize;
|
|
52
|
+
|
|
53
|
+
uniform vec2 tileIJ;
|
|
54
|
+
uniform vec2 dataIJ;
|
|
55
|
+
uniform vec2 numTiles;
|
|
56
|
+
|
|
57
|
+
// What are the dimensions of the texture (width, height)?
|
|
58
|
+
uniform vec2 uTextureSize;
|
|
59
|
+
|
|
60
|
+
// How many consecutive pixels should be aggregated together along each axis?
|
|
61
|
+
uniform vec2 uAggSize;
|
|
62
|
+
|
|
63
|
+
// What are the values of the color scale sliders?
|
|
64
|
+
uniform vec2 uColorScaleRange;
|
|
65
|
+
|
|
66
|
+
// The texture coordinate, varying (interpolated between values set by the vertex shader).
|
|
67
|
+
varying vec2 vTexCoord;
|
|
68
|
+
|
|
69
|
+
vec2 offsetvTexcoord(vec2 coord) {
|
|
70
|
+
float xTileToDataRatio = uTextureSize.x / uOrigDataSize.y;
|
|
71
|
+
float yTileToDataRatio = uTextureSize.y / uOrigDataSize.x;
|
|
72
|
+
vec2 vTexCoordOffset = vec2(
|
|
73
|
+
(tileIJ.y * xTileToDataRatio) + (coord.x * xTileToDataRatio),
|
|
74
|
+
(tileIJ.x * yTileToDataRatio) + ((1. - coord.y) * yTileToDataRatio)
|
|
75
|
+
);
|
|
76
|
+
return vTexCoordOffset;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
vec2 dataCoordinateFromvTexCoordOffset(vec2 vTexCoordOffset) {
|
|
80
|
+
|
|
81
|
+
// True pixel coordinate on scale of uOrigDataSize
|
|
82
|
+
vec2 viewCoord = vec2(floor(vTexCoordOffset.x * uOrigDataSize.y), floor(vTexCoordOffset.y * uOrigDataSize.x));
|
|
83
|
+
return viewCoord;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
float getIndexFromViewCoord(vec2 viewCoord) {
|
|
87
|
+
return viewCoord.y * uOrigDataSize.y + viewCoord.x;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
vec2 transformDataCoordinate(float index) {
|
|
91
|
+
float textureX = (floor( index / uReshapedDataSize.x )) / uReshapedDataSize.x;
|
|
92
|
+
float textureY = (index - (floor( index / uReshapedDataSize.x ) * uReshapedDataSize.x)) / uReshapedDataSize.y;
|
|
93
|
+
vec2 texturedCoord = vec2(textureY, textureX);
|
|
94
|
+
return texturedCoord;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
void main(void) {
|
|
98
|
+
// Compute 1 pixel in texture coordinates
|
|
99
|
+
vec2 onePixel = vec2(1.0, 1.0) / uTextureSize;
|
|
100
|
+
vec2 vTexCoordOffset = offsetvTexcoord(vTexCoord);
|
|
101
|
+
vec2 viewCoordTransformed = dataCoordinateFromvTexCoordOffset(vTexCoordOffset);
|
|
102
|
+
// Compute (x % aggSizeX, y % aggSizeY).
|
|
103
|
+
// These values will be the number of values to the left / above the current position to consider.
|
|
104
|
+
vec2 modAggSize = vec2(-1.0 * mod(viewCoordTransformed.x, uAggSize.x), -1.0 * mod(viewCoordTransformed.y, uAggSize.y));
|
|
105
|
+
// Take the sum of values along each axis.
|
|
106
|
+
float intensitySum = 0.0;
|
|
107
|
+
vec2 offsetPixels = vec2(0.0, 0.0);
|
|
108
|
+
for(int i = 0; i < 16; i++) {
|
|
109
|
+
// Check to break outer loop early.
|
|
110
|
+
// Uniforms cannot be used as conditions in GLSL for loops.
|
|
111
|
+
if(float(i) >= uAggSize.y) {
|
|
112
|
+
// Done in the y direction.
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
offsetPixels = vec2(offsetPixels.x, (modAggSize.y + float(i)));
|
|
117
|
+
|
|
118
|
+
for(int j = 0; j < 16; j++) {
|
|
119
|
+
// Check to break inner loop early.
|
|
120
|
+
// Uniforms cannot be used as conditions in GLSL for loops.
|
|
121
|
+
if(float(j) >= uAggSize.x) {
|
|
122
|
+
// Done in the x direction.
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
offsetPixels = vec2((modAggSize.x + float(j)), offsetPixels.y);
|
|
126
|
+
float indexFull = getIndexFromViewCoord(viewCoordTransformed + offsetPixels);
|
|
127
|
+
float index = indexFull - (floor(indexFull / (uReshapedDataSize.x * uReshapedDataSize.y)) * (uReshapedDataSize.x * uReshapedDataSize.y));
|
|
128
|
+
vec2 vTexCoordTransformed = transformDataCoordinate(index);
|
|
129
|
+
intensitySum += texture2D(uBitmapTexture, vTexCoordTransformed).r;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Compute the mean value.
|
|
134
|
+
float intensityMean = intensitySum / (uAggSize.x * uAggSize.y);
|
|
135
|
+
// Re-scale using the color scale slider values.
|
|
136
|
+
float scaledIntensityMean = (intensityMean - uColorScaleRange[0]) / max(0.005, (uColorScaleRange[1] - uColorScaleRange[0]));
|
|
137
|
+
|
|
138
|
+
gl_FragColor = COLORMAP_FUNC(clamp(scaledIntensityMean, 0.0, 1.0));
|
|
139
|
+
|
|
140
|
+
geometry.uv = vTexCoord;
|
|
141
|
+
DECKGL_FILTER_COLOR(gl_FragColor, geometry);
|
|
142
|
+
}
|
|
143
|
+
`;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { COORDINATE_SYSTEM } from '@deck.gl/core'; // eslint-disable-line import/no-extraneous-dependencies
|
|
2
|
+
import { DataFilterExtension } from '@deck.gl/extensions'; // eslint-disable-line import/no-extraneous-dependencies
|
|
3
|
+
import SelectionLayer from './SelectionLayer';
|
|
4
|
+
/**
|
|
5
|
+
* Convert a DeckGL layer ID to a "base" layer ID for selection.
|
|
6
|
+
* @param {string} layerId The layer ID to convert.
|
|
7
|
+
* @returns {string} The base layer ID.
|
|
8
|
+
*/
|
|
9
|
+
function getBaseLayerId(layerId) {
|
|
10
|
+
return `base-${layerId}`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Convert a DeckGL layer ID to a "selected" layer ID for selection.
|
|
14
|
+
* @param {string} layerId The layer ID to convert.
|
|
15
|
+
* @returns {string} The base layer ID.
|
|
16
|
+
*/
|
|
17
|
+
function getSelectedLayerId(layerId) {
|
|
18
|
+
return `selected-${layerId}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Construct DeckGL selection layers.
|
|
22
|
+
* @param {string} tool
|
|
23
|
+
* @param {number} zoom
|
|
24
|
+
* @param {string} cellBaseLayerId
|
|
25
|
+
* @param {function} getCellCoords
|
|
26
|
+
* @param {function} updateCellsSelection
|
|
27
|
+
* @returns {object[]} The array of DeckGL selection layers.
|
|
28
|
+
*/
|
|
29
|
+
export function getSelectionLayers(tool, zoom, layerId, getCellCoords, obsIndex, updateCellsSelection, cellsQuadTree, flipY = false) {
|
|
30
|
+
if (!tool) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
const cellBaseLayerId = getBaseLayerId(layerId);
|
|
34
|
+
const editHandlePointRadius = 5 / (zoom + 16);
|
|
35
|
+
return [new SelectionLayer({
|
|
36
|
+
id: 'selection',
|
|
37
|
+
flipY,
|
|
38
|
+
cellsQuadTree,
|
|
39
|
+
getCellCoords,
|
|
40
|
+
coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
|
|
41
|
+
selectionType: tool,
|
|
42
|
+
onSelect: ({ pickingInfos }) => {
|
|
43
|
+
const cellIds = pickingInfos.map(i => obsIndex[i]);
|
|
44
|
+
if (updateCellsSelection) {
|
|
45
|
+
updateCellsSelection(cellIds);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
layerIds: [cellBaseLayerId],
|
|
49
|
+
getTentativeFillColor: () => [255, 255, 255, 95],
|
|
50
|
+
getTentativeLineColor: () => [143, 143, 143, 255],
|
|
51
|
+
getTentativeLineDashArray: () => [7, 4],
|
|
52
|
+
lineWidthMinPixels: 2,
|
|
53
|
+
lineWidthMaxPixels: 2,
|
|
54
|
+
getEditHandlePointColor: () => [0xff, 0xff, 0xff, 0xff],
|
|
55
|
+
getEditHandlePointRadius: () => editHandlePointRadius,
|
|
56
|
+
editHandlePointRadiusScale: 1,
|
|
57
|
+
editHandlePointRadiusMinPixels: editHandlePointRadius,
|
|
58
|
+
editHandlePointRadiusMaxPixels: 2 * editHandlePointRadius,
|
|
59
|
+
})];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get deck.gl layer props for selection overlays.
|
|
63
|
+
* @param {object} props
|
|
64
|
+
* @returns {object} Object with two properties,
|
|
65
|
+
* overlay: overlayProps, base: baseProps,
|
|
66
|
+
* where the values are deck.gl layer props.
|
|
67
|
+
*/
|
|
68
|
+
export function overlayBaseProps(props) {
|
|
69
|
+
const { id, getColor, data, isSelected, ...rest } = props;
|
|
70
|
+
return {
|
|
71
|
+
overlay: {
|
|
72
|
+
id: getSelectedLayerId(id),
|
|
73
|
+
getFillColor: getColor,
|
|
74
|
+
getLineColor: getColor,
|
|
75
|
+
data,
|
|
76
|
+
getFilterValue: isSelected,
|
|
77
|
+
extensions: [new DataFilterExtension({ filterSize: 1 })],
|
|
78
|
+
filterRange: [1, 1],
|
|
79
|
+
...rest,
|
|
80
|
+
},
|
|
81
|
+
base: {
|
|
82
|
+
id: getBaseLayerId(id),
|
|
83
|
+
getLineColor: getColor,
|
|
84
|
+
getFillColor: getColor,
|
|
85
|
+
// Alternatively: contrast outlines with solids:
|
|
86
|
+
// getLineColor: getColor,
|
|
87
|
+
// getFillColor: [255, 255, 255],
|
|
88
|
+
data: data.slice(),
|
|
89
|
+
...rest,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
package/dist/spatial.js
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import isEqual from 'lodash/isEqual';
|
|
2
|
+
import { Matrix4 } from 'math.gl';
|
|
3
|
+
import { divide, compare, unit } from 'mathjs';
|
|
4
|
+
import { VIEWER_PALETTE } from '@vitessce/utils';
|
|
5
|
+
import { getMultiSelectionStats } from './layer-controller';
|
|
6
|
+
import { RENDERING_MODES } from './viv';
|
|
7
|
+
export function square(x, y, r) {
|
|
8
|
+
return [[x, y + r], [x + r, y], [x, y - r], [x - r, y]];
|
|
9
|
+
}
|
|
10
|
+
export const GLOBAL_LABELS = ['z', 't'];
|
|
11
|
+
export const DEFAULT_RASTER_DOMAIN_TYPE = 'Min/Max';
|
|
12
|
+
export const DEFAULT_RASTER_LAYER_PROPS = {
|
|
13
|
+
visible: true,
|
|
14
|
+
colormap: null,
|
|
15
|
+
opacity: 1,
|
|
16
|
+
domainType: DEFAULT_RASTER_DOMAIN_TYPE,
|
|
17
|
+
transparentColor: [0, 0, 0],
|
|
18
|
+
renderingMode: RENDERING_MODES.ADDITIVE,
|
|
19
|
+
use3d: false,
|
|
20
|
+
};
|
|
21
|
+
export const DEFAULT_MOLECULES_LAYER = {
|
|
22
|
+
opacity: 1, radius: 20, visible: true,
|
|
23
|
+
};
|
|
24
|
+
export const DEFAULT_CELLS_LAYER = {
|
|
25
|
+
opacity: 1, radius: 50, visible: true, stroked: false,
|
|
26
|
+
};
|
|
27
|
+
export const DEFAULT_NEIGHBORHOODS_LAYER = {
|
|
28
|
+
visible: false,
|
|
29
|
+
};
|
|
30
|
+
export const DEFAULT_LAYER_TYPE_ORDERING = [
|
|
31
|
+
'molecules',
|
|
32
|
+
'cells',
|
|
33
|
+
'neighborhoods',
|
|
34
|
+
'raster',
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* Get a representative PixelSource from a loader object returned from
|
|
38
|
+
* the Vitessce imaging loaders
|
|
39
|
+
* @param {object} loader { data: (PixelSource[]|PixelSource), metadata, channels } object
|
|
40
|
+
* @param {number=} level Level of the multiscale loader from which to get a PixelSource
|
|
41
|
+
* @returns {object} PixelSource object
|
|
42
|
+
*/
|
|
43
|
+
export function getSourceFromLoader(loader, level) {
|
|
44
|
+
const { data } = loader;
|
|
45
|
+
const source = Array.isArray(data) ? data[(level || data.length - 1)] : data;
|
|
46
|
+
return source;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Helper method to determine whether pixel data is interleaved and rgb or not.
|
|
50
|
+
* @param {object} loader
|
|
51
|
+
* @param {array|null} channels
|
|
52
|
+
*/
|
|
53
|
+
export function isRgb(loader, channels) {
|
|
54
|
+
const source = getSourceFromLoader(loader);
|
|
55
|
+
const { shape, dtype, labels } = source;
|
|
56
|
+
const channelSize = shape[(labels.includes('channel') ? labels.indexOf('channel') : labels.indexOf('c'))];
|
|
57
|
+
if (channelSize === 3 && dtype === 'Uint8') {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
if (channels && channels.length === 3
|
|
61
|
+
&& isEqual(channels[0].color, [255, 0, 0])
|
|
62
|
+
&& isEqual(channels[1].color, [0, 255, 0])
|
|
63
|
+
&& isEqual(channels[2].color, [0, 0, 255])) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
// From spatial/utils.js
|
|
69
|
+
function getMetaWithTransformMatrices(imageMeta, imageLoaders) {
|
|
70
|
+
// Do not fill in transformation matrices if any of the layers specify one.
|
|
71
|
+
const sources = imageLoaders.map(loader => getSourceFromLoader(loader));
|
|
72
|
+
if (imageMeta.map(meta => meta?.metadata?.transform?.matrix
|
|
73
|
+
|| meta?.metadata?.transform?.scale
|
|
74
|
+
|| meta?.metadata?.transform?.translate).some(Boolean)
|
|
75
|
+
|| sources.every(source => !source.meta?.physicalSizes?.x || !source.meta?.physicalSizes?.y)) {
|
|
76
|
+
return imageMeta;
|
|
77
|
+
}
|
|
78
|
+
// Get the minimum physical among all the current images.
|
|
79
|
+
const minPhysicalSize = sources.reduce((acc, source) => {
|
|
80
|
+
const hasZPhyscialSize = source.meta?.physicalSizes?.z?.size;
|
|
81
|
+
const sizes = [
|
|
82
|
+
unit(`${source.meta?.physicalSizes.x.size} ${source.meta?.physicalSizes.x.unit}`.replace('µ', 'u')),
|
|
83
|
+
unit(`${source.meta?.physicalSizes.y.size} ${source.meta?.physicalSizes.y.unit}`.replace('µ', 'u')),
|
|
84
|
+
];
|
|
85
|
+
if (hasZPhyscialSize) {
|
|
86
|
+
sizes.push(unit(`${source.meta?.physicalSizes.z.size} ${source.meta?.physicalSizes.z.unit}`.replace('µ', 'u')));
|
|
87
|
+
}
|
|
88
|
+
acc[0] = (acc[0] === undefined || compare(sizes[0], acc[0]) === -1) ? sizes[0] : acc[0];
|
|
89
|
+
acc[1] = (acc[1] === undefined || compare(sizes[1], acc[1]) === -1) ? sizes[1] : acc[1];
|
|
90
|
+
acc[2] = (acc[2] === undefined || compare(sizes[2], acc[2]) === -1) ? sizes[2] : acc[2];
|
|
91
|
+
return acc;
|
|
92
|
+
}, []);
|
|
93
|
+
const imageMetaWithTransform = imageMeta.map((meta, j) => {
|
|
94
|
+
const source = sources[j];
|
|
95
|
+
const hasZPhyscialSize = source.meta?.physicalSizes?.z?.size;
|
|
96
|
+
const sizes = [
|
|
97
|
+
unit(`${source.meta?.physicalSizes.x.size} ${source.meta?.physicalSizes.x.unit}`.replace('µ', 'u')),
|
|
98
|
+
unit(`${source.meta?.physicalSizes.y.size} ${source.meta?.physicalSizes.y.unit}`.replace('µ', 'u')),
|
|
99
|
+
];
|
|
100
|
+
if (hasZPhyscialSize) {
|
|
101
|
+
sizes.push(unit(`${source.meta?.physicalSizes.z.size} ${source.meta?.physicalSizes.z.unit}`.replace('µ', 'u')));
|
|
102
|
+
}
|
|
103
|
+
// Find the ratio of the sizes to get the scaling factor.
|
|
104
|
+
const scale = sizes.map((i, k) => divide(i, minPhysicalSize[k]));
|
|
105
|
+
// Add in z dimension needed for Matrix4 scale API.
|
|
106
|
+
if (!scale[2]) {
|
|
107
|
+
scale[2] = 1;
|
|
108
|
+
}
|
|
109
|
+
// no need to store/use identity scaling
|
|
110
|
+
if (isEqual(scale, [1, 1, 1])) {
|
|
111
|
+
return meta;
|
|
112
|
+
}
|
|
113
|
+
// Make sure to scale the z direction by one.
|
|
114
|
+
const matrix = new Matrix4().scale([...scale]);
|
|
115
|
+
const newMeta = { ...meta };
|
|
116
|
+
newMeta.metadata = {
|
|
117
|
+
...newMeta.metadata,
|
|
118
|
+
// We don't want to store matrix objects in the view config.
|
|
119
|
+
transform: { matrix: matrix.toArray() },
|
|
120
|
+
};
|
|
121
|
+
return newMeta;
|
|
122
|
+
});
|
|
123
|
+
return imageMetaWithTransform;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Return the midpoint of the global dimensions.
|
|
127
|
+
* @param {object} source PixelSource object from Viv
|
|
128
|
+
* @returns {object} The selection.
|
|
129
|
+
*/
|
|
130
|
+
function getDefaultGlobalSelection(source) {
|
|
131
|
+
const globalIndices = source.labels
|
|
132
|
+
.filter(dim => GLOBAL_LABELS.includes(dim));
|
|
133
|
+
const selection = {};
|
|
134
|
+
globalIndices.forEach((dim) => {
|
|
135
|
+
selection[dim] = Math.floor((source.shape[source.labels.indexOf(dim)] || 0) / 2);
|
|
136
|
+
});
|
|
137
|
+
return selection;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Create a default selection using the midpoint of the available global dimensions,
|
|
141
|
+
* and then the first four available selections from the first selectable channel.
|
|
142
|
+
* @param {object} source PixelSource object from Viv
|
|
143
|
+
* @returns {object} The selection.
|
|
144
|
+
*/
|
|
145
|
+
function buildDefaultSelection(source) {
|
|
146
|
+
const selection = [];
|
|
147
|
+
const globalSelection = getDefaultGlobalSelection(source);
|
|
148
|
+
// First non-global dimension with some sort of selectable values
|
|
149
|
+
const firstNonGlobalDimension = source.labels.filter(dim => !GLOBAL_LABELS.includes(dim)
|
|
150
|
+
&& source.shape[source.labels.indexOf(dim)])[0];
|
|
151
|
+
for (let i = 0; i < Math.min(4, source.shape[source.labels.indexOf(firstNonGlobalDimension)]); i += 1) {
|
|
152
|
+
selection.push({
|
|
153
|
+
[firstNonGlobalDimension]: i,
|
|
154
|
+
...globalSelection,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return selection;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* @param {Array.<number>} shape loader shape
|
|
161
|
+
*/
|
|
162
|
+
export function isInterleaved(shape) {
|
|
163
|
+
const lastDimSize = shape[shape.length - 1];
|
|
164
|
+
return lastDimSize === 3 || lastDimSize === 4;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Initialize the channel selections for an individual layer.
|
|
168
|
+
* @param {object} loader A viv loader instance with channel names appended by Vitessce loaders
|
|
169
|
+
* of the form { data: (PixelSource[]|PixelSource), metadata: Object, channels }
|
|
170
|
+
* @returns {object[]} An array of selected channels with default
|
|
171
|
+
* domain/slider settings.
|
|
172
|
+
*/
|
|
173
|
+
export async function initializeLayerChannels(loader, use3d) {
|
|
174
|
+
const result = [];
|
|
175
|
+
const source = getSourceFromLoader(loader);
|
|
176
|
+
// Add channel automatically as the first avaialable value for each dimension.
|
|
177
|
+
let defaultSelection = buildDefaultSelection(source);
|
|
178
|
+
defaultSelection = isInterleaved(source.shape)
|
|
179
|
+
? [{ ...defaultSelection[0], c: 0 }] : defaultSelection;
|
|
180
|
+
const stats = await getMultiSelectionStats({
|
|
181
|
+
loader: loader.data, selections: defaultSelection, use3d,
|
|
182
|
+
});
|
|
183
|
+
const domains = isRgb(loader, null)
|
|
184
|
+
? [[0, 255], [0, 255], [0, 255]]
|
|
185
|
+
: stats.domains;
|
|
186
|
+
const colors = isRgb(loader, null)
|
|
187
|
+
? [[255, 0, 0], [0, 255, 0], [0, 0, 255]]
|
|
188
|
+
: null;
|
|
189
|
+
const sliders = isRgb(loader, null)
|
|
190
|
+
? [[0, 255], [0, 255], [0, 255]]
|
|
191
|
+
: stats.sliders;
|
|
192
|
+
defaultSelection.forEach((selection, i) => {
|
|
193
|
+
const domain = domains[i];
|
|
194
|
+
const slider = sliders[i];
|
|
195
|
+
const channel = {
|
|
196
|
+
selection,
|
|
197
|
+
// eslint-disable-next-line no-nested-ternary
|
|
198
|
+
color: colors ? colors[i]
|
|
199
|
+
: defaultSelection.length !== 1
|
|
200
|
+
? VIEWER_PALETTE[i] : [255, 255, 255],
|
|
201
|
+
visible: true,
|
|
202
|
+
slider: slider || domain,
|
|
203
|
+
};
|
|
204
|
+
result.push(channel);
|
|
205
|
+
});
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Given a set of image layer loader creator functions,
|
|
210
|
+
* create loader objects for an initial layer or set of layers,
|
|
211
|
+
* which will be selected based on default values predefined in
|
|
212
|
+
* the image data file or otherwise by a heuristic
|
|
213
|
+
* (the midpoint of the layers array).
|
|
214
|
+
* @param {object[]} rasterLayers A list of layer metadata objects with
|
|
215
|
+
* shape { name, type, url, createLoader }.
|
|
216
|
+
* @param {(string[]|null)} rasterRenderLayers A list of default raster layers. Optional.
|
|
217
|
+
*/
|
|
218
|
+
export async function initializeRasterLayersAndChannels(rasterLayers, rasterRenderLayers, usePhysicalSizeScaling) {
|
|
219
|
+
const nextImageLoaders = [];
|
|
220
|
+
let nextImageMetaAndLayers = [];
|
|
221
|
+
const autoImageLayerDefPromises = [];
|
|
222
|
+
// Start all loader creators immediately.
|
|
223
|
+
// Reference: https://eslint.org/docs/rules/no-await-in-loop
|
|
224
|
+
const loaders = await Promise.all(rasterLayers.map(layer => layer.loaderCreator()));
|
|
225
|
+
for (let i = 0; i < rasterLayers.length; i++) {
|
|
226
|
+
const layer = rasterLayers[i];
|
|
227
|
+
const loader = loaders[i];
|
|
228
|
+
nextImageLoaders[i] = loader;
|
|
229
|
+
nextImageMetaAndLayers[i] = layer;
|
|
230
|
+
}
|
|
231
|
+
if (usePhysicalSizeScaling) {
|
|
232
|
+
nextImageMetaAndLayers = getMetaWithTransformMatrices(nextImageMetaAndLayers, nextImageLoaders);
|
|
233
|
+
}
|
|
234
|
+
// No layers were pre-defined so set up the default image layers.
|
|
235
|
+
if (!rasterRenderLayers) {
|
|
236
|
+
// Midpoint of images list as default image to show.
|
|
237
|
+
const layerIndex = Math.floor(rasterLayers.length / 2);
|
|
238
|
+
const loader = nextImageLoaders[layerIndex];
|
|
239
|
+
const autoImageLayerDefPromise = initializeLayerChannels(loader)
|
|
240
|
+
.then(channels => Promise.resolve({
|
|
241
|
+
type: nextImageMetaAndLayers[layerIndex]?.metadata?.isBitmask ? 'bitmask' : 'raster',
|
|
242
|
+
index: layerIndex,
|
|
243
|
+
...DEFAULT_RASTER_LAYER_PROPS,
|
|
244
|
+
channels: channels.map((channel, j) => ({
|
|
245
|
+
...channel,
|
|
246
|
+
...(nextImageMetaAndLayers[layerIndex].channels
|
|
247
|
+
? nextImageMetaAndLayers[layerIndex].channels[j] : []),
|
|
248
|
+
})),
|
|
249
|
+
modelMatrix: nextImageMetaAndLayers[layerIndex]?.metadata?.transform?.matrix,
|
|
250
|
+
transparentColor: layerIndex > 0 ? [0, 0, 0] : null,
|
|
251
|
+
}));
|
|
252
|
+
autoImageLayerDefPromises.push(autoImageLayerDefPromise);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
// The renderLayers parameter is a list of layer names to show by default.
|
|
256
|
+
const globalIndicesOfRenderLayers = rasterRenderLayers
|
|
257
|
+
.map(imageName => rasterLayers.findIndex(image => image.name === imageName));
|
|
258
|
+
for (let i = 0; i < globalIndicesOfRenderLayers.length; i++) {
|
|
259
|
+
const layerIndex = globalIndicesOfRenderLayers[i];
|
|
260
|
+
const loader = nextImageLoaders[layerIndex];
|
|
261
|
+
const autoImageLayerDefPromise = initializeLayerChannels(loader)
|
|
262
|
+
// eslint-disable-next-line no-loop-func
|
|
263
|
+
.then(channels => Promise.resolve({
|
|
264
|
+
type: nextImageMetaAndLayers[layerIndex]?.metadata?.isBitmask ? 'bitmask' : 'raster',
|
|
265
|
+
index: layerIndex,
|
|
266
|
+
...DEFAULT_RASTER_LAYER_PROPS,
|
|
267
|
+
channels: channels.map((channel, j) => ({
|
|
268
|
+
...channel,
|
|
269
|
+
...(nextImageMetaAndLayers[layerIndex].channels
|
|
270
|
+
? nextImageMetaAndLayers[layerIndex].channels[j] : []),
|
|
271
|
+
})),
|
|
272
|
+
domainType: 'Min/Max',
|
|
273
|
+
modelMatrix: nextImageMetaAndLayers[layerIndex]?.metadata?.transform?.matrix,
|
|
274
|
+
transparentColor: i > 0 ? [0, 0, 0] : null,
|
|
275
|
+
}));
|
|
276
|
+
autoImageLayerDefPromises.push(autoImageLayerDefPromise);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const autoImageLayerDefs = await Promise.all(autoImageLayerDefPromises);
|
|
280
|
+
return [autoImageLayerDefs, nextImageLoaders, nextImageMetaAndLayers];
|
|
281
|
+
}
|
package/dist/viv.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ZarrPixelSource, loadOmeTiff, loadOmeZarr, XRLayer,
|
|
3
|
+
getChannelStats, RENDERING_MODES, MAX_CHANNELS,
|
|
4
|
+
getDefaultInitialViewState, ScaleBarLayer, MultiscaleImageLayer,
|
|
5
|
+
AdditiveColormapExtension, ColorPaletteExtension, ImageLayer, VolumeLayer,
|
|
6
|
+
AdditiveColormap3DExtensions, ColorPalette3DExtensions,
|
|
7
|
+
// TODO: deprecated
|
|
8
|
+
DTYPE_VALUES,
|
|
9
|
+
} from '@hms-dbmi/viv';
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vitessce/gl",
|
|
3
|
+
"version": "2.0.0-beta.0",
|
|
4
|
+
"author": "Gehlenborg Lab",
|
|
5
|
+
"homepage": "http://vitessce.io",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/vitessce/vitessce.git"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"main": "dist/index.js",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@deck.gl/aggregation-layers": "8.6.7",
|
|
17
|
+
"@deck.gl/core": "8.6.7",
|
|
18
|
+
"@deck.gl/extensions": "8.6.7",
|
|
19
|
+
"@deck.gl/geo-layers": "8.6.7",
|
|
20
|
+
"@deck.gl/layers": "8.6.7",
|
|
21
|
+
"@deck.gl/mesh-layers": "8.6.7",
|
|
22
|
+
"@deck.gl/react": "8.6.7",
|
|
23
|
+
"@hms-dbmi/viv": "~0.12.6",
|
|
24
|
+
"@loaders.gl/3d-tiles": "^3.0.0",
|
|
25
|
+
"@loaders.gl/core": "^3.0.0",
|
|
26
|
+
"@loaders.gl/images": "^3.0.0",
|
|
27
|
+
"@loaders.gl/loader-utils": "^3.0.0",
|
|
28
|
+
"@luma.gl/constants": "8.5.10",
|
|
29
|
+
"@luma.gl/core": "8.5.10",
|
|
30
|
+
"@luma.gl/engine": "8.5.10",
|
|
31
|
+
"@luma.gl/experimental": "8.5.10",
|
|
32
|
+
"@luma.gl/gltools": "8.5.10",
|
|
33
|
+
"@luma.gl/shadertools": "8.5.10",
|
|
34
|
+
"@luma.gl/webgl": "8.5.10",
|
|
35
|
+
"@math.gl/core": "^3.5.6",
|
|
36
|
+
"@nebula.gl/edit-modes": "0.23.8",
|
|
37
|
+
"@nebula.gl/layers": "0.23.8",
|
|
38
|
+
"@turf/area": "^6.5.0",
|
|
39
|
+
"@turf/boolean-contains": "^6.5.0",
|
|
40
|
+
"@turf/boolean-overlap": "^6.5.0",
|
|
41
|
+
"@turf/boolean-point-in-polygon": "^6.5.0",
|
|
42
|
+
"@turf/boolean-within": "^6.5.0",
|
|
43
|
+
"@turf/centroid": "^6.5.0",
|
|
44
|
+
"@turf/helpers": "^6.5.0",
|
|
45
|
+
"@vitessce/utils": "2.0.0-beta.0",
|
|
46
|
+
"deck.gl": "8.6.7",
|
|
47
|
+
"glslify": "^7.0.0",
|
|
48
|
+
"lodash": "^4.17.21",
|
|
49
|
+
"math.gl": "^3.5.6",
|
|
50
|
+
"mathjs": "^9.2.0",
|
|
51
|
+
"nebula.gl": "0.23.8"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"glsl-colormap": "^1.0.1"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"start": "tsc --watch",
|
|
58
|
+
"build": "tsc",
|
|
59
|
+
"test": "pnpm exec vitest --run -r ../../ --dir .",
|
|
60
|
+
"glslify": "cat src/glsl/colormaps.in.glsl | glslify > src/glsl/colormaps.out.glsl"
|
|
61
|
+
}
|
|
62
|
+
}
|