@vitessce/spatial-utils 3.1.2 → 3.2.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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { D, e, a, b, c, d, G, n, l, m, h, i, f, j, k, s } from "./index-551fa504.js";
1
+ import { D, e, a, b, c, d, C, G, z, r, n, A, y, w, l, m, x, v, h, q, o, i, f, j, k, t, p, s, u } from "./index-ca4e31e3.js";
2
2
  import "react";
3
3
  export {
4
4
  D as DEFAULT_CELLS_LAYER,
@@ -7,14 +7,27 @@ export {
7
7
  b as DEFAULT_NEIGHBORHOODS_LAYER,
8
8
  c as DEFAULT_RASTER_DOMAIN_TYPE,
9
9
  d as DEFAULT_RASTER_LAYER_PROPS,
10
+ C as DOMAINS,
10
11
  G as GLOBAL_LABELS,
12
+ z as abbreviateNumber,
13
+ r as canLoadResolution,
11
14
  n as coordinateTransformationsToMatrix,
15
+ A as filterSelection,
16
+ y as getBoundingCube,
17
+ w as getMultiSelectionStats,
12
18
  l as getNgffAxes,
13
19
  m as getNgffAxesForTiff,
20
+ x as getPhysicalSizeScalingMatrix,
21
+ v as getSingleSelectionStats,
14
22
  h as getSourceFromLoader,
23
+ q as getStatsForResolution,
24
+ o as hexToRgb,
15
25
  i as initializeLayerChannels,
16
26
  f as initializeRasterLayersAndChannels,
17
27
  j as isInterleaved,
18
28
  k as isRgb,
19
- s as square
29
+ t as normalizeCoordinateTransformations,
30
+ p as physicalSizeToMatrix,
31
+ s as square,
32
+ u as toRgbUIString
20
33
  };
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-551fa504.js";
1
+ import { B as BaseDecoder } from "./index-ca4e31e3.js";
2
2
  import "react";
3
3
  const dctZigZag = new Int32Array([
4
4
  0,
@@ -1,5 +1,5 @@
1
1
  import { i as inflate_1 } from "./pako.esm-68f84e2a.js";
2
- import { g as getDefaultExportFromCjs, B as BaseDecoder } from "./index-551fa504.js";
2
+ import { g as getDefaultExportFromCjs, B as BaseDecoder } from "./index-ca4e31e3.js";
3
3
  import "react";
4
4
  const LercParameters = {
5
5
  Version: 0,
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-551fa504.js";
1
+ import { B as BaseDecoder } from "./index-ca4e31e3.js";
2
2
  import "react";
3
3
  const MIN_BITS = 9;
4
4
  const CLEAR_CODE = 256;
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-551fa504.js";
1
+ import { B as BaseDecoder } from "./index-ca4e31e3.js";
2
2
  import "react";
3
3
  class PackbitsDecoder extends BaseDecoder {
4
4
  decodeBlock(buffer) {
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-551fa504.js";
1
+ import { B as BaseDecoder } from "./index-ca4e31e3.js";
2
2
  import "react";
3
3
  class RawDecoder extends BaseDecoder {
4
4
  decodeBlock(buffer) {
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-551fa504.js";
1
+ import { B as BaseDecoder } from "./index-ca4e31e3.js";
2
2
  import "react";
3
3
  class WebImageDecoder extends BaseDecoder {
4
4
  constructor() {
@@ -0,0 +1,2 @@
1
+ export const DOMAINS: {};
2
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.js"],"names":[],"mappings":"AAaA,yBAAoC"}
@@ -0,0 +1,12 @@
1
+ import { viv } from '@vitessce/gl';
2
+ function getDomains() {
3
+ const domains = {};
4
+ const needMin = ['Int8', 'Int16', 'Int32'];
5
+ Object.keys(viv.DTYPE_VALUES).forEach((dtype) => {
6
+ const { max } = viv.DTYPE_VALUES[dtype];
7
+ const min = needMin.includes(dtype) ? -(max + 1) : 0;
8
+ domains[dtype] = [min, max];
9
+ });
10
+ return domains;
11
+ }
12
+ export const DOMAINS = getDomains();
@@ -1,2 +1,4 @@
1
- export { GLOBAL_LABELS, DEFAULT_CELLS_LAYER, DEFAULT_MOLECULES_LAYER, DEFAULT_NEIGHBORHOODS_LAYER, DEFAULT_RASTER_DOMAIN_TYPE, DEFAULT_RASTER_LAYER_PROPS, DEFAULT_LAYER_TYPE_ORDERING, square, initializeLayerChannels, initializeRasterLayersAndChannels, getSourceFromLoader, isInterleaved, isRgb, getNgffAxes, getNgffAxesForTiff, coordinateTransformationsToMatrix } from "./spatial.js";
1
+ export { DOMAINS } from "./constants.js";
2
+ export { GLOBAL_LABELS, DEFAULT_CELLS_LAYER, DEFAULT_MOLECULES_LAYER, DEFAULT_NEIGHBORHOODS_LAYER, DEFAULT_RASTER_DOMAIN_TYPE, DEFAULT_RASTER_LAYER_PROPS, DEFAULT_LAYER_TYPE_ORDERING, square, initializeLayerChannels, initializeRasterLayersAndChannels, getSourceFromLoader, isInterleaved, isRgb, getNgffAxes, getNgffAxesForTiff, coordinateTransformationsToMatrix, physicalSizeToMatrix, hexToRgb, getStatsForResolution, canLoadResolution, normalizeCoordinateTransformations } from "./spatial.js";
3
+ export { toRgbUIString, getSingleSelectionStats, getMultiSelectionStats, getPhysicalSizeScalingMatrix, getBoundingCube, abbreviateNumber, filterSelection } from "./layer-controller.js";
2
4
  //# sourceMappingURL=index.d.ts.map
package/dist-tsc/index.js CHANGED
@@ -1 +1,3 @@
1
- export { GLOBAL_LABELS, DEFAULT_CELLS_LAYER, DEFAULT_MOLECULES_LAYER, DEFAULT_NEIGHBORHOODS_LAYER, DEFAULT_RASTER_DOMAIN_TYPE, DEFAULT_RASTER_LAYER_PROPS, DEFAULT_LAYER_TYPE_ORDERING, square, initializeLayerChannels, initializeRasterLayersAndChannels, getSourceFromLoader, isInterleaved, isRgb, getNgffAxes, getNgffAxesForTiff, coordinateTransformationsToMatrix, } from './spatial.js';
1
+ export { GLOBAL_LABELS, DEFAULT_CELLS_LAYER, DEFAULT_MOLECULES_LAYER, DEFAULT_NEIGHBORHOODS_LAYER, DEFAULT_RASTER_DOMAIN_TYPE, DEFAULT_RASTER_LAYER_PROPS, DEFAULT_LAYER_TYPE_ORDERING, square, initializeLayerChannels, initializeRasterLayersAndChannels, getSourceFromLoader, isInterleaved, isRgb, getNgffAxes, getNgffAxesForTiff, coordinateTransformationsToMatrix, physicalSizeToMatrix, hexToRgb, getStatsForResolution, canLoadResolution, normalizeCoordinateTransformations, } from './spatial.js';
2
+ export { toRgbUIString, getSingleSelectionStats, getMultiSelectionStats, getPhysicalSizeScalingMatrix, getBoundingCube, abbreviateNumber, filterSelection, } from './layer-controller.js';
3
+ export { DOMAINS, } from './constants.js';
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Ensure that the channel selection object does not have
3
+ * extra dimension keys, as this will cause Viv functions
4
+ * to throw errors about not being able to access the data.
5
+ * @param {*} loader
6
+ * @param {object} selection Mapping from dimension label to slice index.
7
+ * @returns {object} The filtered selection.
8
+ */
9
+ export function filterSelection(loader: any, selection: object): object;
1
10
  /**
2
11
  * Get physical size scaling Matrix4
3
12
  * @param {Object} loader PixelSource
@@ -11,6 +20,7 @@ export function getPhysicalSizeScalingMatrix(loader: Object): Object;
11
20
  */
12
21
  export function getBoundingCube(loader: Object): any[];
13
22
  export function abbreviateNumber(value: any): any;
23
+ export function toRgbUIString(on: any, arr: any, theme: any): string;
14
24
  export function getSingleSelectionStats({ loader, selection, use3d }: Object): Object;
15
25
  export function getMultiSelectionStats({ loader, selections, use3d }: {
16
26
  loader: any;
@@ -1 +1 @@
1
- {"version":3,"file":"layer-controller.d.ts","sourceRoot":"","sources":["../src/layer-controller.js"],"names":[],"mappings":"AAuEA;;;;GAIG;AACH,qDAHW,MAAM,GACJ,MAAM,CAUlB;AAED;;;;GAIG;AACH,wCAHW,MAAM,SAchB;AAED,kDAwBC;AAzEM,sEALI,MAAM,GAGJ,MAAM,CAOlB;AAEM;;;;;;;GAON"}
1
+ {"version":3,"file":"layer-controller.d.ts","sourceRoot":"","sources":["../src/layer-controller.js"],"names":[],"mappings":"AAaA;;;;;;;GAOG;AACH,wDAHW,MAAM,GACJ,MAAM,CASlB;AAwED;;;;GAIG;AACH,qDAHW,MAAM,GACJ,MAAM,CAUlB;AAED;;;;GAIG;AACH,wCAHW,MAAM,SAchB;AAED,kDAwBC;AAvJM,qEAKN;AAyEM,sEALI,MAAM,GAGJ,MAAM,CAOlB;AAEM;;;;;;;GAON"}
@@ -1,25 +1,52 @@
1
- import { Matrix4 } from 'math.gl';
2
1
  import { viv } from '@vitessce/gl';
2
+ import { fromEntries } from '@vitessce/utils';
3
+ import { Matrix4 } from 'math.gl';
4
+ // Returns an rgb string for display, and changes the color (arr)
5
+ // to use a grey for light theme + white color or if the colormap is on.
6
+ export const toRgbUIString = (on, arr, theme) => {
7
+ const color = on || (theme === 'light' && arr?.every(i => i === 255))
8
+ ? [220, 220, 220]
9
+ : arr;
10
+ return `rgb(${color})`;
11
+ };
12
+ /**
13
+ * Ensure that the channel selection object does not have
14
+ * extra dimension keys, as this will cause Viv functions
15
+ * to throw errors about not being able to access the data.
16
+ * @param {*} loader
17
+ * @param {object} selection Mapping from dimension label to slice index.
18
+ * @returns {object} The filtered selection.
19
+ */
20
+ export function filterSelection(loader, selection) {
21
+ if (!selection) {
22
+ return selection;
23
+ }
24
+ const data = Array.isArray(loader) ? loader[loader.length - 1] : loader;
25
+ const { labels } = data;
26
+ return fromEntries(Object.entries(selection).filter(([key]) => labels.includes(key)));
27
+ }
3
28
  async function getSingleSelectionStats2D({ loader, selection }) {
4
29
  const data = Array.isArray(loader) ? loader[loader.length - 1] : loader;
5
- const raster = await data.getRaster({ selection });
30
+ const filteredSelection = filterSelection(loader, selection);
31
+ const raster = await data.getRaster({ selection: filteredSelection });
6
32
  const selectionStats = viv.getChannelStats(raster.data);
7
33
  const { domain, contrastLimits: slider } = selectionStats;
8
34
  return { domain, slider };
9
35
  }
10
36
  async function getSingleSelectionStats3D({ loader, selection }) {
11
37
  const lowResSource = loader[loader.length - 1];
38
+ const filteredSelection = filterSelection(lowResSource, selection);
12
39
  const { shape, labels } = lowResSource;
13
40
  // eslint-disable-next-line no-bitwise
14
41
  const sizeZ = shape[labels.indexOf('z')] >> (loader.length - 1);
15
42
  const raster0 = await lowResSource.getRaster({
16
- selection: { ...selection, z: 0 },
43
+ selection: { ...filteredSelection, z: 0 },
17
44
  });
18
45
  const rasterMid = await lowResSource.getRaster({
19
- selection: { ...selection, z: Math.floor(sizeZ / 2) },
46
+ selection: { ...filteredSelection, z: Math.floor(sizeZ / 2) },
20
47
  });
21
48
  const rasterTop = await lowResSource.getRaster({
22
- selection: { ...selection, z: Math.max(0, sizeZ - 1) },
49
+ selection: { ...filteredSelection, z: Math.max(0, sizeZ - 1) },
23
50
  });
24
51
  const stats0 = viv.getChannelStats(raster0.data);
25
52
  const statsMid = viv.getChannelStats(rasterMid.data);
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=layer-controller.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layer-controller.test.d.ts","sourceRoot":"","sources":["../src/layer-controller.test.js"],"names":[],"mappings":""}
@@ -0,0 +1,22 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { toRgbUIString } from './layer-controller.js';
3
+ const GREY = [220, 220, 220];
4
+ describe('layer-controller/RasterChannelController.js', () => {
5
+ describe('toRgbUIString()', () => {
6
+ it('Maps color value to itself when colormap off and not white with dark theme', () => {
7
+ expect(toRgbUIString(false, [200, 200, 200], 'dark')).toEqual(`rgb(${[200, 200, 200]})`);
8
+ });
9
+ it('Maps color value to grey when colormap on with dark theme', () => {
10
+ expect(toRgbUIString(true, [200, 200, 200], 'dark')).toEqual(`rgb(${GREY})`);
11
+ });
12
+ it('Maps color value to grey when colormap on with light theme', () => {
13
+ expect(toRgbUIString(true, [200, 200, 200], 'light')).toEqual(`rgb(${GREY})`);
14
+ });
15
+ it('Maps color value to grey when colormap off and white with light theme', () => {
16
+ expect(toRgbUIString(false, [255, 255, 255], 'light')).toEqual(`rgb(${GREY})`);
17
+ });
18
+ it('Maps color value to grey when colormap off and white with light theme', () => {
19
+ expect(toRgbUIString(false, [200, 200, 200], 'light')).toEqual(`rgb(${[200, 200, 200]})`);
20
+ });
21
+ });
22
+ });
@@ -13,6 +13,7 @@ export function getSourceFromLoader(loader: object, level?: number | undefined):
13
13
  * @param {array|null} channels
14
14
  */
15
15
  export function isRgb(loader: object, channels: array | null): boolean;
16
+ export function physicalSizeToMatrix(xSize: any, ySize: any, zSize: any, xUnit: any, yUnit: any, zUnit: any): Matrix4;
16
17
  /**
17
18
  * @param {Array.<number>} shape loader shape
18
19
  */
@@ -44,13 +45,25 @@ export function getNgffAxesForTiff(dimOrder: any): any;
44
45
  /**
45
46
  * Convert an array of coordinateTransformations objects to a 16-element
46
47
  * plain JS array using Matrix4 linear algebra transformation functions.
47
- * @param {object[]} coordinateTransformations List of objects matching the
48
+ * @param {object[]|undefined} coordinateTransformations List of objects matching the
48
49
  * OME-NGFF v0.4 coordinateTransformations spec.
49
- * @param {object[]} axes Axes in OME-NGFF v0.4 format, objects
50
+ * @param {object[]|undefined} axes Axes in OME-NGFF v0.4 format, objects
50
51
  * with { type, name }.
51
- * @returns {Array<number>} Array of 16 numbers representing the Matrix4.
52
+ * @returns {Matrix4} Array of 16 numbers representing the Matrix4.
52
53
  */
53
- export function coordinateTransformationsToMatrix(coordinateTransformations: object[], axes: object[]): Array<number>;
54
+ export function coordinateTransformationsToMatrix(coordinateTransformations: object[] | undefined, axes: object[] | undefined): Matrix4;
55
+ export function hexToRgb(hex: any): number[];
56
+ /**
57
+ * Normalize coordinate transformations to the OME-NGFF v0.4 format,
58
+ * despite potentially being in the new format proposed in
59
+ * https://github.com/ome/ngff/pull/138 (As of 2023-09-02).
60
+ * @param {object[]|undefined} coordinateTransformations Value of
61
+ * multiscales[0].coordinateTransformations in either OME-NGFF v0.4 format
62
+ * or that proposed in https://github.com/ome/ngff/pull/138.
63
+ * @param {object[]} datasets Value of multiscales[0].datasets in OME-NGFF v0.4 format.
64
+ * @returns {object[]} Array of coordinateTransformations in OME-NGFF v0.4 format.
65
+ */
66
+ export function normalizeCoordinateTransformations(coordinateTransformations: object[] | undefined, datasets: object[]): object[];
54
67
  export const GLOBAL_LABELS: string[];
55
68
  export const DEFAULT_RASTER_DOMAIN_TYPE: "Min/Max";
56
69
  export namespace DEFAULT_RASTER_LAYER_PROPS {
@@ -83,5 +96,13 @@ export namespace DEFAULT_NEIGHBORHOODS_LAYER {
83
96
  export { visible_3 as visible };
84
97
  }
85
98
  export const DEFAULT_LAYER_TYPE_ORDERING: string[];
99
+ export function getStatsForResolution(loader: any, resolution: any): {
100
+ height: any;
101
+ width: any;
102
+ depthDownsampled: number;
103
+ totalBytes: number;
104
+ };
105
+ export function canLoadResolution(loader: any, resolution: any): boolean;
106
+ import { Matrix4 } from ".pnpm/@math.gl+core@3.6.3/node_modules/@math.gl/core";
86
107
  import { viv } from "@vitessce/gl";
87
108
  //# sourceMappingURL=spatial.d.ts.map
@@ -1 +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"}
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;AAoCD;;;;GAIG;AACH,8BAHW,MAAM,YACN,QAAM,IAAI,WAiBpB;AAID,sHAkCC;AAiHD;;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,GAAC,SAAS,QAElB,MAAM,EAAE,GAAC,SAAS,GAEhB,OAAO,CA4CnB;AAED,6CAOC;AAED;;;;;;;;;GASG;AACH,8EANW,MAAM,EAAE,GAAC,SAAS,YAGlB,MAAM,EAAE,GACN,MAAM,EAAE,CAiDpB;AAhiBD,qCAAwC;AAExC,mDAAoD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsBpD,mDAKE;AAeK;;;;;EAYN;AAEM,yEAkBN"}
@@ -45,6 +45,31 @@ export function getSourceFromLoader(loader, level) {
45
45
  const source = Array.isArray(data) ? data[(level || data.length - 1)] : data;
46
46
  return source;
47
47
  }
48
+ export const getStatsForResolution = (loader, resolution) => {
49
+ const { shape, labels } = loader[resolution];
50
+ const height = shape[labels.indexOf('y')];
51
+ const width = shape[labels.indexOf('x')];
52
+ const depth = shape[labels.indexOf('z')];
53
+ // eslint-disable-next-line no-bitwise
54
+ const depthDownsampled = Math.max(1, depth >> resolution);
55
+ // Check memory allocation limits for Float32Array (used in XR3DLayer for rendering)
56
+ const totalBytes = 4 * height * width * depthDownsampled;
57
+ return {
58
+ height, width, depthDownsampled, totalBytes,
59
+ };
60
+ };
61
+ export const canLoadResolution = (loader, resolution) => {
62
+ const { totalBytes, height, width, depthDownsampled, } = getStatsForResolution(loader, resolution);
63
+ const maxHeapSize = window.performance?.memory
64
+ && window.performance?.memory?.jsHeapSizeLimit / 2;
65
+ const maxSize = maxHeapSize || (2 ** 31) - 1;
66
+ // 2048 is a normal texture size limit although some browsers go larger.
67
+ return (totalBytes < maxSize
68
+ && height <= 2048
69
+ && depthDownsampled <= 2048
70
+ && width <= 2048
71
+ && depthDownsampled > 1);
72
+ };
48
73
  /**
49
74
  * Helper method to determine whether pixel data is interleaved and rgb or not.
50
75
  * @param {object} loader
@@ -66,6 +91,39 @@ export function isRgb(loader, channels) {
66
91
  return false;
67
92
  }
68
93
  // From spatial/utils.js
94
+ export function physicalSizeToMatrix(xSize, ySize, zSize, xUnit, yUnit, zUnit) {
95
+ const hasZPhyscialSize = Boolean(zSize) && Boolean(zUnit);
96
+ const hasYPhyscialSize = Boolean(ySize) && Boolean(yUnit);
97
+ const hasXPhyscialSize = Boolean(xSize) && Boolean(xUnit);
98
+ if (!hasXPhyscialSize || !hasYPhyscialSize) {
99
+ return (new Matrix4()).identity();
100
+ }
101
+ const sizes = [
102
+ unit(`${xSize} ${xUnit}`.replace('µ', 'u')),
103
+ unit(`${ySize} ${yUnit}`.replace('µ', 'u')),
104
+ ];
105
+ if (hasZPhyscialSize) {
106
+ sizes.push(unit(`${zSize} ${zUnit}`.replace('µ', 'u')));
107
+ }
108
+ // Find the ratio of the sizes to get the scaling factor.
109
+ const scale = sizes.map(i => divide(i, unit('1 um')));
110
+ // TODO: is this still needed
111
+ // sizes are special objects with own equals method - see `unit` in declaration
112
+ if (!sizes[0].equals(sizes[1])) {
113
+ // Handle scaling in the Y direction for non-square pixels
114
+ scale[1] = divide(sizes[1], sizes[0]);
115
+ }
116
+ // END TODO: is this still needed
117
+ // Add in z dimension needed for Matrix4 scale API.
118
+ if (!scale[2]) {
119
+ scale[2] = 1;
120
+ }
121
+ // no need to store/use identity scaling
122
+ if (isEqual(scale, [1, 1, 1])) {
123
+ return (new Matrix4()).identity();
124
+ }
125
+ return new Matrix4().scale([...scale]);
126
+ }
69
127
  function getMetaWithTransformMatrices(imageMeta, imageLoaders) {
70
128
  // Do not fill in transformation matrices if any of the layers specify one.
71
129
  const sources = imageLoaders.map(loader => getSourceFromLoader(loader));
@@ -328,11 +386,11 @@ export function getNgffAxesForTiff(dimOrder) {
328
386
  /**
329
387
  * Convert an array of coordinateTransformations objects to a 16-element
330
388
  * plain JS array using Matrix4 linear algebra transformation functions.
331
- * @param {object[]} coordinateTransformations List of objects matching the
389
+ * @param {object[]|undefined} coordinateTransformations List of objects matching the
332
390
  * OME-NGFF v0.4 coordinateTransformations spec.
333
- * @param {object[]} axes Axes in OME-NGFF v0.4 format, objects
391
+ * @param {object[]|undefined} axes Axes in OME-NGFF v0.4 format, objects
334
392
  * with { type, name }.
335
- * @returns {Array<number>} Array of 16 numbers representing the Matrix4.
393
+ * @returns {Matrix4} Array of 16 numbers representing the Matrix4.
336
394
  */
337
395
  export function coordinateTransformationsToMatrix(coordinateTransformations, axes) {
338
396
  let mat = (new Matrix4()).identity();
@@ -373,3 +431,66 @@ export function coordinateTransformationsToMatrix(coordinateTransformations, axe
373
431
  }
374
432
  return mat;
375
433
  }
434
+ export function hexToRgb(hex) {
435
+ const result = /^#?([A-F\d]{2})([A-F\d]{2})([A-F\d]{2})$/i.exec(hex);
436
+ return [
437
+ parseInt(result[1].toLowerCase(), 16),
438
+ parseInt(result[2].toLowerCase(), 16),
439
+ parseInt(result[3].toLowerCase(), 16),
440
+ ];
441
+ }
442
+ /**
443
+ * Normalize coordinate transformations to the OME-NGFF v0.4 format,
444
+ * despite potentially being in the new format proposed in
445
+ * https://github.com/ome/ngff/pull/138 (As of 2023-09-02).
446
+ * @param {object[]|undefined} coordinateTransformations Value of
447
+ * multiscales[0].coordinateTransformations in either OME-NGFF v0.4 format
448
+ * or that proposed in https://github.com/ome/ngff/pull/138.
449
+ * @param {object[]} datasets Value of multiscales[0].datasets in OME-NGFF v0.4 format.
450
+ * @returns {object[]} Array of coordinateTransformations in OME-NGFF v0.4 format.
451
+ */
452
+ export function normalizeCoordinateTransformations(coordinateTransformations, datasets) {
453
+ // "The transformations in the list are applied sequentially and in order."
454
+ // Reference: https://ngff.openmicroscopy.org/0.4/index.html#trafo-md
455
+ let result = [];
456
+ if (Array.isArray(coordinateTransformations)) {
457
+ result = coordinateTransformations.map((transform) => {
458
+ if (transform.input && transform.output) {
459
+ // This is a new-style coordinate transformation.
460
+ // (As proposed in https://github.com/ome/ngff/pull/138)
461
+ const { type } = transform;
462
+ if (type === 'translation') {
463
+ return {
464
+ type,
465
+ translation: transform.translation,
466
+ };
467
+ }
468
+ if (type === 'scale') {
469
+ return {
470
+ type,
471
+ scale: transform.scale,
472
+ };
473
+ }
474
+ if (type === 'identity') {
475
+ return { type };
476
+ }
477
+ console.warn(`Coordinate transformation type "${type}" is not supported.`);
478
+ }
479
+ // Assume it was already an old-style (NGFF v0.4) coordinate transformation.
480
+ return transform;
481
+ });
482
+ }
483
+ if (Array.isArray(datasets?.[0]?.coordinateTransformations)) {
484
+ // "Datasets SHOULD define a transformation from array space
485
+ // to their "native physical space."
486
+ // This transformation SHOULD describe physical pixel spacing
487
+ // and origin only, and therefore SHOULD consist of
488
+ // `scale` and/or `translation` types only.""
489
+ // Reference: https://github.com/ome/ngff/blob/b92f540dc95440f2d6b7012185b09c2b862aa744/latest/transform-details.bs#L99
490
+ result = [
491
+ ...datasets[0].coordinateTransformations,
492
+ ...result,
493
+ ];
494
+ }
495
+ return result;
496
+ }
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { square, coordinateTransformationsToMatrix } from './spatial.js';
2
+ import { square, coordinateTransformationsToMatrix, normalizeCoordinateTransformations, } from './spatial.js';
3
3
  const defaultAxes = [
4
4
  { type: 'time', name: 't' },
5
5
  { type: 'channel', name: 'c' },
@@ -41,4 +41,159 @@ describe('Spatial.js', () => {
41
41
  ]);
42
42
  });
43
43
  });
44
+ describe('normalizeCoordinateTransformations', () => {
45
+ it('does nothing for OME-NGFF v0.4 input', () => {
46
+ const datasets = [
47
+ {
48
+ coordinateTransformations: [
49
+ {
50
+ scale: [
51
+ 1,
52
+ 0.5002025531914894,
53
+ 0.3603981534640209,
54
+ 0.3603981534640209,
55
+ ],
56
+ type: 'scale',
57
+ },
58
+ ],
59
+ path: '0',
60
+ },
61
+ {
62
+ coordinateTransformations: [
63
+ {
64
+ scale: [
65
+ 1,
66
+ 0.5002025531914894,
67
+ 0.7207963069280418,
68
+ 0.7207963069280418,
69
+ ],
70
+ type: 'scale',
71
+ },
72
+ ],
73
+ path: '1',
74
+ },
75
+ {
76
+ coordinateTransformations: [
77
+ {
78
+ scale: [
79
+ 1,
80
+ 0.5002025531914894,
81
+ 1.4415926138560835,
82
+ 1.4415926138560835,
83
+ ],
84
+ type: 'scale',
85
+ },
86
+ ],
87
+ path: '2',
88
+ },
89
+ ];
90
+ expect(normalizeCoordinateTransformations(undefined, datasets)).toEqual([
91
+ // Here, we expect only the first dataset to be used.
92
+ // However, we should eventually support transforms
93
+ // specified for each dataset individually,
94
+ // since there could in theory be irregular ways of downsampling.
95
+ {
96
+ scale: [
97
+ 1,
98
+ 0.5002025531914894,
99
+ 0.3603981534640209,
100
+ 0.3603981534640209,
101
+ ],
102
+ type: 'scale',
103
+ },
104
+ ]);
105
+ });
106
+ it('transforms SpatialData input', () => {
107
+ const newCoordinateTransformations = [
108
+ {
109
+ input: {
110
+ axes: [
111
+ {
112
+ name: 'c',
113
+ type: 'channel',
114
+ },
115
+ {
116
+ name: 'y',
117
+ type: 'space',
118
+ unit: 'unit',
119
+ },
120
+ {
121
+ name: 'x',
122
+ type: 'space',
123
+ unit: 'unit',
124
+ },
125
+ ],
126
+ name: 'cyx',
127
+ },
128
+ output: {
129
+ axes: [
130
+ {
131
+ name: 'c',
132
+ type: 'channel',
133
+ },
134
+ {
135
+ name: 'y',
136
+ type: 'space',
137
+ unit: 'unit',
138
+ },
139
+ {
140
+ name: 'x',
141
+ type: 'space',
142
+ unit: 'unit',
143
+ },
144
+ ],
145
+ name: 'ST8059048',
146
+ },
147
+ scale: [
148
+ 1.0,
149
+ 8.670500183814605,
150
+ 8.670500183814605,
151
+ ],
152
+ type: 'scale',
153
+ },
154
+ ];
155
+ const datasets = [
156
+ {
157
+ coordinateTransformations: [
158
+ {
159
+ scale: [
160
+ 1.0,
161
+ 1.0,
162
+ 1.0,
163
+ ],
164
+ type: 'scale',
165
+ },
166
+ ],
167
+ path: '0',
168
+ },
169
+ ];
170
+ expect(normalizeCoordinateTransformations(newCoordinateTransformations, datasets)).toEqual([
171
+ // Here, we expect only the first dataset to be used.
172
+ // However, we should eventually support transforms
173
+ // specified for each dataset individually,
174
+ // since there could in theory be irregular ways of downsampling.
175
+ {
176
+ scale: [
177
+ 1.0,
178
+ 1.0,
179
+ 1.0,
180
+ ],
181
+ type: 'scale',
182
+ },
183
+ // We expect the dataset transform to be prepended
184
+ // to the newCoordinateTransformations items.
185
+ // We do not check the input/output coordinate systems, but we should eventually allow
186
+ // the user to specify this somehow and use that information to filter which transforms
187
+ // are included here.
188
+ {
189
+ scale: [
190
+ 1.0,
191
+ 8.670500183814605,
192
+ 8.670500183814605,
193
+ ],
194
+ type: 'scale',
195
+ },
196
+ ]);
197
+ });
198
+ });
44
199
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/spatial-utils",
3
- "version": "3.1.2",
3
+ "version": "3.2.0",
4
4
  "author": "Gehlenborg Lab",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
@@ -19,8 +19,8 @@
19
19
  "lodash-es": "^4.17.21",
20
20
  "math.gl": "^3.5.6",
21
21
  "mathjs": "^9.2.0",
22
- "@vitessce/gl": "3.1.2",
23
- "@vitessce/utils": "3.1.2"
22
+ "@vitessce/gl": "3.2.0",
23
+ "@vitessce/utils": "3.2.0"
24
24
  },
25
25
  "scripts": {
26
26
  "bundle": "pnpm exec vite build -c ../../../scripts/vite.config.js",
@@ -0,0 +1,14 @@
1
+ import { viv } from '@vitessce/gl';
2
+
3
+ function getDomains() {
4
+ const domains = {};
5
+ const needMin = ['Int8', 'Int16', 'Int32'];
6
+ Object.keys(viv.DTYPE_VALUES).forEach((dtype) => {
7
+ const { max } = viv.DTYPE_VALUES[dtype];
8
+ const min = needMin.includes(dtype) ? -(max + 1) : 0;
9
+ domains[dtype] = [min, max];
10
+ });
11
+ return domains;
12
+ }
13
+
14
+ export const DOMAINS = getDomains();