@vitessce/spatial-utils 3.6.6 → 3.6.8
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-Cumj_9kB.js → deflate-D50eseYg.js} +1 -1
- package/dist/{index-C6jVF_qi.js → index-RMBcVNTZ.js} +115 -8
- package/dist/index.js +1 -1
- package/dist/{jpeg-Bv9e5TsR.js → jpeg-Dp762_Sy.js} +1 -1
- package/dist/{lerc-CN8oy9fE.js → lerc-R19D-u1d.js} +1 -1
- package/dist/{lzw-Ckpw-zOq.js → lzw-CS9qpOLK.js} +1 -1
- package/dist/{packbits-KgFJoulg.js → packbits-smBOUAP8.js} +1 -1
- package/dist/{raw-DwHPYVQy.js → raw-B-RRtlid.js} +1 -1
- package/dist/{webimage-BeD_xi6W.js → webimage-CZyj01Qe.js} +1 -1
- package/dist-tsc/load-ome-zarr.d.ts.map +1 -1
- package/dist-tsc/load-ome-zarr.js +16 -1
- package/dist-tsc/spatial.d.ts +8 -0
- package/dist-tsc/spatial.d.ts.map +1 -1
- package/dist-tsc/spatial.js +96 -0
- package/dist-tsc/spatial.test.js +120 -1
- package/package.json +7 -6
- package/src/load-ome-zarr.js +14 -1
- package/src/spatial.js +109 -0
- package/src/spatial.test.js +122 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as inflate_1 } from "./pako.esm-SxljTded.js";
|
|
2
|
-
import { B as BaseDecoder } from "./index-
|
|
2
|
+
import { B as BaseDecoder } from "./index-RMBcVNTZ.js";
|
|
3
3
|
class DeflateDecoder extends BaseDecoder {
|
|
4
4
|
decodeBlock(buffer) {
|
|
5
5
|
return inflate_1(new Uint8Array(buffer)).buffer;
|
|
@@ -120365,22 +120365,22 @@ function addDecoder(cases, importFn) {
|
|
|
120365
120365
|
}
|
|
120366
120366
|
cases.forEach((c) => registry$2.set(c, importFn));
|
|
120367
120367
|
}
|
|
120368
|
-
addDecoder([void 0, 1], () => import("./raw-
|
|
120369
|
-
addDecoder(5, () => import("./lzw-
|
|
120368
|
+
addDecoder([void 0, 1], () => import("./raw-B-RRtlid.js").then((m) => m.default));
|
|
120369
|
+
addDecoder(5, () => import("./lzw-CS9qpOLK.js").then((m) => m.default));
|
|
120370
120370
|
addDecoder(6, () => {
|
|
120371
120371
|
throw new Error("old style JPEG compression is not supported.");
|
|
120372
120372
|
});
|
|
120373
|
-
addDecoder(7, () => import("./jpeg-
|
|
120374
|
-
addDecoder([8, 32946], () => import("./deflate-
|
|
120375
|
-
addDecoder(32773, () => import("./packbits-
|
|
120373
|
+
addDecoder(7, () => import("./jpeg-Dp762_Sy.js").then((m) => m.default));
|
|
120374
|
+
addDecoder([8, 32946], () => import("./deflate-D50eseYg.js").then((m) => m.default));
|
|
120375
|
+
addDecoder(32773, () => import("./packbits-smBOUAP8.js").then((m) => m.default));
|
|
120376
120376
|
addDecoder(
|
|
120377
120377
|
34887,
|
|
120378
|
-
() => import("./lerc-
|
|
120378
|
+
() => import("./lerc-R19D-u1d.js").then(async (m) => {
|
|
120379
120379
|
await m.zstd.init();
|
|
120380
120380
|
return m;
|
|
120381
120381
|
}).then((m) => m.default)
|
|
120382
120382
|
);
|
|
120383
|
-
addDecoder(50001, () => import("./webimage-
|
|
120383
|
+
addDecoder(50001, () => import("./webimage-CZyj01Qe.js").then((m) => m.default));
|
|
120384
120384
|
function decodeRowAcc(row, stride) {
|
|
120385
120385
|
let length2 = row.length - stride;
|
|
120386
120386
|
let offset = 0;
|
|
@@ -137995,11 +137995,80 @@ function getNgffAxesForTiff(dimOrder) {
|
|
|
137995
137995
|
type: getDefaultAxisType(name2)
|
|
137996
137996
|
}));
|
|
137997
137997
|
}
|
|
137998
|
+
function getSwapAxesMatrix(inputAxes, outputAxes) {
|
|
137999
|
+
const size2 = inputAxes.length;
|
|
138000
|
+
if (!(size2 === 2 || size2 === 3)) {
|
|
138001
|
+
throw new Error(`Expected input and output axes to have 2 or 3 elements, got ${size2}.`);
|
|
138002
|
+
}
|
|
138003
|
+
const matrix2 = [
|
|
138004
|
+
[0, 0, 0, 0],
|
|
138005
|
+
[0, 0, 0, 0],
|
|
138006
|
+
[0, 0, size2 === 2 ? 1 : 0, 0],
|
|
138007
|
+
// In 2D, leave Z unchanged.
|
|
138008
|
+
[0, 0, 0, 1]
|
|
138009
|
+
];
|
|
138010
|
+
for (let outRow = 0; outRow < size2; outRow++) {
|
|
138011
|
+
const axis = outputAxes[outRow];
|
|
138012
|
+
const inCol = inputAxes.indexOf(axis);
|
|
138013
|
+
if (inCol === -1) {
|
|
138014
|
+
throw new Error(`Axis "${axis}" not found in inputAxes`);
|
|
138015
|
+
}
|
|
138016
|
+
matrix2[outRow][inCol] = 1;
|
|
138017
|
+
}
|
|
138018
|
+
return matrix2;
|
|
138019
|
+
}
|
|
137998
138020
|
function coordinateTransformationsToMatrix(coordinateTransformations, axes) {
|
|
137999
138021
|
let mat = new Matrix4().identity();
|
|
138000
138022
|
if (coordinateTransformations && axes) {
|
|
138001
138023
|
const xyzIndices = ["x", "y", "z"].map((name2) => axes.findIndex((axisObj) => axisObj.type === "space" && axisObj.name === name2));
|
|
138002
138024
|
coordinateTransformations.forEach((transform2) => {
|
|
138025
|
+
if (transform2.type === "affine") {
|
|
138026
|
+
const { affine, input, output } = transform2;
|
|
138027
|
+
const excludeEntries = input.axes.map((axis, i2) => [axis, i2]).filter(([axis, i2]) => axis.type !== "space").map(([axis, i2]) => i2);
|
|
138028
|
+
const filteredAffine = affine.filter((_, i2) => !excludeEntries.includes(i2)).map((row) => row.filter((_, i2) => !excludeEntries.includes(i2)));
|
|
138029
|
+
const spatialInputAxes = input.axes.filter((axis) => axis.type === "space");
|
|
138030
|
+
const spatialOutputAxes = output.axes.filter((axis) => axis.type === "space");
|
|
138031
|
+
const inputAxisNames = spatialInputAxes.map((axis) => axis.name);
|
|
138032
|
+
const outputAxisNames = spatialOutputAxes.map((axis) => axis.name);
|
|
138033
|
+
if (spatialInputAxes.length === 3) {
|
|
138034
|
+
const nextMat = new Matrix4().fromArray([
|
|
138035
|
+
...filteredAffine[0],
|
|
138036
|
+
...filteredAffine[1],
|
|
138037
|
+
...filteredAffine[2],
|
|
138038
|
+
0,
|
|
138039
|
+
0,
|
|
138040
|
+
0,
|
|
138041
|
+
1
|
|
138042
|
+
]);
|
|
138043
|
+
mat = mat.multiplyLeft(nextMat);
|
|
138044
|
+
if (!isEqual$1(inputAxisNames, outputAxisNames)) {
|
|
138045
|
+
const swapMatNested = getSwapAxesMatrix(inputAxisNames, outputAxisNames);
|
|
138046
|
+
const swapMat = new Matrix4().fromArray(swapMatNested.flat());
|
|
138047
|
+
mat = mat.multiplyLeft(swapMat);
|
|
138048
|
+
}
|
|
138049
|
+
} else if (spatialOutputAxes.length === 2) {
|
|
138050
|
+
const nextMat = new Matrix4().fromArray([
|
|
138051
|
+
...filteredAffine[0],
|
|
138052
|
+
...filteredAffine[1],
|
|
138053
|
+
0,
|
|
138054
|
+
0,
|
|
138055
|
+
1,
|
|
138056
|
+
0,
|
|
138057
|
+
0,
|
|
138058
|
+
0,
|
|
138059
|
+
0,
|
|
138060
|
+
1
|
|
138061
|
+
]);
|
|
138062
|
+
mat = mat.multiplyLeft(nextMat);
|
|
138063
|
+
if (!isEqual$1(inputAxisNames, outputAxisNames)) {
|
|
138064
|
+
const swapMatNested = getSwapAxesMatrix(inputAxisNames, outputAxisNames);
|
|
138065
|
+
const swapMat = new Matrix4().fromArray(swapMatNested.flat());
|
|
138066
|
+
mat = mat.multiplyLeft(swapMat);
|
|
138067
|
+
}
|
|
138068
|
+
} else {
|
|
138069
|
+
throw new Error("Affine transformation must have 2 or 3 rows.");
|
|
138070
|
+
}
|
|
138071
|
+
}
|
|
138003
138072
|
if (transform2.type === "translation") {
|
|
138004
138073
|
const { translation: axisOrderedTranslation } = transform2;
|
|
138005
138074
|
if (axisOrderedTranslation.length !== axes.length) {
|
|
@@ -138059,6 +138128,16 @@ function normalizeCoordinateTransformations(coordinateTransformations, datasets)
|
|
|
138059
138128
|
scale: transform2.scale
|
|
138060
138129
|
};
|
|
138061
138130
|
}
|
|
138131
|
+
if (type === "affine") {
|
|
138132
|
+
return {
|
|
138133
|
+
type,
|
|
138134
|
+
affine: transform2.affine,
|
|
138135
|
+
// Affine transformations can potentially swap axes,
|
|
138136
|
+
// so we need to include the input and output axes.
|
|
138137
|
+
input: transform2.input,
|
|
138138
|
+
output: transform2.output
|
|
138139
|
+
};
|
|
138140
|
+
}
|
|
138062
138141
|
if (type === "identity") {
|
|
138063
138142
|
return { type };
|
|
138064
138143
|
}
|
|
@@ -139564,6 +139643,24 @@ function createZarrArrayAdapter(arr) {
|
|
|
139564
139643
|
}
|
|
139565
139644
|
});
|
|
139566
139645
|
}
|
|
139646
|
+
class VitessceError extends Error {
|
|
139647
|
+
constructor(message) {
|
|
139648
|
+
super(message);
|
|
139649
|
+
this.name = "VitessceError";
|
|
139650
|
+
}
|
|
139651
|
+
}
|
|
139652
|
+
class DataLoaderError extends VitessceError {
|
|
139653
|
+
constructor(message) {
|
|
139654
|
+
super(message);
|
|
139655
|
+
this.name = "DataLoaderError";
|
|
139656
|
+
}
|
|
139657
|
+
}
|
|
139658
|
+
class ZarrNodeNotFoundError extends DataLoaderError {
|
|
139659
|
+
constructor(message) {
|
|
139660
|
+
super(message);
|
|
139661
|
+
this.name = "ZarrNodeNotFoundError";
|
|
139662
|
+
}
|
|
139663
|
+
}
|
|
139567
139664
|
function prevPowerOf2(x) {
|
|
139568
139665
|
return 2 ** Math.floor(Math.log2(x));
|
|
139569
139666
|
}
|
|
@@ -139581,7 +139678,15 @@ function isAxis(axisOrLabel) {
|
|
|
139581
139678
|
return typeof axisOrLabel[0] !== "string";
|
|
139582
139679
|
}
|
|
139583
139680
|
async function loadMultiscales(root2) {
|
|
139584
|
-
|
|
139681
|
+
let rootAttrs;
|
|
139682
|
+
try {
|
|
139683
|
+
rootAttrs = (await open(root2)).attrs;
|
|
139684
|
+
} catch (e2) {
|
|
139685
|
+
if (e2.name === "NodeNotFoundError") {
|
|
139686
|
+
throw new ZarrNodeNotFoundError(root2.path);
|
|
139687
|
+
}
|
|
139688
|
+
throw e2;
|
|
139689
|
+
}
|
|
139585
139690
|
let paths = ["0"];
|
|
139586
139691
|
let labels = ["t", "c", "z", "y", "x"];
|
|
139587
139692
|
if ("multiscales" in rootAttrs) {
|
|
@@ -139594,6 +139699,8 @@ async function loadMultiscales(root2) {
|
|
|
139594
139699
|
labels = axes;
|
|
139595
139700
|
}
|
|
139596
139701
|
}
|
|
139702
|
+
} else {
|
|
139703
|
+
throw new Error("Expected the Zarr store to contain OME-NGFF multiscales metadata.");
|
|
139597
139704
|
}
|
|
139598
139705
|
const data = paths.map((path) => open(root2.resolve(path), { kind: "array" }));
|
|
139599
139706
|
return {
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D, e, a, b, c, d, F, G, Z, C, r, n, E, A, y, l, m, z, x, h, q, v, o, i, f, j, k, u, t, s, p, w } from "./index-
|
|
1
|
+
import { D, e, a, b, c, d, F, G, Z, C, r, n, E, A, y, l, m, z, x, h, q, v, o, i, f, j, k, u, t, s, p, w } from "./index-RMBcVNTZ.js";
|
|
2
2
|
export {
|
|
3
3
|
D as DEFAULT_CELLS_LAYER,
|
|
4
4
|
e as DEFAULT_LAYER_TYPE_ORDERING,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as inflate_1 } from "./pako.esm-SxljTded.js";
|
|
2
|
-
import { g as getDefaultExportFromCjs, B as BaseDecoder } from "./index-
|
|
2
|
+
import { g as getDefaultExportFromCjs, B as BaseDecoder } from "./index-RMBcVNTZ.js";
|
|
3
3
|
const LercParameters = {
|
|
4
4
|
AddCompression: 1
|
|
5
5
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-ome-zarr.d.ts","sourceRoot":"","sources":["../src/load-ome-zarr.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"load-ome-zarr.d.ts","sourceRoot":"","sources":["../src/load-ome-zarr.js"],"names":[],"mappings":"AAmBA,gDAMC;AA6DD;;;GAaC;AA7BD;IACE,kDASC;IADC,qBAAwB;CAE3B;oBAjFmB,cAAc"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { viv } from '@vitessce/gl';
|
|
2
2
|
import { open as zarrOpen } from 'zarrita';
|
|
3
3
|
import { createZarrArrayAdapter } from '@vitessce/zarr-utils';
|
|
4
|
+
import { ZarrNodeNotFoundError } from '@vitessce/error';
|
|
4
5
|
function prevPowerOf2(x) {
|
|
5
6
|
return 2 ** Math.floor(Math.log2(x));
|
|
6
7
|
}
|
|
@@ -24,7 +25,17 @@ function isAxis(axisOrLabel) {
|
|
|
24
25
|
return typeof axisOrLabel[0] !== 'string';
|
|
25
26
|
}
|
|
26
27
|
async function loadMultiscales(root) {
|
|
27
|
-
|
|
28
|
+
let rootAttrs;
|
|
29
|
+
try {
|
|
30
|
+
rootAttrs = (await zarrOpen(root)).attrs;
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
if (e.name === 'NodeNotFoundError') {
|
|
34
|
+
throw new ZarrNodeNotFoundError(root.path);
|
|
35
|
+
}
|
|
36
|
+
// Re-throw the error if it is not a NodeNotFoundError.
|
|
37
|
+
throw e;
|
|
38
|
+
}
|
|
28
39
|
let paths = ['0'];
|
|
29
40
|
// Default axes used for v0.1 and v0.2.
|
|
30
41
|
let labels = ['t', 'c', 'z', 'y', 'x'];
|
|
@@ -40,6 +51,10 @@ async function loadMultiscales(root) {
|
|
|
40
51
|
}
|
|
41
52
|
}
|
|
42
53
|
}
|
|
54
|
+
else {
|
|
55
|
+
// TODO: Relax this after https://github.com/vitessce/vitessce/issues/2199
|
|
56
|
+
throw new Error('Expected the Zarr store to contain OME-NGFF multiscales metadata.');
|
|
57
|
+
}
|
|
43
58
|
const data = paths
|
|
44
59
|
.map(path => zarrOpen(root.resolve(path), { kind: 'array' }));
|
|
45
60
|
return {
|
package/dist-tsc/spatial.d.ts
CHANGED
|
@@ -41,6 +41,14 @@ export function getNgffAxes(firstMultiscalesAxes: any): {
|
|
|
41
41
|
name: string;
|
|
42
42
|
}[];
|
|
43
43
|
export function getNgffAxesForTiff(dimOrder: any): any;
|
|
44
|
+
/**
|
|
45
|
+
* Get a 4x4 matrix that swaps axes.
|
|
46
|
+
* TODO: add unit tests.
|
|
47
|
+
* @param {("x"|"y"|"z")[]} inputAxes
|
|
48
|
+
* @param {("x"|"y"|"z")[]} outputAxes
|
|
49
|
+
* @return {number[][]} 4x4 matrix that swaps axes.
|
|
50
|
+
*/
|
|
51
|
+
export function getSwapAxesMatrix(inputAxes: ("x" | "y" | "z")[], outputAxes: ("x" | "y" | "z")[]): number[][];
|
|
44
52
|
/**
|
|
45
53
|
* Convert an array of coordinateTransformations objects to a 16-element
|
|
46
54
|
* plain JS array using Matrix4 linear algebra transformation functions.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spatial.d.ts","sourceRoot":"","sources":["../src/spatial.js"],"names":[],"mappings":"AAOA;;;;;;GAMG;AACH,4CAJW,MAAM,SACN,MAAM,GAAG,SAAS,GAChB,MAAM,CAMlB;AAoCD;;;;GAIG;AACH,8BAHW,MAAM,YACN,KAAK,GAAC,IAAI,WAiBpB;AAID,sHAmCC;AAiHD;;GAEG;AACH,qCAFW,KAAK,CAAE,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,
|
|
1
|
+
{"version":3,"file":"spatial.d.ts","sourceRoot":"","sources":["../src/spatial.js"],"names":[],"mappings":"AAOA;;;;;;GAMG;AACH,4CAJW,MAAM,SACN,MAAM,GAAG,SAAS,GAChB,MAAM,CAMlB;AAoCD;;;;GAIG;AACH,8BAHW,MAAM,YACN,KAAK,GAAC,IAAI,WAiBpB;AAID,sHAmCC;AAiHD;;GAEG;AACH,qCAFW,KAAK,CAAE,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;;;;;;GAMG;AACH,6CAJW,CAAC,GAAG,GAAC,GAAG,GAAC,GAAG,CAAC,EAAE,cACf,CAAC,GAAG,GAAC,GAAG,GAAC,GAAG,CAAC,EAAE,GACd,MAAM,EAAE,EAAE,CA6BrB;AAED;;;;;;;;GAQG;AACH,6EANW,MAAM,EAAE,GAAC,SAAS,QAElB,MAAM,EAAE,GAAC,SAAS,GAEhB,OAAO,CA0GnB;AAED,6CAOC;AAMD,8CASC;AAED;;;;;;;;;GASG;AACH,8EANW,MAAM,EAAE,GAAC,SAAS,YAGlB,MAAM,EAAE,GACN,MAAM,EAAE,CA+DpB;AApnBM;;;;;EAYN;AAEM,yEAkBN;wBAnDuB,SAAS"}
|
package/dist-tsc/spatial.js
CHANGED
|
@@ -356,6 +356,38 @@ export function getNgffAxesForTiff(dimOrder) {
|
|
|
356
356
|
type: getDefaultAxisType(name),
|
|
357
357
|
}));
|
|
358
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* Get a 4x4 matrix that swaps axes.
|
|
361
|
+
* TODO: add unit tests.
|
|
362
|
+
* @param {("x"|"y"|"z")[]} inputAxes
|
|
363
|
+
* @param {("x"|"y"|"z")[]} outputAxes
|
|
364
|
+
* @return {number[][]} 4x4 matrix that swaps axes.
|
|
365
|
+
*/
|
|
366
|
+
export function getSwapAxesMatrix(inputAxes, outputAxes) {
|
|
367
|
+
const size = inputAxes.length;
|
|
368
|
+
if (!(size === 2 || size === 3)) {
|
|
369
|
+
throw new Error(`Expected input and output axes to have 2 or 3 elements, got ${size}.`);
|
|
370
|
+
}
|
|
371
|
+
// Initialize a 4x4 matrix.
|
|
372
|
+
const matrix = [
|
|
373
|
+
[0, 0, 0, 0],
|
|
374
|
+
[0, 0, 0, 0],
|
|
375
|
+
[0, 0, (size === 2 ? 1 : 0), 0], // In 2D, leave Z unchanged.
|
|
376
|
+
[0, 0, 0, 1],
|
|
377
|
+
];
|
|
378
|
+
// Fill linear transformation (top-left 3x3)
|
|
379
|
+
// Iterate over output rows.
|
|
380
|
+
for (let outRow = 0; outRow < size; outRow++) {
|
|
381
|
+
// Get the axis name for the current output row.
|
|
382
|
+
const axis = outputAxes[outRow];
|
|
383
|
+
const inCol = inputAxes.indexOf(axis);
|
|
384
|
+
if (inCol === -1) {
|
|
385
|
+
throw new Error(`Axis "${axis}" not found in inputAxes`);
|
|
386
|
+
}
|
|
387
|
+
matrix[outRow][inCol] = 1;
|
|
388
|
+
}
|
|
389
|
+
return matrix;
|
|
390
|
+
}
|
|
359
391
|
/**
|
|
360
392
|
* Convert an array of coordinateTransformations objects to a 16-element
|
|
361
393
|
* plain JS array using Matrix4 linear algebra transformation functions.
|
|
@@ -373,6 +405,58 @@ export function coordinateTransformationsToMatrix(coordinateTransformations, axe
|
|
|
373
405
|
// Apply each transformation sequentially and in order according to the OME-NGFF v0.4 spec.
|
|
374
406
|
// Reference: https://ngff.openmicroscopy.org/0.4/#trafo-md
|
|
375
407
|
coordinateTransformations.forEach((transform) => {
|
|
408
|
+
if (transform.type === 'affine') {
|
|
409
|
+
const { affine, input, output } = transform;
|
|
410
|
+
// This is needed since axes can include "c" for example.
|
|
411
|
+
const excludeEntries = input.axes
|
|
412
|
+
.map((axis, i) => ([axis, i]))
|
|
413
|
+
// eslint-disable-next-line no-unused-vars
|
|
414
|
+
.filter(([axis, i]) => axis.type !== 'space')
|
|
415
|
+
// eslint-disable-next-line no-unused-vars
|
|
416
|
+
.map(([axis, i]) => i);
|
|
417
|
+
// We only want to keep the affine transformation entries for the spatial axes.
|
|
418
|
+
const filteredAffine = affine
|
|
419
|
+
.filter((_, i) => !excludeEntries.includes(i))
|
|
420
|
+
.map(row => row.filter((_, i) => !excludeEntries.includes(i)));
|
|
421
|
+
// TODO: if there was some affine transformation for the "c" axis, warn/error.
|
|
422
|
+
const spatialInputAxes = input.axes.filter(axis => axis.type === 'space');
|
|
423
|
+
const spatialOutputAxes = output.axes.filter(axis => axis.type === 'space');
|
|
424
|
+
const inputAxisNames = spatialInputAxes.map(axis => axis.name);
|
|
425
|
+
const outputAxisNames = spatialOutputAxes.map(axis => axis.name);
|
|
426
|
+
if (spatialInputAxes.length === 3) { // 3D case
|
|
427
|
+
const nextMat = (new Matrix4()).fromArray([
|
|
428
|
+
...filteredAffine[0],
|
|
429
|
+
...filteredAffine[1],
|
|
430
|
+
...filteredAffine[2],
|
|
431
|
+
0, 0, 0, 1,
|
|
432
|
+
]);
|
|
433
|
+
mat = mat.multiplyLeft(nextMat);
|
|
434
|
+
if (!isEqual(inputAxisNames, outputAxisNames)) {
|
|
435
|
+
// Handle 3D axis swapping.
|
|
436
|
+
const swapMatNested = getSwapAxesMatrix(inputAxisNames, outputAxisNames);
|
|
437
|
+
const swapMat = (new Matrix4()).fromArray(swapMatNested.flat());
|
|
438
|
+
mat = mat.multiplyLeft(swapMat);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
else if (spatialOutputAxes.length === 2) { // 2D case
|
|
442
|
+
const nextMat = (new Matrix4()).fromArray([
|
|
443
|
+
...filteredAffine[0],
|
|
444
|
+
...filteredAffine[1],
|
|
445
|
+
0, 0, 1, 0,
|
|
446
|
+
0, 0, 0, 1,
|
|
447
|
+
]);
|
|
448
|
+
mat = mat.multiplyLeft(nextMat);
|
|
449
|
+
if (!isEqual(inputAxisNames, outputAxisNames)) {
|
|
450
|
+
// Handle 2D axis swapping.
|
|
451
|
+
const swapMatNested = getSwapAxesMatrix(inputAxisNames, outputAxisNames);
|
|
452
|
+
const swapMat = (new Matrix4()).fromArray(swapMatNested.flat());
|
|
453
|
+
mat = mat.multiplyLeft(swapMat);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
throw new Error('Affine transformation must have 2 or 3 rows.');
|
|
458
|
+
}
|
|
459
|
+
}
|
|
376
460
|
if (transform.type === 'translation') {
|
|
377
461
|
const { translation: axisOrderedTranslation } = transform;
|
|
378
462
|
if (axisOrderedTranslation.length !== axes.length) {
|
|
@@ -385,6 +469,7 @@ export function coordinateTransformationsToMatrix(coordinateTransformations, axe
|
|
|
385
469
|
: defaultValue));
|
|
386
470
|
const nextMat = (new Matrix4()).translate(xyzTranslation);
|
|
387
471
|
mat = mat.multiplyLeft(nextMat);
|
|
472
|
+
// TODO: error if the user tries to use a translation on the "c" axis.
|
|
388
473
|
}
|
|
389
474
|
if (transform.type === 'scale') {
|
|
390
475
|
const { scale: axisOrderedScale } = transform;
|
|
@@ -399,6 +484,7 @@ export function coordinateTransformationsToMatrix(coordinateTransformations, axe
|
|
|
399
484
|
: defaultValue));
|
|
400
485
|
const nextMat = (new Matrix4()).scale(xyzScale);
|
|
401
486
|
mat = mat.multiplyLeft(nextMat);
|
|
487
|
+
// TODO: error if the user tries to use a scale on the "c" axis.
|
|
402
488
|
}
|
|
403
489
|
});
|
|
404
490
|
}
|
|
@@ -457,6 +543,16 @@ export function normalizeCoordinateTransformations(coordinateTransformations, da
|
|
|
457
543
|
scale: transform.scale,
|
|
458
544
|
};
|
|
459
545
|
}
|
|
546
|
+
if (type === 'affine') {
|
|
547
|
+
return {
|
|
548
|
+
type,
|
|
549
|
+
affine: transform.affine,
|
|
550
|
+
// Affine transformations can potentially swap axes,
|
|
551
|
+
// so we need to include the input and output axes.
|
|
552
|
+
input: transform.input,
|
|
553
|
+
output: transform.output,
|
|
554
|
+
};
|
|
555
|
+
}
|
|
460
556
|
if (type === 'identity') {
|
|
461
557
|
return { type };
|
|
462
558
|
}
|
package/dist-tsc/spatial.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { coordinateTransformationsToMatrix, normalizeCoordinateTransformations, } from './spatial.js';
|
|
2
|
+
import { coordinateTransformationsToMatrix, normalizeCoordinateTransformations, getSwapAxesMatrix, } from './spatial.js';
|
|
3
3
|
const defaultAxes = [
|
|
4
4
|
{ type: 'time', name: 't' },
|
|
5
5
|
{ type: 'channel', name: 'c' },
|
|
@@ -35,6 +35,125 @@ describe('Spatial.js', () => {
|
|
|
35
35
|
0, 0, 0, 1,
|
|
36
36
|
]);
|
|
37
37
|
});
|
|
38
|
+
it('swap axes works for 2D', () => {
|
|
39
|
+
const inputAxes = ['x', 'y'];
|
|
40
|
+
const outputAxes = ['y', 'x'];
|
|
41
|
+
const swapMatrix = getSwapAxesMatrix(inputAxes, outputAxes);
|
|
42
|
+
expect(swapMatrix.flat()).toEqual([
|
|
43
|
+
0, 1, 0, 0,
|
|
44
|
+
1, 0, 0, 0,
|
|
45
|
+
0, 0, 1, 0,
|
|
46
|
+
0, 0, 0, 1,
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
it('swap axes works for 3D', () => {
|
|
50
|
+
const inputAxes = ['x', 'y', 'z'];
|
|
51
|
+
const outputAxes = ['z', 'y', 'x'];
|
|
52
|
+
const swapMatrix = getSwapAxesMatrix(inputAxes, outputAxes);
|
|
53
|
+
expect(swapMatrix.flat()).toEqual([
|
|
54
|
+
0, 0, 1, 0,
|
|
55
|
+
0, 1, 0, 0,
|
|
56
|
+
1, 0, 0, 0,
|
|
57
|
+
0, 0, 0, 1,
|
|
58
|
+
]);
|
|
59
|
+
});
|
|
60
|
+
it('swap axes works for 3D, different order', () => {
|
|
61
|
+
const inputAxes = ['x', 'z', 'y'];
|
|
62
|
+
const outputAxes = ['x', 'y', 'z'];
|
|
63
|
+
const swapMatrix = getSwapAxesMatrix(inputAxes, outputAxes);
|
|
64
|
+
expect(swapMatrix.flat()).toEqual([
|
|
65
|
+
1, 0, 0, 0,
|
|
66
|
+
0, 0, 1, 0,
|
|
67
|
+
0, 1, 0, 0,
|
|
68
|
+
0, 0, 0, 1,
|
|
69
|
+
]);
|
|
70
|
+
});
|
|
71
|
+
it('supports 3D affine transformation, for coordinate system with C axis', () => {
|
|
72
|
+
const coordinateTransformations = [
|
|
73
|
+
{ type: 'scale', scale: [1, 1, 1, 1] },
|
|
74
|
+
{
|
|
75
|
+
type: 'affine',
|
|
76
|
+
affine: [
|
|
77
|
+
[1, 0, 0, 0, 0],
|
|
78
|
+
[0, 0, 0, 1, 0],
|
|
79
|
+
[0, 0, 1, 0, 0],
|
|
80
|
+
[0, 13.76, 0, 0, -13.76],
|
|
81
|
+
],
|
|
82
|
+
input: {
|
|
83
|
+
name: 'czyx',
|
|
84
|
+
axes: [
|
|
85
|
+
{ type: 'channel', name: 'c' },
|
|
86
|
+
{ type: 'space', name: 'x' },
|
|
87
|
+
{ type: 'space', name: 'y' },
|
|
88
|
+
{ type: 'space', name: 'z' },
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
output: {
|
|
92
|
+
name: 'global',
|
|
93
|
+
axes: [
|
|
94
|
+
// Notice: swap-axes (CXYZ to CZYX).
|
|
95
|
+
{ type: 'channel', name: 'c' },
|
|
96
|
+
{ type: 'space', name: 'z' },
|
|
97
|
+
{ type: 'space', name: 'y' },
|
|
98
|
+
{ type: 'space', name: 'x' },
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
];
|
|
103
|
+
const axes = [
|
|
104
|
+
{ type: 'channel', name: 'c' },
|
|
105
|
+
{ type: 'space', name: 'x' },
|
|
106
|
+
{ type: 'space', name: 'y' },
|
|
107
|
+
{ type: 'space', name: 'z' },
|
|
108
|
+
];
|
|
109
|
+
expect(coordinateTransformationsToMatrix(coordinateTransformations, axes)).toEqual([
|
|
110
|
+
1, 0, 0, 0,
|
|
111
|
+
0, 1, 0, 0,
|
|
112
|
+
0, 0, 13.76, -13.76, // scale in Z by 13.76; translate in Z by -13.76 after scaling.
|
|
113
|
+
0, 0, 0, 1,
|
|
114
|
+
]);
|
|
115
|
+
});
|
|
116
|
+
it('supports 3D affine transformation, for coordinate system without C axis', () => {
|
|
117
|
+
const coordinateTransformations = [
|
|
118
|
+
{ type: 'scale', scale: [1, 1, 1] },
|
|
119
|
+
{
|
|
120
|
+
type: 'affine',
|
|
121
|
+
affine: [
|
|
122
|
+
[0, 0, 1, 0],
|
|
123
|
+
[0, 1, 0, 0],
|
|
124
|
+
[13.76, 0, 0, -13.76],
|
|
125
|
+
],
|
|
126
|
+
input: {
|
|
127
|
+
name: 'zyx',
|
|
128
|
+
axes: [
|
|
129
|
+
{ type: 'space', name: 'z' },
|
|
130
|
+
{ type: 'space', name: 'y' },
|
|
131
|
+
{ type: 'space', name: 'x' },
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
output: {
|
|
135
|
+
name: 'global',
|
|
136
|
+
axes: [
|
|
137
|
+
// Notice: swap-axes (ZYX to XYZ).
|
|
138
|
+
{ type: 'space', name: 'x' },
|
|
139
|
+
{ type: 'space', name: 'y' },
|
|
140
|
+
{ type: 'space', name: 'z' },
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
const axes = [
|
|
146
|
+
{ type: 'space', name: 'z' },
|
|
147
|
+
{ type: 'space', name: 'y' },
|
|
148
|
+
{ type: 'space', name: 'x' },
|
|
149
|
+
];
|
|
150
|
+
expect(coordinateTransformationsToMatrix(coordinateTransformations, axes)).toEqual([
|
|
151
|
+
1, 0, 0, 0,
|
|
152
|
+
0, 1, 0, 0,
|
|
153
|
+
0, 0, 13.76, -13.76, // scale in Z by 13.76; translate in Z by -13.76 after scaling.
|
|
154
|
+
0, 0, 0, 1,
|
|
155
|
+
]);
|
|
156
|
+
});
|
|
38
157
|
});
|
|
39
158
|
describe('normalizeCoordinateTransformations', () => {
|
|
40
159
|
it('does nothing for OME-NGFF v0.4 input', () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitessce/spatial-utils",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.8",
|
|
4
4
|
"author": "HIDIVE Lab at HMS",
|
|
5
5
|
"homepage": "http://vitessce.io",
|
|
6
6
|
"repository": {
|
|
@@ -20,11 +20,12 @@
|
|
|
20
20
|
"zarrita": "0.5.2",
|
|
21
21
|
"math.gl": "^3.5.6",
|
|
22
22
|
"mathjs": "^9.2.0",
|
|
23
|
-
"@vitessce/gl": "3.6.
|
|
24
|
-
"@vitessce/utils": "3.6.
|
|
25
|
-
"@vitessce/zarr-utils": "3.6.
|
|
26
|
-
"@vitessce/constants-internal": "3.6.
|
|
27
|
-
"@vitessce/globals": "3.6.
|
|
23
|
+
"@vitessce/gl": "3.6.8",
|
|
24
|
+
"@vitessce/utils": "3.6.8",
|
|
25
|
+
"@vitessce/zarr-utils": "3.6.8",
|
|
26
|
+
"@vitessce/constants-internal": "3.6.8",
|
|
27
|
+
"@vitessce/globals": "3.6.8",
|
|
28
|
+
"@vitessce/error": "3.6.8"
|
|
28
29
|
},
|
|
29
30
|
"scripts": {
|
|
30
31
|
"bundle": "pnpm exec vite build -c ../../../scripts/vite.config.js",
|
package/src/load-ome-zarr.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { viv } from '@vitessce/gl';
|
|
2
2
|
import { open as zarrOpen } from 'zarrita';
|
|
3
3
|
import { createZarrArrayAdapter } from '@vitessce/zarr-utils';
|
|
4
|
+
import { ZarrNodeNotFoundError } from '@vitessce/error';
|
|
4
5
|
|
|
5
6
|
function prevPowerOf2(x) {
|
|
6
7
|
return 2 ** Math.floor(Math.log2(x));
|
|
@@ -29,7 +30,16 @@ function isAxis(axisOrLabel) {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
async function loadMultiscales(root) {
|
|
32
|
-
|
|
33
|
+
let rootAttrs;
|
|
34
|
+
try {
|
|
35
|
+
rootAttrs = (await zarrOpen(root)).attrs;
|
|
36
|
+
} catch (e) {
|
|
37
|
+
if (e.name === 'NodeNotFoundError') {
|
|
38
|
+
throw new ZarrNodeNotFoundError(root.path);
|
|
39
|
+
}
|
|
40
|
+
// Re-throw the error if it is not a NodeNotFoundError.
|
|
41
|
+
throw e;
|
|
42
|
+
}
|
|
33
43
|
|
|
34
44
|
let paths = ['0'];
|
|
35
45
|
// Default axes used for v0.1 and v0.2.
|
|
@@ -44,6 +54,9 @@ async function loadMultiscales(root) {
|
|
|
44
54
|
labels = axes;
|
|
45
55
|
}
|
|
46
56
|
}
|
|
57
|
+
} else {
|
|
58
|
+
// TODO: Relax this after https://github.com/vitessce/vitessce/issues/2199
|
|
59
|
+
throw new Error('Expected the Zarr store to contain OME-NGFF multiscales metadata.');
|
|
47
60
|
}
|
|
48
61
|
|
|
49
62
|
const data = paths
|
package/src/spatial.js
CHANGED
|
@@ -400,6 +400,42 @@ export function getNgffAxesForTiff(dimOrder) {
|
|
|
400
400
|
}));
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
+
/**
|
|
404
|
+
* Get a 4x4 matrix that swaps axes.
|
|
405
|
+
* TODO: add unit tests.
|
|
406
|
+
* @param {("x"|"y"|"z")[]} inputAxes
|
|
407
|
+
* @param {("x"|"y"|"z")[]} outputAxes
|
|
408
|
+
* @return {number[][]} 4x4 matrix that swaps axes.
|
|
409
|
+
*/
|
|
410
|
+
export function getSwapAxesMatrix(inputAxes, outputAxes) {
|
|
411
|
+
const size = inputAxes.length;
|
|
412
|
+
if (!(size === 2 || size === 3)) {
|
|
413
|
+
throw new Error(`Expected input and output axes to have 2 or 3 elements, got ${size}.`);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Initialize a 4x4 matrix.
|
|
417
|
+
const matrix = [
|
|
418
|
+
[0, 0, 0, 0],
|
|
419
|
+
[0, 0, 0, 0],
|
|
420
|
+
[0, 0, (size === 2 ? 1 : 0), 0], // In 2D, leave Z unchanged.
|
|
421
|
+
[0, 0, 0, 1],
|
|
422
|
+
];
|
|
423
|
+
|
|
424
|
+
// Fill linear transformation (top-left 3x3)
|
|
425
|
+
// Iterate over output rows.
|
|
426
|
+
for (let outRow = 0; outRow < size; outRow++) {
|
|
427
|
+
// Get the axis name for the current output row.
|
|
428
|
+
const axis = outputAxes[outRow];
|
|
429
|
+
const inCol = inputAxes.indexOf(axis);
|
|
430
|
+
if (inCol === -1) {
|
|
431
|
+
throw new Error(`Axis "${axis}" not found in inputAxes`);
|
|
432
|
+
}
|
|
433
|
+
matrix[outRow][inCol] = 1;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return matrix;
|
|
437
|
+
}
|
|
438
|
+
|
|
403
439
|
/**
|
|
404
440
|
* Convert an array of coordinateTransformations objects to a 16-element
|
|
405
441
|
* plain JS array using Matrix4 linear algebra transformation functions.
|
|
@@ -417,6 +453,64 @@ export function coordinateTransformationsToMatrix(coordinateTransformations, axe
|
|
|
417
453
|
// Apply each transformation sequentially and in order according to the OME-NGFF v0.4 spec.
|
|
418
454
|
// Reference: https://ngff.openmicroscopy.org/0.4/#trafo-md
|
|
419
455
|
coordinateTransformations.forEach((transform) => {
|
|
456
|
+
if (transform.type === 'affine') {
|
|
457
|
+
const { affine, input, output } = transform;
|
|
458
|
+
|
|
459
|
+
// This is needed since axes can include "c" for example.
|
|
460
|
+
const excludeEntries = input.axes
|
|
461
|
+
.map((axis, i) => ([axis, i]))
|
|
462
|
+
// eslint-disable-next-line no-unused-vars
|
|
463
|
+
.filter(([axis, i]) => axis.type !== 'space')
|
|
464
|
+
// eslint-disable-next-line no-unused-vars
|
|
465
|
+
.map(([axis, i]) => i);
|
|
466
|
+
|
|
467
|
+
// We only want to keep the affine transformation entries for the spatial axes.
|
|
468
|
+
const filteredAffine = affine
|
|
469
|
+
.filter((_, i) => !excludeEntries.includes(i))
|
|
470
|
+
.map(row => row.filter((_, i) => !excludeEntries.includes(i)));
|
|
471
|
+
|
|
472
|
+
// TODO: if there was some affine transformation for the "c" axis, warn/error.
|
|
473
|
+
|
|
474
|
+
const spatialInputAxes = input.axes.filter(axis => axis.type === 'space');
|
|
475
|
+
const spatialOutputAxes = output.axes.filter(axis => axis.type === 'space');
|
|
476
|
+
|
|
477
|
+
const inputAxisNames = spatialInputAxes.map(axis => axis.name);
|
|
478
|
+
const outputAxisNames = spatialOutputAxes.map(axis => axis.name);
|
|
479
|
+
|
|
480
|
+
if (spatialInputAxes.length === 3) { // 3D case
|
|
481
|
+
const nextMat = (new Matrix4()).fromArray([
|
|
482
|
+
...filteredAffine[0],
|
|
483
|
+
...filteredAffine[1],
|
|
484
|
+
...filteredAffine[2],
|
|
485
|
+
0, 0, 0, 1,
|
|
486
|
+
]);
|
|
487
|
+
mat = mat.multiplyLeft(nextMat);
|
|
488
|
+
|
|
489
|
+
if (!isEqual(inputAxisNames, outputAxisNames)) {
|
|
490
|
+
// Handle 3D axis swapping.
|
|
491
|
+
const swapMatNested = getSwapAxesMatrix(inputAxisNames, outputAxisNames);
|
|
492
|
+
const swapMat = (new Matrix4()).fromArray(swapMatNested.flat());
|
|
493
|
+
mat = mat.multiplyLeft(swapMat);
|
|
494
|
+
}
|
|
495
|
+
} else if (spatialOutputAxes.length === 2) { // 2D case
|
|
496
|
+
const nextMat = (new Matrix4()).fromArray([
|
|
497
|
+
...filteredAffine[0],
|
|
498
|
+
...filteredAffine[1],
|
|
499
|
+
0, 0, 1, 0,
|
|
500
|
+
0, 0, 0, 1,
|
|
501
|
+
]);
|
|
502
|
+
mat = mat.multiplyLeft(nextMat);
|
|
503
|
+
|
|
504
|
+
if (!isEqual(inputAxisNames, outputAxisNames)) {
|
|
505
|
+
// Handle 2D axis swapping.
|
|
506
|
+
const swapMatNested = getSwapAxesMatrix(inputAxisNames, outputAxisNames);
|
|
507
|
+
const swapMat = (new Matrix4()).fromArray(swapMatNested.flat());
|
|
508
|
+
mat = mat.multiplyLeft(swapMat);
|
|
509
|
+
}
|
|
510
|
+
} else {
|
|
511
|
+
throw new Error('Affine transformation must have 2 or 3 rows.');
|
|
512
|
+
}
|
|
513
|
+
}
|
|
420
514
|
if (transform.type === 'translation') {
|
|
421
515
|
const { translation: axisOrderedTranslation } = transform;
|
|
422
516
|
if (axisOrderedTranslation.length !== axes.length) {
|
|
@@ -431,6 +525,8 @@ export function coordinateTransformationsToMatrix(coordinateTransformations, axe
|
|
|
431
525
|
));
|
|
432
526
|
const nextMat = (new Matrix4()).translate(xyzTranslation);
|
|
433
527
|
mat = mat.multiplyLeft(nextMat);
|
|
528
|
+
|
|
529
|
+
// TODO: error if the user tries to use a translation on the "c" axis.
|
|
434
530
|
}
|
|
435
531
|
if (transform.type === 'scale') {
|
|
436
532
|
const { scale: axisOrderedScale } = transform;
|
|
@@ -447,6 +543,8 @@ export function coordinateTransformationsToMatrix(coordinateTransformations, axe
|
|
|
447
543
|
));
|
|
448
544
|
const nextMat = (new Matrix4()).scale(xyzScale);
|
|
449
545
|
mat = mat.multiplyLeft(nextMat);
|
|
546
|
+
|
|
547
|
+
// TODO: error if the user tries to use a scale on the "c" axis.
|
|
450
548
|
}
|
|
451
549
|
});
|
|
452
550
|
}
|
|
@@ -510,12 +608,23 @@ export function normalizeCoordinateTransformations(coordinateTransformations, da
|
|
|
510
608
|
scale: transform.scale,
|
|
511
609
|
};
|
|
512
610
|
}
|
|
611
|
+
if (type === 'affine') {
|
|
612
|
+
return {
|
|
613
|
+
type,
|
|
614
|
+
affine: transform.affine,
|
|
615
|
+
// Affine transformations can potentially swap axes,
|
|
616
|
+
// so we need to include the input and output axes.
|
|
617
|
+
input: transform.input,
|
|
618
|
+
output: transform.output,
|
|
619
|
+
};
|
|
620
|
+
}
|
|
513
621
|
if (type === 'identity') {
|
|
514
622
|
return { type };
|
|
515
623
|
}
|
|
516
624
|
if (type === 'sequence') {
|
|
517
625
|
return normalizeCoordinateTransformations(transform.transformations, datasets);
|
|
518
626
|
}
|
|
627
|
+
|
|
519
628
|
log.warn(`Coordinate transformation type "${type}" is not supported.`);
|
|
520
629
|
}
|
|
521
630
|
// Assume it was already an old-style (NGFF v0.4) coordinate transformation.
|
package/src/spatial.test.js
CHANGED
|
@@ -2,6 +2,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
2
2
|
import {
|
|
3
3
|
coordinateTransformationsToMatrix,
|
|
4
4
|
normalizeCoordinateTransformations,
|
|
5
|
+
getSwapAxesMatrix,
|
|
5
6
|
} from './spatial.js';
|
|
6
7
|
|
|
7
8
|
const defaultAxes = [
|
|
@@ -40,6 +41,127 @@ describe('Spatial.js', () => {
|
|
|
40
41
|
0, 0, 0, 1,
|
|
41
42
|
]);
|
|
42
43
|
});
|
|
44
|
+
|
|
45
|
+
it('swap axes works for 2D', () => {
|
|
46
|
+
const inputAxes = ['x', 'y'];
|
|
47
|
+
const outputAxes = ['y', 'x'];
|
|
48
|
+
const swapMatrix = getSwapAxesMatrix(inputAxes, outputAxes);
|
|
49
|
+
expect(swapMatrix.flat()).toEqual([
|
|
50
|
+
0, 1, 0, 0,
|
|
51
|
+
1, 0, 0, 0,
|
|
52
|
+
0, 0, 1, 0,
|
|
53
|
+
0, 0, 0, 1,
|
|
54
|
+
]);
|
|
55
|
+
});
|
|
56
|
+
it('swap axes works for 3D', () => {
|
|
57
|
+
const inputAxes = ['x', 'y', 'z'];
|
|
58
|
+
const outputAxes = ['z', 'y', 'x'];
|
|
59
|
+
const swapMatrix = getSwapAxesMatrix(inputAxes, outputAxes);
|
|
60
|
+
expect(swapMatrix.flat()).toEqual([
|
|
61
|
+
0, 0, 1, 0,
|
|
62
|
+
0, 1, 0, 0,
|
|
63
|
+
1, 0, 0, 0,
|
|
64
|
+
0, 0, 0, 1,
|
|
65
|
+
]);
|
|
66
|
+
});
|
|
67
|
+
it('swap axes works for 3D, different order', () => {
|
|
68
|
+
const inputAxes = ['x', 'z', 'y'];
|
|
69
|
+
const outputAxes = ['x', 'y', 'z'];
|
|
70
|
+
const swapMatrix = getSwapAxesMatrix(inputAxes, outputAxes);
|
|
71
|
+
expect(swapMatrix.flat()).toEqual([
|
|
72
|
+
1, 0, 0, 0,
|
|
73
|
+
0, 0, 1, 0,
|
|
74
|
+
0, 1, 0, 0,
|
|
75
|
+
0, 0, 0, 1,
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('supports 3D affine transformation, for coordinate system with C axis', () => {
|
|
80
|
+
const coordinateTransformations = [
|
|
81
|
+
{ type: 'scale', scale: [1, 1, 1, 1] },
|
|
82
|
+
{
|
|
83
|
+
type: 'affine',
|
|
84
|
+
affine: [
|
|
85
|
+
[1, 0, 0, 0, 0],
|
|
86
|
+
[0, 0, 0, 1, 0],
|
|
87
|
+
[0, 0, 1, 0, 0],
|
|
88
|
+
[0, 13.76, 0, 0, -13.76],
|
|
89
|
+
],
|
|
90
|
+
input: {
|
|
91
|
+
name: 'czyx',
|
|
92
|
+
axes: [
|
|
93
|
+
{ type: 'channel', name: 'c' },
|
|
94
|
+
{ type: 'space', name: 'x' },
|
|
95
|
+
{ type: 'space', name: 'y' },
|
|
96
|
+
{ type: 'space', name: 'z' },
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
output: {
|
|
100
|
+
name: 'global',
|
|
101
|
+
axes: [
|
|
102
|
+
// Notice: swap-axes (CXYZ to CZYX).
|
|
103
|
+
{ type: 'channel', name: 'c' },
|
|
104
|
+
{ type: 'space', name: 'z' },
|
|
105
|
+
{ type: 'space', name: 'y' },
|
|
106
|
+
{ type: 'space', name: 'x' },
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
const axes = [
|
|
112
|
+
{ type: 'channel', name: 'c' },
|
|
113
|
+
{ type: 'space', name: 'x' },
|
|
114
|
+
{ type: 'space', name: 'y' },
|
|
115
|
+
{ type: 'space', name: 'z' },
|
|
116
|
+
];
|
|
117
|
+
expect(coordinateTransformationsToMatrix(coordinateTransformations, axes)).toEqual([
|
|
118
|
+
1, 0, 0, 0,
|
|
119
|
+
0, 1, 0, 0,
|
|
120
|
+
0, 0, 13.76, -13.76, // scale in Z by 13.76; translate in Z by -13.76 after scaling.
|
|
121
|
+
0, 0, 0, 1,
|
|
122
|
+
]);
|
|
123
|
+
});
|
|
124
|
+
it('supports 3D affine transformation, for coordinate system without C axis', () => {
|
|
125
|
+
const coordinateTransformations = [
|
|
126
|
+
{ type: 'scale', scale: [1, 1, 1] },
|
|
127
|
+
{
|
|
128
|
+
type: 'affine',
|
|
129
|
+
affine: [
|
|
130
|
+
[0, 0, 1, 0],
|
|
131
|
+
[0, 1, 0, 0],
|
|
132
|
+
[13.76, 0, 0, -13.76],
|
|
133
|
+
],
|
|
134
|
+
input: {
|
|
135
|
+
name: 'zyx',
|
|
136
|
+
axes: [
|
|
137
|
+
{ type: 'space', name: 'z' },
|
|
138
|
+
{ type: 'space', name: 'y' },
|
|
139
|
+
{ type: 'space', name: 'x' },
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
output: {
|
|
143
|
+
name: 'global',
|
|
144
|
+
axes: [
|
|
145
|
+
// Notice: swap-axes (ZYX to XYZ).
|
|
146
|
+
{ type: 'space', name: 'x' },
|
|
147
|
+
{ type: 'space', name: 'y' },
|
|
148
|
+
{ type: 'space', name: 'z' },
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
const axes = [
|
|
154
|
+
{ type: 'space', name: 'z' },
|
|
155
|
+
{ type: 'space', name: 'y' },
|
|
156
|
+
{ type: 'space', name: 'x' },
|
|
157
|
+
];
|
|
158
|
+
expect(coordinateTransformationsToMatrix(coordinateTransformations, axes)).toEqual([
|
|
159
|
+
1, 0, 0, 0,
|
|
160
|
+
0, 1, 0, 0,
|
|
161
|
+
0, 0, 13.76, -13.76, // scale in Z by 13.76; translate in Z by -13.76 after scaling.
|
|
162
|
+
0, 0, 0, 1,
|
|
163
|
+
]);
|
|
164
|
+
});
|
|
43
165
|
});
|
|
44
166
|
describe('normalizeCoordinateTransformations', () => {
|
|
45
167
|
it('does nothing for OME-NGFF v0.4 input', () => {
|