@vitessce/spatial-utils 2.0.3-beta.0 → 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.
@@ -0,0 +1,10 @@
1
+ import { B as BaseDecoder } from "./index-ce1fad7e.js";
2
+ import "react";
3
+ class RawDecoder extends BaseDecoder {
4
+ decodeBlock(buffer) {
5
+ return buffer;
6
+ }
7
+ }
8
+ export {
9
+ RawDecoder as default
10
+ };
@@ -0,0 +1,30 @@
1
+ import { B as BaseDecoder } from "./index-ce1fad7e.js";
2
+ import "react";
3
+ class WebImageDecoder extends BaseDecoder {
4
+ constructor() {
5
+ super();
6
+ if (typeof createImageBitmap === "undefined") {
7
+ throw new Error("Cannot decode WebImage as `createImageBitmap` is not available");
8
+ } else if (typeof document === "undefined" && typeof OffscreenCanvas === "undefined") {
9
+ throw new Error("Cannot decode WebImage as neither `document` nor `OffscreenCanvas` is not available");
10
+ }
11
+ }
12
+ async decode(fileDirectory, buffer) {
13
+ const blob = new Blob([buffer]);
14
+ const imageBitmap = await createImageBitmap(blob);
15
+ let canvas;
16
+ if (typeof document !== "undefined") {
17
+ canvas = document.createElement("canvas");
18
+ canvas.width = imageBitmap.width;
19
+ canvas.height = imageBitmap.height;
20
+ } else {
21
+ canvas = new OffscreenCanvas(imageBitmap.width, imageBitmap.height);
22
+ }
23
+ const ctx = canvas.getContext("2d");
24
+ ctx.drawImage(imageBitmap, 0, 0);
25
+ return ctx.getImageData(0, 0, imageBitmap.width, imageBitmap.height).data.buffer;
26
+ }
27
+ }
28
+ export {
29
+ WebImageDecoder as default
30
+ };
@@ -0,0 +1,2 @@
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";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":""}
@@ -0,0 +1 @@
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';
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Get physical size scaling Matrix4
3
+ * @param {Object} loader PixelSource
4
+ * @returns {Object} matrix
5
+ */
6
+ export function getPhysicalSizeScalingMatrix(loader: Object): Object;
7
+ /**
8
+ * Get bounding cube for a given loader
9
+ * @param {Object} loader PixelSource|PixelSource[]
10
+ * @returns {Array} [0, width], [0, height], [0, depth]]
11
+ */
12
+ export function getBoundingCube(loader: Object): any[];
13
+ export function abbreviateNumber(value: any): any;
14
+ export function getSingleSelectionStats({ loader, selection, use3d }: Object): Object;
15
+ export function getMultiSelectionStats({ loader, selections, use3d }: {
16
+ loader: any;
17
+ selections: any;
18
+ use3d: any;
19
+ }): Promise<{
20
+ domains: any[];
21
+ sliders: any[];
22
+ }>;
23
+ //# sourceMappingURL=layer-controller.d.ts.map
@@ -0,0 +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"}
@@ -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"}
@@ -1,9 +1,9 @@
1
- import isEqual from 'lodash/isEqual';
1
+ import { isEqual } from 'lodash-es';
2
2
  import { Matrix4 } from 'math.gl';
3
3
  import { divide, compare, unit } from 'mathjs';
4
4
  import { VIEWER_PALETTE } from '@vitessce/utils';
5
5
  import { viv } from '@vitessce/gl';
6
- import { getMultiSelectionStats } from './layer-controller';
6
+ import { getMultiSelectionStats } from './layer-controller.js';
7
7
  export function square(x, y, r) {
8
8
  return [[x, y + r], [x + r, y], [x, y - r], [x - r, y]];
9
9
  }
@@ -102,6 +102,11 @@ function getMetaWithTransformMatrices(imageMeta, imageLoaders) {
102
102
  }
103
103
  // Find the ratio of the sizes to get the scaling factor.
104
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
+ }
105
110
  // Add in z dimension needed for Matrix4 scale API.
106
111
  if (!scale[2]) {
107
112
  scale[2] = 1;
@@ -279,3 +284,92 @@ export async function initializeRasterLayersAndChannels(rasterLayers, rasterRend
279
284
  const autoImageLayerDefs = await Promise.all(autoImageLayerDefPromises);
280
285
  return [autoImageLayerDefs, nextImageLoaders, nextImageMetaAndLayers];
281
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=spatial.test.d.ts.map
@@ -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": "2.0.3-beta.0",
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
+ "type": "module",
11
12
  "main": "dist/index.js",
12
13
  "files": [
13
- "dist"
14
+ "src",
15
+ "dist",
16
+ "dist-tsc"
14
17
  ],
15
18
  "dependencies": {
16
- "lodash": "^4.17.21",
19
+ "lodash-es": "^4.17.21",
17
20
  "math.gl": "^3.5.6",
18
21
  "mathjs": "^9.2.0",
19
- "@vitessce/utils": "2.0.3-beta.0",
20
- "@vitessce/gl": "2.0.3-beta.0"
22
+ "@vitessce/gl": "3.0.0",
23
+ "@vitessce/utils": "3.0.0"
21
24
  },
22
25
  "scripts": {
23
- "start": "tsc --watch",
24
- "build": "tsc",
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
  }
package/src/index.js ADDED
@@ -0,0 +1,18 @@
1
+ export {
2
+ GLOBAL_LABELS,
3
+ DEFAULT_CELLS_LAYER,
4
+ DEFAULT_MOLECULES_LAYER,
5
+ DEFAULT_NEIGHBORHOODS_LAYER,
6
+ DEFAULT_RASTER_DOMAIN_TYPE,
7
+ DEFAULT_RASTER_LAYER_PROPS,
8
+ DEFAULT_LAYER_TYPE_ORDERING,
9
+ square,
10
+ initializeLayerChannels,
11
+ initializeRasterLayersAndChannels,
12
+ getSourceFromLoader,
13
+ isInterleaved,
14
+ isRgb,
15
+ getNgffAxes,
16
+ getNgffAxesForTiff,
17
+ coordinateTransformationsToMatrix,
18
+ } from './spatial.js';
@@ -0,0 +1,129 @@
1
+ import { Matrix4 } from 'math.gl';
2
+ import { viv } from '@vitessce/gl';
3
+
4
+ async function getSingleSelectionStats2D({ loader, selection }) {
5
+ const data = Array.isArray(loader) ? loader[loader.length - 1] : loader;
6
+ const raster = await data.getRaster({ selection });
7
+ const selectionStats = viv.getChannelStats(raster.data);
8
+ const { domain, contrastLimits: slider } = selectionStats;
9
+ return { domain, slider };
10
+ }
11
+
12
+ async function getSingleSelectionStats3D({ loader, selection }) {
13
+ const lowResSource = loader[loader.length - 1];
14
+ const { shape, labels } = lowResSource;
15
+ // eslint-disable-next-line no-bitwise
16
+ const sizeZ = shape[labels.indexOf('z')] >> (loader.length - 1);
17
+ const raster0 = await lowResSource.getRaster({
18
+ selection: { ...selection, z: 0 },
19
+ });
20
+ const rasterMid = await lowResSource.getRaster({
21
+ selection: { ...selection, z: Math.floor(sizeZ / 2) },
22
+ });
23
+ const rasterTop = await lowResSource.getRaster({
24
+ selection: { ...selection, z: Math.max(0, sizeZ - 1) },
25
+ });
26
+ const stats0 = viv.getChannelStats(raster0.data);
27
+ const statsMid = viv.getChannelStats(rasterMid.data);
28
+ const statsTop = viv.getChannelStats(rasterTop.data);
29
+ return {
30
+ domain: [
31
+ Math.min(stats0.domain[0], statsMid.domain[0], statsTop.domain[0]),
32
+ Math.max(stats0.domain[1], statsMid.domain[1], statsTop.domain[1]),
33
+ ],
34
+ slider: [
35
+ Math.min(
36
+ stats0.contrastLimits[0],
37
+ statsMid.contrastLimits[0],
38
+ statsTop.contrastLimits[0],
39
+ ),
40
+ Math.max(
41
+ stats0.contrastLimits[1],
42
+ statsMid.contrastLimits[1],
43
+ statsTop.contrastLimits[1],
44
+ ),
45
+ ],
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Get bounding cube for a given loader i.e [[0, width], [0, height], [0, depth]]
51
+ * @param {Object} loader PixelSource|PixelSource[]
52
+ * @param {[]} selection Selection for stats.
53
+ * @param {boolean} use3d Whether or not to get 3d stats.
54
+ * @returns {Object} { domains, sliders }
55
+ */
56
+ export const getSingleSelectionStats = async ({ loader, selection, use3d }) => {
57
+ const getStats = use3d
58
+ ? getSingleSelectionStats3D
59
+ : getSingleSelectionStats2D;
60
+ return getStats({ loader, selection });
61
+ };
62
+
63
+ export const getMultiSelectionStats = async ({ loader, selections, use3d }) => {
64
+ const stats = await Promise.all(
65
+ selections.map(selection => getSingleSelectionStats({ loader, selection, use3d })),
66
+ );
67
+ const domains = stats.map(stat => stat.domain);
68
+ const sliders = stats.map(stat => stat.slider);
69
+ return { domains, sliders };
70
+ };
71
+
72
+ /**
73
+ * Get physical size scaling Matrix4
74
+ * @param {Object} loader PixelSource
75
+ * @returns {Object} matrix
76
+ */
77
+ export function getPhysicalSizeScalingMatrix(loader) {
78
+ const { x, y, z } = loader?.meta?.physicalSizes ?? {};
79
+ if (x?.size && y?.size && z?.size) {
80
+ const min = Math.min(z.size, x.size, y.size);
81
+ const ratio = [x.size / min, y.size / min, z.size / min];
82
+ return new Matrix4().scale(ratio);
83
+ }
84
+ return new Matrix4().identity();
85
+ }
86
+
87
+ /**
88
+ * Get bounding cube for a given loader
89
+ * @param {Object} loader PixelSource|PixelSource[]
90
+ * @returns {Array} [0, width], [0, height], [0, depth]]
91
+ */
92
+ export function getBoundingCube(loader) {
93
+ const source = Array.isArray(loader) ? loader[0] : loader;
94
+ const { shape, labels } = source;
95
+ const physicalSizeScalingMatrix = getPhysicalSizeScalingMatrix(source);
96
+ const xSlice = [0, physicalSizeScalingMatrix[0] * shape[labels.indexOf('x')]];
97
+ const ySlice = [0, physicalSizeScalingMatrix[5] * shape[labels.indexOf('y')]];
98
+ const zSlice = [
99
+ 0,
100
+ physicalSizeScalingMatrix[10] * shape[labels.indexOf('z')],
101
+ ];
102
+ return [xSlice, ySlice, zSlice];
103
+ }
104
+
105
+ export function abbreviateNumber(value) {
106
+ // Return an abbreviated representation of value, in 5 characters or less.
107
+
108
+ const maxLength = 5;
109
+ let maxNaiveDigits = maxLength;
110
+
111
+ /* eslint-disable no-plusplus */
112
+ if (!Number.isInteger(value)) {
113
+ --maxNaiveDigits;
114
+ } // Wasted on "."
115
+ if (value < 1) {
116
+ --maxNaiveDigits;
117
+ } // Wasted on "0."
118
+ /* eslint-disable no-plusplus */
119
+
120
+ const naive = Intl.NumberFormat('en-US', {
121
+ maximumSignificantDigits: maxNaiveDigits,
122
+ useGrouping: false,
123
+ }).format(value);
124
+ if (naive.length <= maxLength) return naive;
125
+
126
+ // "e+9" consumes 3 characters, so if we even had two significant digits,
127
+ // it would take take us to six characters, including the decimal point.
128
+ return value.toExponential(0);
129
+ }