@vitessce/spatial-utils 3.1.3 → 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, o, s } from "./index-e86d2626.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,15 +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
- o as normalizeCoordinateTransformations,
20
- s as square
29
+ t as normalizeCoordinateTransformations,
30
+ p as physicalSizeToMatrix,
31
+ s as square,
32
+ u as toRgbUIString
21
33
  };
@@ -1,4 +1,4 @@
1
- import { B as BaseDecoder } from "./index-e86d2626.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-e86d2626.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-e86d2626.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-e86d2626.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-e86d2626.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-e86d2626.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, normalizeCoordinateTransformations } 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, normalizeCoordinateTransformations, } 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,14 @@ 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[];
54
56
  /**
55
57
  * Normalize coordinate transformations to the OME-NGFF v0.4 format,
56
58
  * despite potentially being in the new format proposed in
@@ -94,5 +96,13 @@ export namespace DEFAULT_NEIGHBORHOODS_LAYER {
94
96
  export { visible_3 as visible };
95
97
  }
96
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";
97
107
  import { viv } from "@vitessce/gl";
98
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;AAED;;;;;;;;;GASG;AACH,8EANW,MAAM,EAAE,GAAC,SAAS,YAGlB,MAAM,EAAE,GACN,MAAM,EAAE,CAiDpB;AAjdD,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,6 +431,14 @@ 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
+ }
376
442
  /**
377
443
  * Normalize coordinate transformations to the OME-NGFF v0.4 format,
378
444
  * despite potentially being in the new format proposed in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/spatial-utils",
3
- "version": "3.1.3",
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.3",
23
- "@vitessce/utils": "3.1.3"
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();
package/src/index.js CHANGED
@@ -15,5 +15,21 @@ export {
15
15
  getNgffAxes,
16
16
  getNgffAxesForTiff,
17
17
  coordinateTransformationsToMatrix,
18
+ physicalSizeToMatrix,
19
+ hexToRgb,
20
+ getStatsForResolution,
21
+ canLoadResolution,
18
22
  normalizeCoordinateTransformations,
19
23
  } from './spatial.js';
24
+ export {
25
+ toRgbUIString,
26
+ getSingleSelectionStats,
27
+ getMultiSelectionStats,
28
+ getPhysicalSizeScalingMatrix,
29
+ getBoundingCube,
30
+ abbreviateNumber,
31
+ filterSelection,
32
+ } from './layer-controller.js';
33
+ export {
34
+ DOMAINS,
35
+ } from './constants.js';
@@ -1,9 +1,37 @@
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
+
5
+ // Returns an rgb string for display, and changes the color (arr)
6
+ // to use a grey for light theme + white color or if the colormap is on.
7
+ export const toRgbUIString = (on, arr, theme) => {
8
+ const color = on || (theme === 'light' && arr?.every(i => i === 255))
9
+ ? [220, 220, 220]
10
+ : arr;
11
+ return `rgb(${color})`;
12
+ };
13
+
14
+ /**
15
+ * Ensure that the channel selection object does not have
16
+ * extra dimension keys, as this will cause Viv functions
17
+ * to throw errors about not being able to access the data.
18
+ * @param {*} loader
19
+ * @param {object} selection Mapping from dimension label to slice index.
20
+ * @returns {object} The filtered selection.
21
+ */
22
+ export function filterSelection(loader, selection) {
23
+ if (!selection) {
24
+ return selection;
25
+ }
26
+ const data = Array.isArray(loader) ? loader[loader.length - 1] : loader;
27
+ const { labels } = data;
28
+ return fromEntries(Object.entries(selection).filter(([key]) => labels.includes(key)));
29
+ }
3
30
 
4
31
  async function getSingleSelectionStats2D({ loader, selection }) {
5
32
  const data = Array.isArray(loader) ? loader[loader.length - 1] : loader;
6
- const raster = await data.getRaster({ selection });
33
+ const filteredSelection = filterSelection(loader, selection);
34
+ const raster = await data.getRaster({ selection: filteredSelection });
7
35
  const selectionStats = viv.getChannelStats(raster.data);
8
36
  const { domain, contrastLimits: slider } = selectionStats;
9
37
  return { domain, slider };
@@ -11,17 +39,18 @@ async function getSingleSelectionStats2D({ loader, selection }) {
11
39
 
12
40
  async function getSingleSelectionStats3D({ loader, selection }) {
13
41
  const lowResSource = loader[loader.length - 1];
42
+ const filteredSelection = filterSelection(lowResSource, selection);
14
43
  const { shape, labels } = lowResSource;
15
44
  // eslint-disable-next-line no-bitwise
16
45
  const sizeZ = shape[labels.indexOf('z')] >> (loader.length - 1);
17
46
  const raster0 = await lowResSource.getRaster({
18
- selection: { ...selection, z: 0 },
47
+ selection: { ...filteredSelection, z: 0 },
19
48
  });
20
49
  const rasterMid = await lowResSource.getRaster({
21
- selection: { ...selection, z: Math.floor(sizeZ / 2) },
50
+ selection: { ...filteredSelection, z: Math.floor(sizeZ / 2) },
22
51
  });
23
52
  const rasterTop = await lowResSource.getRaster({
24
- selection: { ...selection, z: Math.max(0, sizeZ - 1) },
53
+ selection: { ...filteredSelection, z: Math.max(0, sizeZ - 1) },
25
54
  });
26
55
  const stats0 = viv.getChannelStats(raster0.data);
27
56
  const statsMid = viv.getChannelStats(rasterMid.data);
@@ -0,0 +1,24 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { toRgbUIString } from './layer-controller.js';
3
+
4
+ const GREY = [220, 220, 220];
5
+
6
+ describe('layer-controller/RasterChannelController.js', () => {
7
+ describe('toRgbUIString()', () => {
8
+ it('Maps color value to itself when colormap off and not white with dark theme', () => {
9
+ expect(toRgbUIString(false, [200, 200, 200], 'dark')).toEqual(`rgb(${[200, 200, 200]})`);
10
+ });
11
+ it('Maps color value to grey when colormap on with dark theme', () => {
12
+ expect(toRgbUIString(true, [200, 200, 200], 'dark')).toEqual(`rgb(${GREY})`);
13
+ });
14
+ it('Maps color value to grey when colormap on with light theme', () => {
15
+ expect(toRgbUIString(true, [200, 200, 200], 'light')).toEqual(`rgb(${GREY})`);
16
+ });
17
+ it('Maps color value to grey when colormap off and white with light theme', () => {
18
+ expect(toRgbUIString(false, [255, 255, 255], 'light')).toEqual(`rgb(${GREY})`);
19
+ });
20
+ it('Maps color value to grey when colormap off and white with light theme', () => {
21
+ expect(toRgbUIString(false, [200, 200, 200], 'light')).toEqual(`rgb(${[200, 200, 200]})`);
22
+ });
23
+ });
24
+ });
package/src/spatial.js CHANGED
@@ -53,6 +53,40 @@ export function getSourceFromLoader(loader, level) {
53
53
  return source;
54
54
  }
55
55
 
56
+ export const getStatsForResolution = (loader, resolution) => {
57
+ const { shape, labels } = loader[resolution];
58
+ const height = shape[labels.indexOf('y')];
59
+ const width = shape[labels.indexOf('x')];
60
+ const depth = shape[labels.indexOf('z')];
61
+ // eslint-disable-next-line no-bitwise
62
+ const depthDownsampled = Math.max(1, depth >> resolution);
63
+ // Check memory allocation limits for Float32Array (used in XR3DLayer for rendering)
64
+ const totalBytes = 4 * height * width * depthDownsampled;
65
+ return {
66
+ height, width, depthDownsampled, totalBytes,
67
+ };
68
+ };
69
+
70
+ export const canLoadResolution = (loader, resolution) => {
71
+ const {
72
+ totalBytes, height, width, depthDownsampled,
73
+ } = getStatsForResolution(
74
+ loader,
75
+ resolution,
76
+ );
77
+ const maxHeapSize = window.performance?.memory
78
+ && window.performance?.memory?.jsHeapSizeLimit / 2;
79
+ const maxSize = maxHeapSize || (2 ** 31) - 1;
80
+ // 2048 is a normal texture size limit although some browsers go larger.
81
+ return (
82
+ totalBytes < maxSize
83
+ && height <= 2048
84
+ && depthDownsampled <= 2048
85
+ && width <= 2048
86
+ && depthDownsampled > 1
87
+ );
88
+ };
89
+
56
90
  /**
57
91
  * Helper method to determine whether pixel data is interleaved and rgb or not.
58
92
  * @param {object} loader
@@ -77,6 +111,42 @@ export function isRgb(loader, channels) {
77
111
 
78
112
  // From spatial/utils.js
79
113
 
114
+ export function physicalSizeToMatrix(xSize, ySize, zSize, xUnit, yUnit, zUnit) {
115
+ const hasZPhyscialSize = Boolean(zSize) && Boolean(zUnit);
116
+ const hasYPhyscialSize = Boolean(ySize) && Boolean(yUnit);
117
+ const hasXPhyscialSize = Boolean(xSize) && Boolean(xUnit);
118
+ if (!hasXPhyscialSize || !hasYPhyscialSize) {
119
+ return (new Matrix4()).identity();
120
+ }
121
+ const sizes = [
122
+ unit(`${xSize} ${xUnit}`.replace('µ', 'u')),
123
+ unit(`${ySize} ${yUnit}`.replace('µ', 'u')),
124
+ ];
125
+ if (hasZPhyscialSize) {
126
+ sizes.push(unit(`${zSize} ${zUnit}`.replace('µ', 'u')));
127
+ }
128
+ // Find the ratio of the sizes to get the scaling factor.
129
+ const scale = sizes.map(i => divide(i, unit('1 um')));
130
+
131
+ // TODO: is this still needed
132
+ // sizes are special objects with own equals method - see `unit` in declaration
133
+ if (!sizes[0].equals(sizes[1])) {
134
+ // Handle scaling in the Y direction for non-square pixels
135
+ scale[1] = divide(sizes[1], sizes[0]);
136
+ }
137
+ // END TODO: is this still needed
138
+
139
+ // Add in z dimension needed for Matrix4 scale API.
140
+ if (!scale[2]) {
141
+ scale[2] = 1;
142
+ }
143
+ // no need to store/use identity scaling
144
+ if (isEqual(scale, [1, 1, 1])) {
145
+ return (new Matrix4()).identity();
146
+ }
147
+ return new Matrix4().scale([...scale]);
148
+ }
149
+
80
150
  function getMetaWithTransformMatrices(imageMeta, imageLoaders) {
81
151
  // Do not fill in transformation matrices if any of the layers specify one.
82
152
  const sources = imageLoaders.map(loader => getSourceFromLoader(loader));
@@ -367,11 +437,11 @@ export function getNgffAxesForTiff(dimOrder) {
367
437
  /**
368
438
  * Convert an array of coordinateTransformations objects to a 16-element
369
439
  * plain JS array using Matrix4 linear algebra transformation functions.
370
- * @param {object[]} coordinateTransformations List of objects matching the
440
+ * @param {object[]|undefined} coordinateTransformations List of objects matching the
371
441
  * OME-NGFF v0.4 coordinateTransformations spec.
372
- * @param {object[]} axes Axes in OME-NGFF v0.4 format, objects
442
+ * @param {object[]|undefined} axes Axes in OME-NGFF v0.4 format, objects
373
443
  * with { type, name }.
374
- * @returns {Array<number>} Array of 16 numbers representing the Matrix4.
444
+ * @returns {Matrix4} Array of 16 numbers representing the Matrix4.
375
445
  */
376
446
  export function coordinateTransformationsToMatrix(coordinateTransformations, axes) {
377
447
  let mat = (new Matrix4()).identity();
@@ -417,6 +487,15 @@ export function coordinateTransformationsToMatrix(coordinateTransformations, axe
417
487
  return mat;
418
488
  }
419
489
 
490
+ export function hexToRgb(hex) {
491
+ const result = /^#?([A-F\d]{2})([A-F\d]{2})([A-F\d]{2})$/i.exec(hex);
492
+ return [
493
+ parseInt(result[1].toLowerCase(), 16),
494
+ parseInt(result[2].toLowerCase(), 16),
495
+ parseInt(result[3].toLowerCase(), 16),
496
+ ];
497
+ }
498
+
420
499
  /**
421
500
  * Normalize coordinate transformations to the OME-NGFF v0.4 format,
422
501
  * despite potentially being in the new format proposed in