@vitessce/spatial-utils 2.0.3 → 3.0.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/dist/{deflate.b31189ff.mjs → deflate-132dedf0.js} +2 -3
- package/dist/{index.75f0a489.mjs → index-ce1fad7e.js} +37344 -28760
- package/dist/{index.mjs → index.js} +7 -5
- package/dist/{jpeg.1d104fed.mjs → jpeg-258024ce.js} +1 -2
- package/dist/{lerc.95b793c7.mjs → lerc-279c1032.js} +76 -6
- package/dist/{lzw.ade1d376.mjs → lzw-d830df9b.js} +1 -2
- package/dist/{packbits.16b1394a.mjs → packbits-fe766b57.js} +1 -2
- package/dist/{pako.esm.4b234125.mjs → pako.esm-68f84e2a.js} +97 -15
- package/dist/{raw.c3b45ab1.mjs → raw-f15bab0d.js} +1 -2
- package/dist/{webimage.53b9bf75.mjs → webimage-03150187.js} +1 -2
- package/dist-tsc/index.d.ts +2 -0
- package/dist-tsc/index.d.ts.map +1 -0
- package/dist-tsc/index.js +1 -1
- package/dist-tsc/layer-controller.d.ts +23 -0
- package/dist-tsc/layer-controller.d.ts.map +1 -0
- package/dist-tsc/layer-controller.js +109 -0
- package/dist-tsc/spatial.d.ts +87 -0
- package/dist-tsc/spatial.d.ts.map +1 -0
- package/dist-tsc/spatial.js +375 -0
- package/dist-tsc/spatial.test.d.ts +2 -0
- package/dist-tsc/spatial.test.d.ts.map +1 -0
- package/dist-tsc/spatial.test.js +43 -0
- package/package.json +15 -6
- package/src/index.js +4 -1
- package/src/spatial.js +102 -2
- package/src/spatial.test.js +37 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export function square(x: any, y: any, r: any): any[][];
|
|
2
|
+
/**
|
|
3
|
+
* Get a representative PixelSource from a loader object returned from
|
|
4
|
+
* the Vitessce imaging loaders
|
|
5
|
+
* @param {object} loader { data: (PixelSource[]|PixelSource), metadata, channels } object
|
|
6
|
+
* @param {number=} level Level of the multiscale loader from which to get a PixelSource
|
|
7
|
+
* @returns {object} PixelSource object
|
|
8
|
+
*/
|
|
9
|
+
export function getSourceFromLoader(loader: object, level?: number | undefined): object;
|
|
10
|
+
/**
|
|
11
|
+
* Helper method to determine whether pixel data is interleaved and rgb or not.
|
|
12
|
+
* @param {object} loader
|
|
13
|
+
* @param {array|null} channels
|
|
14
|
+
*/
|
|
15
|
+
export function isRgb(loader: object, channels: array | null): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* @param {Array.<number>} shape loader shape
|
|
18
|
+
*/
|
|
19
|
+
export function isInterleaved(shape: Array<number>): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Initialize the channel selections for an individual layer.
|
|
22
|
+
* @param {object} loader A viv loader instance with channel names appended by Vitessce loaders
|
|
23
|
+
* of the form { data: (PixelSource[]|PixelSource), metadata: Object, channels }
|
|
24
|
+
* @returns {object[]} An array of selected channels with default
|
|
25
|
+
* domain/slider settings.
|
|
26
|
+
*/
|
|
27
|
+
export function initializeLayerChannels(loader: object, use3d: any): object[];
|
|
28
|
+
/**
|
|
29
|
+
* Given a set of image layer loader creator functions,
|
|
30
|
+
* create loader objects for an initial layer or set of layers,
|
|
31
|
+
* which will be selected based on default values predefined in
|
|
32
|
+
* the image data file or otherwise by a heuristic
|
|
33
|
+
* (the midpoint of the layers array).
|
|
34
|
+
* @param {object[]} rasterLayers A list of layer metadata objects with
|
|
35
|
+
* shape { name, type, url, createLoader }.
|
|
36
|
+
* @param {(string[]|null)} rasterRenderLayers A list of default raster layers. Optional.
|
|
37
|
+
*/
|
|
38
|
+
export function initializeRasterLayersAndChannels(rasterLayers: object[], rasterRenderLayers: (string[] | null), usePhysicalSizeScaling: any): Promise<any[]>;
|
|
39
|
+
export function getNgffAxes(firstMultiscalesAxes: any): {
|
|
40
|
+
type: string;
|
|
41
|
+
name: string;
|
|
42
|
+
}[];
|
|
43
|
+
export function getNgffAxesForTiff(dimOrder: any): any;
|
|
44
|
+
/**
|
|
45
|
+
* Convert an array of coordinateTransformations objects to a 16-element
|
|
46
|
+
* plain JS array using Matrix4 linear algebra transformation functions.
|
|
47
|
+
* @param {object[]} coordinateTransformations List of objects matching the
|
|
48
|
+
* OME-NGFF v0.4 coordinateTransformations spec.
|
|
49
|
+
* @param {object[]} axes Axes in OME-NGFF v0.4 format, objects
|
|
50
|
+
* with { type, name }.
|
|
51
|
+
* @returns {Array<number>} Array of 16 numbers representing the Matrix4.
|
|
52
|
+
*/
|
|
53
|
+
export function coordinateTransformationsToMatrix(coordinateTransformations: object[], axes: object[]): Array<number>;
|
|
54
|
+
export const GLOBAL_LABELS: string[];
|
|
55
|
+
export const DEFAULT_RASTER_DOMAIN_TYPE: "Min/Max";
|
|
56
|
+
export namespace DEFAULT_RASTER_LAYER_PROPS {
|
|
57
|
+
export const visible: boolean;
|
|
58
|
+
export const colormap: null;
|
|
59
|
+
export const opacity: number;
|
|
60
|
+
export { DEFAULT_RASTER_DOMAIN_TYPE as domainType };
|
|
61
|
+
export const transparentColor: number[];
|
|
62
|
+
export const renderingMode: viv.RENDERING_MODES;
|
|
63
|
+
export const use3d: boolean;
|
|
64
|
+
}
|
|
65
|
+
export namespace DEFAULT_MOLECULES_LAYER {
|
|
66
|
+
const opacity_1: number;
|
|
67
|
+
export { opacity_1 as opacity };
|
|
68
|
+
export const radius: number;
|
|
69
|
+
const visible_1: boolean;
|
|
70
|
+
export { visible_1 as visible };
|
|
71
|
+
}
|
|
72
|
+
export namespace DEFAULT_CELLS_LAYER {
|
|
73
|
+
const opacity_2: number;
|
|
74
|
+
export { opacity_2 as opacity };
|
|
75
|
+
const radius_1: number;
|
|
76
|
+
export { radius_1 as radius };
|
|
77
|
+
const visible_2: boolean;
|
|
78
|
+
export { visible_2 as visible };
|
|
79
|
+
export const stroked: boolean;
|
|
80
|
+
}
|
|
81
|
+
export namespace DEFAULT_NEIGHBORHOODS_LAYER {
|
|
82
|
+
const visible_3: boolean;
|
|
83
|
+
export { visible_3 as visible };
|
|
84
|
+
}
|
|
85
|
+
export const DEFAULT_LAYER_TYPE_ORDERING: string[];
|
|
86
|
+
import { viv } from "@vitessce/gl";
|
|
87
|
+
//# sourceMappingURL=spatial.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spatial.d.ts","sourceRoot":"","sources":["../src/spatial.js"],"names":[],"mappings":"AAOA,wDAEC;AAiCD;;;;;;GAMG;AACH,4CAJW,MAAM,UACN,MAAM,eACJ,MAAM,CAMlB;AAED;;;;GAIG;AACH,8BAHW,MAAM,YACN,QAAM,IAAI,WAiBpB;AAmHD;;GAEG;AACH,qCAFW,MAAO,MAAM,CAAC,WAKxB;AAED;;;;;;GAMG;AACH,gDALW,MAAM,eAEJ,MAAM,EAAE,CAuCpB;AAED;;;;;;;;;GASG;AACH,gEAJW,MAAM,EAAE,sBAER,CAAC,MAAM,EAAE,GAAC,IAAI,CAAC,+CAuEzB;AASD;;;IAuBC;AAED,uDAQC;AAED;;;;;;;;GAQG;AACH,6EANW,MAAM,EAAE,QAER,MAAM,EAAE,GAEN,MAAM,MAAM,CAAC,CA4CzB;AAtZD,qCAAwC;AAExC,mDAAoD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsBpD,mDAKE"}
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { isEqual } from 'lodash-es';
|
|
2
|
+
import { Matrix4 } from 'math.gl';
|
|
3
|
+
import { divide, compare, unit } from 'mathjs';
|
|
4
|
+
import { VIEWER_PALETTE } from '@vitessce/utils';
|
|
5
|
+
import { viv } from '@vitessce/gl';
|
|
6
|
+
import { getMultiSelectionStats } from './layer-controller.js';
|
|
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: viv.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
|
+
// sizes are special objects with own equals method - see `unit` in declaration
|
|
106
|
+
if (!sizes[0].equals(sizes[1])) {
|
|
107
|
+
// Handle scaling in the Y direction for non-square pixels
|
|
108
|
+
scale[1] = divide(sizes[1], sizes[0]);
|
|
109
|
+
}
|
|
110
|
+
// Add in z dimension needed for Matrix4 scale API.
|
|
111
|
+
if (!scale[2]) {
|
|
112
|
+
scale[2] = 1;
|
|
113
|
+
}
|
|
114
|
+
// no need to store/use identity scaling
|
|
115
|
+
if (isEqual(scale, [1, 1, 1])) {
|
|
116
|
+
return meta;
|
|
117
|
+
}
|
|
118
|
+
// Make sure to scale the z direction by one.
|
|
119
|
+
const matrix = new Matrix4().scale([...scale]);
|
|
120
|
+
const newMeta = { ...meta };
|
|
121
|
+
newMeta.metadata = {
|
|
122
|
+
...newMeta.metadata,
|
|
123
|
+
// We don't want to store matrix objects in the view config.
|
|
124
|
+
transform: { matrix: matrix.toArray() },
|
|
125
|
+
};
|
|
126
|
+
return newMeta;
|
|
127
|
+
});
|
|
128
|
+
return imageMetaWithTransform;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Return the midpoint of the global dimensions.
|
|
132
|
+
* @param {object} source PixelSource object from Viv
|
|
133
|
+
* @returns {object} The selection.
|
|
134
|
+
*/
|
|
135
|
+
function getDefaultGlobalSelection(source) {
|
|
136
|
+
const globalIndices = source.labels
|
|
137
|
+
.filter(dim => GLOBAL_LABELS.includes(dim));
|
|
138
|
+
const selection = {};
|
|
139
|
+
globalIndices.forEach((dim) => {
|
|
140
|
+
selection[dim] = Math.floor((source.shape[source.labels.indexOf(dim)] || 0) / 2);
|
|
141
|
+
});
|
|
142
|
+
return selection;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Create a default selection using the midpoint of the available global dimensions,
|
|
146
|
+
* and then the first four available selections from the first selectable channel.
|
|
147
|
+
* @param {object} source PixelSource object from Viv
|
|
148
|
+
* @returns {object} The selection.
|
|
149
|
+
*/
|
|
150
|
+
function buildDefaultSelection(source) {
|
|
151
|
+
const selection = [];
|
|
152
|
+
const globalSelection = getDefaultGlobalSelection(source);
|
|
153
|
+
// First non-global dimension with some sort of selectable values
|
|
154
|
+
const firstNonGlobalDimension = source.labels.filter(dim => !GLOBAL_LABELS.includes(dim)
|
|
155
|
+
&& source.shape[source.labels.indexOf(dim)])[0];
|
|
156
|
+
for (let i = 0; i < Math.min(4, source.shape[source.labels.indexOf(firstNonGlobalDimension)]); i += 1) {
|
|
157
|
+
selection.push({
|
|
158
|
+
[firstNonGlobalDimension]: i,
|
|
159
|
+
...globalSelection,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return selection;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* @param {Array.<number>} shape loader shape
|
|
166
|
+
*/
|
|
167
|
+
export function isInterleaved(shape) {
|
|
168
|
+
const lastDimSize = shape[shape.length - 1];
|
|
169
|
+
return lastDimSize === 3 || lastDimSize === 4;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Initialize the channel selections for an individual layer.
|
|
173
|
+
* @param {object} loader A viv loader instance with channel names appended by Vitessce loaders
|
|
174
|
+
* of the form { data: (PixelSource[]|PixelSource), metadata: Object, channels }
|
|
175
|
+
* @returns {object[]} An array of selected channels with default
|
|
176
|
+
* domain/slider settings.
|
|
177
|
+
*/
|
|
178
|
+
export async function initializeLayerChannels(loader, use3d) {
|
|
179
|
+
const result = [];
|
|
180
|
+
const source = getSourceFromLoader(loader);
|
|
181
|
+
// Add channel automatically as the first avaialable value for each dimension.
|
|
182
|
+
let defaultSelection = buildDefaultSelection(source);
|
|
183
|
+
defaultSelection = isInterleaved(source.shape)
|
|
184
|
+
? [{ ...defaultSelection[0], c: 0 }] : defaultSelection;
|
|
185
|
+
const stats = await getMultiSelectionStats({
|
|
186
|
+
loader: loader.data, selections: defaultSelection, use3d,
|
|
187
|
+
});
|
|
188
|
+
const domains = isRgb(loader, null)
|
|
189
|
+
? [[0, 255], [0, 255], [0, 255]]
|
|
190
|
+
: stats.domains;
|
|
191
|
+
const colors = isRgb(loader, null)
|
|
192
|
+
? [[255, 0, 0], [0, 255, 0], [0, 0, 255]]
|
|
193
|
+
: null;
|
|
194
|
+
const sliders = isRgb(loader, null)
|
|
195
|
+
? [[0, 255], [0, 255], [0, 255]]
|
|
196
|
+
: stats.sliders;
|
|
197
|
+
defaultSelection.forEach((selection, i) => {
|
|
198
|
+
const domain = domains[i];
|
|
199
|
+
const slider = sliders[i];
|
|
200
|
+
const channel = {
|
|
201
|
+
selection,
|
|
202
|
+
// eslint-disable-next-line no-nested-ternary
|
|
203
|
+
color: colors ? colors[i]
|
|
204
|
+
: defaultSelection.length !== 1
|
|
205
|
+
? VIEWER_PALETTE[i] : [255, 255, 255],
|
|
206
|
+
visible: true,
|
|
207
|
+
slider: slider || domain,
|
|
208
|
+
};
|
|
209
|
+
result.push(channel);
|
|
210
|
+
});
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Given a set of image layer loader creator functions,
|
|
215
|
+
* create loader objects for an initial layer or set of layers,
|
|
216
|
+
* which will be selected based on default values predefined in
|
|
217
|
+
* the image data file or otherwise by a heuristic
|
|
218
|
+
* (the midpoint of the layers array).
|
|
219
|
+
* @param {object[]} rasterLayers A list of layer metadata objects with
|
|
220
|
+
* shape { name, type, url, createLoader }.
|
|
221
|
+
* @param {(string[]|null)} rasterRenderLayers A list of default raster layers. Optional.
|
|
222
|
+
*/
|
|
223
|
+
export async function initializeRasterLayersAndChannels(rasterLayers, rasterRenderLayers, usePhysicalSizeScaling) {
|
|
224
|
+
const nextImageLoaders = [];
|
|
225
|
+
let nextImageMetaAndLayers = [];
|
|
226
|
+
const autoImageLayerDefPromises = [];
|
|
227
|
+
// Start all loader creators immediately.
|
|
228
|
+
// Reference: https://eslint.org/docs/rules/no-await-in-loop
|
|
229
|
+
const loaders = await Promise.all(rasterLayers.map(layer => layer.loaderCreator()));
|
|
230
|
+
for (let i = 0; i < rasterLayers.length; i++) {
|
|
231
|
+
const layer = rasterLayers[i];
|
|
232
|
+
const loader = loaders[i];
|
|
233
|
+
nextImageLoaders[i] = loader;
|
|
234
|
+
nextImageMetaAndLayers[i] = layer;
|
|
235
|
+
}
|
|
236
|
+
if (usePhysicalSizeScaling) {
|
|
237
|
+
nextImageMetaAndLayers = getMetaWithTransformMatrices(nextImageMetaAndLayers, nextImageLoaders);
|
|
238
|
+
}
|
|
239
|
+
// No layers were pre-defined so set up the default image layers.
|
|
240
|
+
if (!rasterRenderLayers) {
|
|
241
|
+
// Midpoint of images list as default image to show.
|
|
242
|
+
const layerIndex = Math.floor(rasterLayers.length / 2);
|
|
243
|
+
const loader = nextImageLoaders[layerIndex];
|
|
244
|
+
const autoImageLayerDefPromise = initializeLayerChannels(loader)
|
|
245
|
+
.then(channels => Promise.resolve({
|
|
246
|
+
type: nextImageMetaAndLayers[layerIndex]?.metadata?.isBitmask ? 'bitmask' : 'raster',
|
|
247
|
+
index: layerIndex,
|
|
248
|
+
...DEFAULT_RASTER_LAYER_PROPS,
|
|
249
|
+
channels: channels.map((channel, j) => ({
|
|
250
|
+
...channel,
|
|
251
|
+
...(nextImageMetaAndLayers[layerIndex].channels
|
|
252
|
+
? nextImageMetaAndLayers[layerIndex].channels[j] : []),
|
|
253
|
+
})),
|
|
254
|
+
modelMatrix: nextImageMetaAndLayers[layerIndex]?.metadata?.transform?.matrix,
|
|
255
|
+
transparentColor: layerIndex > 0 ? [0, 0, 0] : null,
|
|
256
|
+
}));
|
|
257
|
+
autoImageLayerDefPromises.push(autoImageLayerDefPromise);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
// The renderLayers parameter is a list of layer names to show by default.
|
|
261
|
+
const globalIndicesOfRenderLayers = rasterRenderLayers
|
|
262
|
+
.map(imageName => rasterLayers.findIndex(image => image.name === imageName));
|
|
263
|
+
for (let i = 0; i < globalIndicesOfRenderLayers.length; i++) {
|
|
264
|
+
const layerIndex = globalIndicesOfRenderLayers[i];
|
|
265
|
+
const loader = nextImageLoaders[layerIndex];
|
|
266
|
+
const autoImageLayerDefPromise = initializeLayerChannels(loader)
|
|
267
|
+
// eslint-disable-next-line no-loop-func
|
|
268
|
+
.then(channels => Promise.resolve({
|
|
269
|
+
type: nextImageMetaAndLayers[layerIndex]?.metadata?.isBitmask ? 'bitmask' : 'raster',
|
|
270
|
+
index: layerIndex,
|
|
271
|
+
...DEFAULT_RASTER_LAYER_PROPS,
|
|
272
|
+
channels: channels.map((channel, j) => ({
|
|
273
|
+
...channel,
|
|
274
|
+
...(nextImageMetaAndLayers[layerIndex].channels
|
|
275
|
+
? nextImageMetaAndLayers[layerIndex].channels[j] : []),
|
|
276
|
+
})),
|
|
277
|
+
domainType: 'Min/Max',
|
|
278
|
+
modelMatrix: nextImageMetaAndLayers[layerIndex]?.metadata?.transform?.matrix,
|
|
279
|
+
transparentColor: i > 0 ? [0, 0, 0] : null,
|
|
280
|
+
}));
|
|
281
|
+
autoImageLayerDefPromises.push(autoImageLayerDefPromise);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const autoImageLayerDefs = await Promise.all(autoImageLayerDefPromises);
|
|
285
|
+
return [autoImageLayerDefs, nextImageLoaders, nextImageMetaAndLayers];
|
|
286
|
+
}
|
|
287
|
+
function getDefaultAxisType(name) {
|
|
288
|
+
if (name === 't')
|
|
289
|
+
return 'time';
|
|
290
|
+
if (name === 'c')
|
|
291
|
+
return 'channel';
|
|
292
|
+
return 'space';
|
|
293
|
+
}
|
|
294
|
+
// Reference: https://github.com/hms-dbmi/vizarr/blob/ade9d5d71bbedc8c20357c5310557ff9a0c59ac5/src/utils.ts#LL114-L142C2
|
|
295
|
+
export function getNgffAxes(firstMultiscalesAxes) {
|
|
296
|
+
// Returns axes in the latest v0.4+ format.
|
|
297
|
+
// defaults for v0.1 & v0.2
|
|
298
|
+
const defaultAxes = [
|
|
299
|
+
{ type: 'time', name: 't' },
|
|
300
|
+
{ type: 'channel', name: 'c' },
|
|
301
|
+
{ type: 'space', name: 'z' },
|
|
302
|
+
{ type: 'space', name: 'y' },
|
|
303
|
+
{ type: 'space', name: 'x' },
|
|
304
|
+
];
|
|
305
|
+
let axes = defaultAxes;
|
|
306
|
+
// v0.3 & v0.4+
|
|
307
|
+
if (firstMultiscalesAxes) {
|
|
308
|
+
axes = firstMultiscalesAxes.map((axis) => {
|
|
309
|
+
// axis may be string 'x' (v0.3) or object
|
|
310
|
+
if (typeof axis === 'string') {
|
|
311
|
+
return { name: axis, type: getDefaultAxisType(axis) };
|
|
312
|
+
}
|
|
313
|
+
const { name, type } = axis;
|
|
314
|
+
return { name, type: type ?? getDefaultAxisType(name) };
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
return axes;
|
|
318
|
+
}
|
|
319
|
+
export function getNgffAxesForTiff(dimOrder) {
|
|
320
|
+
return dimOrder
|
|
321
|
+
.toLowerCase()
|
|
322
|
+
.split('')
|
|
323
|
+
.map(name => ({
|
|
324
|
+
name,
|
|
325
|
+
type: getDefaultAxisType(name),
|
|
326
|
+
}));
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Convert an array of coordinateTransformations objects to a 16-element
|
|
330
|
+
* plain JS array using Matrix4 linear algebra transformation functions.
|
|
331
|
+
* @param {object[]} coordinateTransformations List of objects matching the
|
|
332
|
+
* OME-NGFF v0.4 coordinateTransformations spec.
|
|
333
|
+
* @param {object[]} axes Axes in OME-NGFF v0.4 format, objects
|
|
334
|
+
* with { type, name }.
|
|
335
|
+
* @returns {Array<number>} Array of 16 numbers representing the Matrix4.
|
|
336
|
+
*/
|
|
337
|
+
export function coordinateTransformationsToMatrix(coordinateTransformations, axes) {
|
|
338
|
+
let mat = (new Matrix4()).identity();
|
|
339
|
+
if (coordinateTransformations && axes) {
|
|
340
|
+
// Get the indices of the objects corresponding to X, Y, and Z from `axes`.
|
|
341
|
+
const xyzIndices = ['x', 'y', 'z'].map(name => axes.findIndex(axisObj => axisObj.type === 'space' && axisObj.name === name));
|
|
342
|
+
// Apply each transformation sequentially and in order according to the OME-NGFF v0.4 spec.
|
|
343
|
+
// Reference: https://ngff.openmicroscopy.org/0.4/#trafo-md
|
|
344
|
+
coordinateTransformations.forEach((transform) => {
|
|
345
|
+
if (transform.type === 'translation') {
|
|
346
|
+
const { translation: axisOrderedTranslation } = transform;
|
|
347
|
+
if (axisOrderedTranslation.length !== axes.length) {
|
|
348
|
+
throw new Error('Length of translation array was expected to match length of axes.');
|
|
349
|
+
}
|
|
350
|
+
const defaultValue = 0;
|
|
351
|
+
// Get the translation values for [x, y, z].
|
|
352
|
+
const xyzTranslation = xyzIndices.map(axisIndex => (axisIndex >= 0
|
|
353
|
+
? axisOrderedTranslation[axisIndex]
|
|
354
|
+
: defaultValue));
|
|
355
|
+
const nextMat = (new Matrix4()).translate(xyzTranslation);
|
|
356
|
+
mat = mat.multiplyLeft(nextMat);
|
|
357
|
+
}
|
|
358
|
+
if (transform.type === 'scale') {
|
|
359
|
+
const { scale: axisOrderedScale } = transform;
|
|
360
|
+
// Add in z dimension needed for Matrix4 scale API.
|
|
361
|
+
if (axisOrderedScale.length !== axes.length) {
|
|
362
|
+
throw new Error('Length of scale array was expected to match length of axes.');
|
|
363
|
+
}
|
|
364
|
+
const defaultValue = 1;
|
|
365
|
+
// Get the scale values for [x, y, z].
|
|
366
|
+
const xyzScale = xyzIndices.map(axisIndex => (axisIndex >= 0
|
|
367
|
+
? axisOrderedScale[axisIndex]
|
|
368
|
+
: defaultValue));
|
|
369
|
+
const nextMat = (new Matrix4()).scale(xyzScale);
|
|
370
|
+
mat = mat.multiplyLeft(nextMat);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
return mat;
|
|
375
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spatial.test.d.ts","sourceRoot":"","sources":["../src/spatial.test.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { square, coordinateTransformationsToMatrix } from './spatial.js';
|
|
2
|
+
const defaultAxes = [
|
|
3
|
+
{ type: 'time', name: 't' },
|
|
4
|
+
{ type: 'channel', name: 'c' },
|
|
5
|
+
{ type: 'space', name: 'z' },
|
|
6
|
+
{ type: 'space', name: 'y' },
|
|
7
|
+
{ type: 'space', name: 'x' },
|
|
8
|
+
];
|
|
9
|
+
describe('Spatial.js', () => {
|
|
10
|
+
describe('square()', () => {
|
|
11
|
+
it('gives the right coordinates', () => {
|
|
12
|
+
expect(square(0, 0, 50)).toEqual([[0, 50], [50, 0], [0, -50], [-50, 0]]);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
describe('coordinateTransformationsToMatrix', () => {
|
|
16
|
+
it('returns an Array instance', () => {
|
|
17
|
+
const transformations = [
|
|
18
|
+
{
|
|
19
|
+
type: 'translation',
|
|
20
|
+
translation: [0, 0, 0, 1, 1],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
type: 'scale',
|
|
24
|
+
scale: [1, 1, 0.5, 0.5, 0.5],
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
expect(coordinateTransformationsToMatrix(transformations, defaultAxes)).toEqual([
|
|
28
|
+
0.5, 0, 0, 0,
|
|
29
|
+
0, 0.5, 0, 0,
|
|
30
|
+
0, 0, 0.5, 0,
|
|
31
|
+
0.5, 0.5, 0, 1,
|
|
32
|
+
]);
|
|
33
|
+
});
|
|
34
|
+
it('returns Identity matrix when coordinateTransformations is null', () => {
|
|
35
|
+
expect(coordinateTransformationsToMatrix(null, defaultAxes)).toEqual([
|
|
36
|
+
1, 0, 0, 0,
|
|
37
|
+
0, 1, 0, 0,
|
|
38
|
+
0, 0, 1, 0,
|
|
39
|
+
0, 0, 0, 1,
|
|
40
|
+
]);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitessce/spatial-utils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"author": "Gehlenborg Lab",
|
|
5
5
|
"homepage": "http://vitessce.io",
|
|
6
6
|
"repository": {
|
|
@@ -8,20 +8,29 @@
|
|
|
8
8
|
"url": "git+https://github.com/vitessce/vitessce.git"
|
|
9
9
|
},
|
|
10
10
|
"license": "MIT",
|
|
11
|
-
"
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "dist/index.js",
|
|
12
13
|
"files": [
|
|
14
|
+
"src",
|
|
13
15
|
"dist",
|
|
14
|
-
"
|
|
16
|
+
"dist-tsc"
|
|
15
17
|
],
|
|
16
18
|
"dependencies": {
|
|
17
|
-
"lodash": "^4.17.21",
|
|
19
|
+
"lodash-es": "^4.17.21",
|
|
18
20
|
"math.gl": "^3.5.6",
|
|
19
21
|
"mathjs": "^9.2.0",
|
|
20
|
-
"@vitessce/gl": "
|
|
21
|
-
"@vitessce/utils": "
|
|
22
|
+
"@vitessce/gl": "3.0.0",
|
|
23
|
+
"@vitessce/utils": "3.0.0"
|
|
22
24
|
},
|
|
23
25
|
"scripts": {
|
|
24
26
|
"bundle": "pnpm exec vite build -c ../../../scripts/vite.config.js",
|
|
25
27
|
"test": "pnpm exec vitest --run -r ../../../ --dir ."
|
|
28
|
+
},
|
|
29
|
+
"module": "dist/index.js",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist-tsc/index.d.ts",
|
|
33
|
+
"import": "./dist/index.js"
|
|
34
|
+
}
|
|
26
35
|
}
|
|
27
36
|
}
|