@fideus-labs/fidnii 0.4.0 → 0.5.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/OMEZarrNVImage.d.ts.map +1 -1
- package/dist/OMEZarrNVImage.js +59 -30
- package/dist/OMEZarrNVImage.js.map +1 -1
- package/dist/fromTiff.d.ts +22 -1
- package/dist/fromTiff.d.ts.map +1 -1
- package/dist/fromTiff.js +9 -3
- package/dist/fromTiff.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/affine.d.ts +12 -2
- package/dist/utils/affine.d.ts.map +1 -1
- package/dist/utils/affine.js +15 -3
- package/dist/utils/affine.js.map +1 -1
- package/dist/utils/orientation.d.ts +157 -0
- package/dist/utils/orientation.d.ts.map +1 -0
- package/dist/utils/orientation.js +225 -0
- package/dist/utils/orientation.js.map +1 -0
- package/package.json +5 -5
- package/src/OMEZarrNVImage.ts +74 -30
- package/src/fromTiff.ts +32 -4
- package/src/index.ts +12 -1
- package/src/utils/affine.ts +16 -3
- package/src/utils/orientation.ts +292 -0
package/dist/utils/affine.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: Copyright (c) Fideus Labs LLC
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
import { mat4 } from "gl-matrix";
|
|
4
|
+
import { applyOrientationToAffine } from "./orientation.js";
|
|
4
5
|
/**
|
|
5
6
|
* Create a 4x4 affine transformation matrix from OME-Zarr scale and translation.
|
|
6
7
|
*
|
|
@@ -60,11 +61,22 @@ export function createAffineFromOMEZarr(scale, translation) {
|
|
|
60
61
|
/**
|
|
61
62
|
* Create an affine matrix from an NgffImage.
|
|
62
63
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
64
|
+
* If the image has RFC-4 anatomical orientation metadata
|
|
65
|
+
* (`axesOrientations`), the affine column vectors and translations
|
|
66
|
+
* are sign-flipped so the matrix encodes direction relative to
|
|
67
|
+
* the NIfTI RAS+ convention. This allows NiiVue's `calculateRAS()`
|
|
68
|
+
* to correctly determine the anatomical layout.
|
|
69
|
+
*
|
|
70
|
+
* When no orientation metadata is present, the matrix is identical
|
|
71
|
+
* to a plain scale + translation affine (backward-compatible).
|
|
72
|
+
*
|
|
73
|
+
* @param ngffImage - The NgffImage containing scale, translation,
|
|
74
|
+
* and optional `axesOrientations`
|
|
75
|
+
* @returns 4x4 affine matrix with orientation signs applied
|
|
65
76
|
*/
|
|
66
77
|
export function createAffineFromNgffImage(ngffImage) {
|
|
67
|
-
|
|
78
|
+
const affine = createAffineFromOMEZarr(ngffImage.scale, ngffImage.translation);
|
|
79
|
+
return applyOrientationToAffine(affine, ngffImage.axesOrientations);
|
|
68
80
|
}
|
|
69
81
|
/**
|
|
70
82
|
* Convert an affine matrix to a flat array for NIfTI header.
|
package/dist/utils/affine.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"affine.js","sourceRoot":"","sources":["../../src/utils/affine.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,+BAA+B;AAG/B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,uBAAuB,CACrC,KAA6B,EAC7B,WAAmC;IAEnC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAA;IAE5B,qDAAqD;IACrD,8EAA8E;IAC9E,2EAA2E;IAE3E,8CAA8C;IAC9C,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC,CAAA;IAClC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC,CAAA;IAClC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC,CAAA;IAElC,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,IAAI,CAAC,CAAA;IAC9C,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,IAAI,CAAC,CAAA;IAC9C,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,IAAI,CAAC,CAAA;IAE9C,sBAAsB;IACtB,0EAA0E;IAC1E,oEAAoE;IAEpE,4DAA4D;IAC5D,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,CAAA;IACd,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACb,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACb,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IAEb,6DAA6D;IAC7D,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACb,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,CAAA;IACd,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACb,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IAEb,4DAA4D;IAC5D,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACb,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACb,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,CAAA;IACf,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;IAEd,wBAAwB;IACxB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,CAAA;IACf,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,CAAA;IACf,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,CAAA;IACf,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;IAEd,OAAO,MAAM,CAAA;AACf,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"affine.js","sourceRoot":"","sources":["../../src/utils/affine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { AnatomicalOrientation } from "@fideus-labs/ngff-zarr";
|
|
2
|
+
import type { mat4 } from "gl-matrix";
|
|
3
|
+
/**
|
|
4
|
+
* Mapping from an array axis orientation to the physical (RAS) row
|
|
5
|
+
* and sign it should occupy in the NIfTI affine.
|
|
6
|
+
*
|
|
7
|
+
* - `physicalRow`: which row of the 4x4 affine the scale/translation
|
|
8
|
+
* should be placed in (0 = R/L, 1 = A/P, 2 = S/I)
|
|
9
|
+
* - `sign`: `1` if the orientation is in the RAS+ direction,
|
|
10
|
+
* `-1` if opposite
|
|
11
|
+
*/
|
|
12
|
+
export interface OrientationMapping {
|
|
13
|
+
readonly physicalRow: 0 | 1 | 2;
|
|
14
|
+
readonly sign: 1 | -1;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Sign multipliers for each spatial axis, used to encode anatomical
|
|
18
|
+
* orientation into the NIfTI affine matrix.
|
|
19
|
+
*
|
|
20
|
+
* A value of `1` means the axis increases in the RAS+ direction
|
|
21
|
+
* (Right, Anterior, Superior). A value of `-1` means it increases
|
|
22
|
+
* in the opposite direction (Left, Posterior, Inferior).
|
|
23
|
+
*
|
|
24
|
+
* @deprecated Use {@link getOrientationMapping} for full permutation
|
|
25
|
+
* support. This interface only captures sign, not axis permutations.
|
|
26
|
+
*/
|
|
27
|
+
export interface OrientationSigns {
|
|
28
|
+
readonly x: 1 | -1;
|
|
29
|
+
readonly y: 1 | -1;
|
|
30
|
+
readonly z: 1 | -1;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get the orientation mapping (physical row + RAS sign) for a single
|
|
34
|
+
* axis orientation.
|
|
35
|
+
*
|
|
36
|
+
* For unknown/exotic orientations, returns `undefined`.
|
|
37
|
+
*
|
|
38
|
+
* @param orientation - The anatomical orientation for one axis
|
|
39
|
+
* @returns The physical row and sign, or `undefined` if not a standard
|
|
40
|
+
* L/R, A/P, or S/I orientation
|
|
41
|
+
*/
|
|
42
|
+
export declare function getOrientationInfo(orientation: AnatomicalOrientation): OrientationMapping | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Get orientation mappings for all three spatial axes.
|
|
45
|
+
*
|
|
46
|
+
* Each mapping tells you which physical (RAS) row the array axis
|
|
47
|
+
* maps to and what sign to apply. This supports both sign flips
|
|
48
|
+
* (e.g. LPS) and axis permutations (e.g. when the OME-Zarr y axis
|
|
49
|
+
* encodes S/I instead of A/P).
|
|
50
|
+
*
|
|
51
|
+
* When no orientation metadata is present, returns the identity
|
|
52
|
+
* mapping: x→row 0 sign +1, y→row 1 sign +1, z→row 2 sign +1.
|
|
53
|
+
*
|
|
54
|
+
* @param axesOrientations - Orientation metadata from
|
|
55
|
+
* `NgffImage.axesOrientations`, or `undefined`
|
|
56
|
+
* @returns Mappings for x, y, and z axes
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* // LPS data: x→row0 sign-1, y→row1 sign-1, z→row2 sign+1
|
|
61
|
+
* getOrientationMapping(LPS)
|
|
62
|
+
*
|
|
63
|
+
* // Permuted (mri.nii.gz): x→row0 sign-1, y→row2 sign-1, z→row1 sign+1
|
|
64
|
+
* getOrientationMapping(permutedOrientations)
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function getOrientationMapping(axesOrientations: Record<string, AnatomicalOrientation> | undefined): {
|
|
68
|
+
x: OrientationMapping;
|
|
69
|
+
y: OrientationMapping;
|
|
70
|
+
z: OrientationMapping;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Compute RAS+ sign multipliers from RFC-4 anatomical orientation metadata.
|
|
74
|
+
*
|
|
75
|
+
* For each spatial axis, determines whether the axis direction is aligned
|
|
76
|
+
* with (+1) or opposite to (-1) the NIfTI RAS+ convention:
|
|
77
|
+
* - x: positive = left-to-right (R), negative = right-to-left (L)
|
|
78
|
+
* - y: positive = posterior-to-anterior (A), negative = anterior-to-posterior (P)
|
|
79
|
+
* - z: positive = inferior-to-superior (S), negative = superior-to-inferior (I)
|
|
80
|
+
*
|
|
81
|
+
* Only the 6 standard L/R, A/P, I/S orientations are handled. Exotic
|
|
82
|
+
* orientations (dorsal/ventral, rostral/caudal, etc.) are treated as
|
|
83
|
+
* unknown and default to +1.
|
|
84
|
+
*
|
|
85
|
+
* **Note**: This function only returns sign information, not axis
|
|
86
|
+
* permutation. For full permutation support, use
|
|
87
|
+
* {@link getOrientationMapping} and {@link applyOrientationToAffine}.
|
|
88
|
+
*
|
|
89
|
+
* @param axesOrientations - Orientation metadata from `NgffImage.axesOrientations`,
|
|
90
|
+
* or `undefined` if no orientation metadata is present
|
|
91
|
+
* @returns Sign multipliers for x, y, and z axes
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* import { LPS, RAS } from "@fideus-labs/ngff-zarr"
|
|
96
|
+
*
|
|
97
|
+
* // LPS data: x and y are anti-RAS+
|
|
98
|
+
* getOrientationSigns(LPS)
|
|
99
|
+
* // => { x: -1, y: -1, z: 1 }
|
|
100
|
+
*
|
|
101
|
+
* // RAS data: all axes are RAS+
|
|
102
|
+
* getOrientationSigns(RAS)
|
|
103
|
+
* // => { x: 1, y: 1, z: 1 }
|
|
104
|
+
*
|
|
105
|
+
* // No orientation: defaults to all positive
|
|
106
|
+
* getOrientationSigns(undefined)
|
|
107
|
+
* // => { x: 1, y: 1, z: 1 }
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export declare function getOrientationSigns(axesOrientations: Record<string, AnatomicalOrientation> | undefined): OrientationSigns;
|
|
111
|
+
/**
|
|
112
|
+
* Apply anatomical orientation to an affine matrix in place.
|
|
113
|
+
*
|
|
114
|
+
* Builds a rotation/permutation matrix from the orientation metadata
|
|
115
|
+
* and applies it to the affine's 3x3 rotation/scale submatrix. This
|
|
116
|
+
* supports both simple sign flips (e.g. LPS where axes align with
|
|
117
|
+
* physical axes but directions differ) and full axis permutations
|
|
118
|
+
* (e.g. when OME-Zarr y axis encodes S/I instead of A/P).
|
|
119
|
+
*
|
|
120
|
+
* The input affine is expected to be a diagonal scale+translation
|
|
121
|
+
* matrix in gl-matrix column-major format, as produced by
|
|
122
|
+
* `createAffineFromOMEZarr()`:
|
|
123
|
+
*
|
|
124
|
+
* ```
|
|
125
|
+
* | sx 0 0 tx |
|
|
126
|
+
* | 0 sy 0 ty |
|
|
127
|
+
* | 0 0 sz tz |
|
|
128
|
+
* | 0 0 0 1 |
|
|
129
|
+
* ```
|
|
130
|
+
*
|
|
131
|
+
* **3x3 submatrix**: Each column's scale is placed in the row
|
|
132
|
+
* corresponding to the physical RAS axis the array axis maps to,
|
|
133
|
+
* with the appropriate sign:
|
|
134
|
+
*
|
|
135
|
+
* ```
|
|
136
|
+
* Column j (array axis j):
|
|
137
|
+
* row = physicalRow for axis j
|
|
138
|
+
* affine[j*4 + row] = sign * scale_j
|
|
139
|
+
* ```
|
|
140
|
+
*
|
|
141
|
+
* **Translation column**: Sign-flipped for LPS→RAS conversion but
|
|
142
|
+
* NOT row-permuted. This is because `itkImageToNgffImage` stores
|
|
143
|
+
* the ITK LPS origin values in array-axis-label order without
|
|
144
|
+
* transforming them through the direction matrix. The label
|
|
145
|
+
* assignment (x/y/z) follows the reversed ITK axis indices, so the
|
|
146
|
+
* physical meaning of each translation value matches its original
|
|
147
|
+
* LPS axis, regardless of axis permutation.
|
|
148
|
+
*
|
|
149
|
+
* When `axesOrientations` is `undefined`, the affine is left
|
|
150
|
+
* unchanged (backward-compatible identity mapping).
|
|
151
|
+
*
|
|
152
|
+
* @param affine - 4x4 affine matrix (column-major, modified in place)
|
|
153
|
+
* @param axesOrientations - Orientation metadata from `NgffImage.axesOrientations`
|
|
154
|
+
* @returns The same affine matrix (for chaining)
|
|
155
|
+
*/
|
|
156
|
+
export declare function applyOrientationToAffine(affine: mat4, axesOrientations: Record<string, AnatomicalOrientation> | undefined): mat4;
|
|
157
|
+
//# sourceMappingURL=orientation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orientation.d.ts","sourceRoot":"","sources":["../../src/utils/orientation.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AACnE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAErC;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC/B,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;CACtB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;IAClB,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;IAClB,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;CACnB;AAuBD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,qBAAqB,GACjC,kBAAkB,GAAG,SAAS,CAEhC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,qBAAqB,CACnC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,GAAG,SAAS,GAClE;IAAE,CAAC,EAAE,kBAAkB,CAAC;IAAC,CAAC,EAAE,kBAAkB,CAAC;IAAC,CAAC,EAAE,kBAAkB,CAAA;CAAE,CA2CzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,mBAAmB,CACjC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,GAAG,SAAS,GAClE,gBAAgB,CAOlB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,IAAI,EACZ,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,GAAG,SAAS,GAClE,IAAI,CAiDN"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) Fideus Labs LLC
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* Lookup table mapping each RFC-4 anatomical orientation string to
|
|
5
|
+
* its NIfTI RAS+ physical row and sign.
|
|
6
|
+
*
|
|
7
|
+
* RAS+ convention:
|
|
8
|
+
* - Row 0 (X): left-to-right (R+)
|
|
9
|
+
* - Row 1 (Y): posterior-to-anterior (A+)
|
|
10
|
+
* - Row 2 (Z): inferior-to-superior (S+)
|
|
11
|
+
*/
|
|
12
|
+
const ORIENTATION_INFO = {
|
|
13
|
+
// L/R pair → physical row 0
|
|
14
|
+
"left-to-right": { physicalRow: 0, sign: 1 },
|
|
15
|
+
"right-to-left": { physicalRow: 0, sign: -1 },
|
|
16
|
+
// A/P pair → physical row 1
|
|
17
|
+
"posterior-to-anterior": { physicalRow: 1, sign: 1 },
|
|
18
|
+
"anterior-to-posterior": { physicalRow: 1, sign: -1 },
|
|
19
|
+
// S/I pair → physical row 2
|
|
20
|
+
"inferior-to-superior": { physicalRow: 2, sign: 1 },
|
|
21
|
+
"superior-to-inferior": { physicalRow: 2, sign: -1 },
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Get the orientation mapping (physical row + RAS sign) for a single
|
|
25
|
+
* axis orientation.
|
|
26
|
+
*
|
|
27
|
+
* For unknown/exotic orientations, returns `undefined`.
|
|
28
|
+
*
|
|
29
|
+
* @param orientation - The anatomical orientation for one axis
|
|
30
|
+
* @returns The physical row and sign, or `undefined` if not a standard
|
|
31
|
+
* L/R, A/P, or S/I orientation
|
|
32
|
+
*/
|
|
33
|
+
export function getOrientationInfo(orientation) {
|
|
34
|
+
return ORIENTATION_INFO[orientation.value];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get orientation mappings for all three spatial axes.
|
|
38
|
+
*
|
|
39
|
+
* Each mapping tells you which physical (RAS) row the array axis
|
|
40
|
+
* maps to and what sign to apply. This supports both sign flips
|
|
41
|
+
* (e.g. LPS) and axis permutations (e.g. when the OME-Zarr y axis
|
|
42
|
+
* encodes S/I instead of A/P).
|
|
43
|
+
*
|
|
44
|
+
* When no orientation metadata is present, returns the identity
|
|
45
|
+
* mapping: x→row 0 sign +1, y→row 1 sign +1, z→row 2 sign +1.
|
|
46
|
+
*
|
|
47
|
+
* @param axesOrientations - Orientation metadata from
|
|
48
|
+
* `NgffImage.axesOrientations`, or `undefined`
|
|
49
|
+
* @returns Mappings for x, y, and z axes
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* // LPS data: x→row0 sign-1, y→row1 sign-1, z→row2 sign+1
|
|
54
|
+
* getOrientationMapping(LPS)
|
|
55
|
+
*
|
|
56
|
+
* // Permuted (mri.nii.gz): x→row0 sign-1, y→row2 sign-1, z→row1 sign+1
|
|
57
|
+
* getOrientationMapping(permutedOrientations)
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function getOrientationMapping(axesOrientations) {
|
|
61
|
+
const defaultMapping = {
|
|
62
|
+
x: { physicalRow: 0, sign: 1 },
|
|
63
|
+
y: { physicalRow: 1, sign: 1 },
|
|
64
|
+
z: { physicalRow: 2, sign: 1 },
|
|
65
|
+
};
|
|
66
|
+
if (!axesOrientations) {
|
|
67
|
+
return defaultMapping;
|
|
68
|
+
}
|
|
69
|
+
const xOrientation = axesOrientations.x ?? axesOrientations.X;
|
|
70
|
+
const yOrientation = axesOrientations.y ?? axesOrientations.Y;
|
|
71
|
+
const zOrientation = axesOrientations.z ?? axesOrientations.Z;
|
|
72
|
+
const mapping = {
|
|
73
|
+
x: (xOrientation ? getOrientationInfo(xOrientation) : undefined) ??
|
|
74
|
+
defaultMapping.x,
|
|
75
|
+
y: (yOrientation ? getOrientationInfo(yOrientation) : undefined) ??
|
|
76
|
+
defaultMapping.y,
|
|
77
|
+
z: (zOrientation ? getOrientationInfo(zOrientation) : undefined) ??
|
|
78
|
+
defaultMapping.z,
|
|
79
|
+
};
|
|
80
|
+
// Validate that each physicalRow is used exactly once to prevent
|
|
81
|
+
// degenerate affine matrices where columns overwrite each other
|
|
82
|
+
const rowsUsed = new Set([
|
|
83
|
+
mapping.x.physicalRow,
|
|
84
|
+
mapping.y.physicalRow,
|
|
85
|
+
mapping.z.physicalRow,
|
|
86
|
+
]);
|
|
87
|
+
if (rowsUsed.size !== 3) {
|
|
88
|
+
console.warn("[fidnii] Invalid orientation metadata: multiple axes map to the same physical row. Falling back to identity mapping.");
|
|
89
|
+
return defaultMapping;
|
|
90
|
+
}
|
|
91
|
+
return mapping;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Compute RAS+ sign multipliers from RFC-4 anatomical orientation metadata.
|
|
95
|
+
*
|
|
96
|
+
* For each spatial axis, determines whether the axis direction is aligned
|
|
97
|
+
* with (+1) or opposite to (-1) the NIfTI RAS+ convention:
|
|
98
|
+
* - x: positive = left-to-right (R), negative = right-to-left (L)
|
|
99
|
+
* - y: positive = posterior-to-anterior (A), negative = anterior-to-posterior (P)
|
|
100
|
+
* - z: positive = inferior-to-superior (S), negative = superior-to-inferior (I)
|
|
101
|
+
*
|
|
102
|
+
* Only the 6 standard L/R, A/P, I/S orientations are handled. Exotic
|
|
103
|
+
* orientations (dorsal/ventral, rostral/caudal, etc.) are treated as
|
|
104
|
+
* unknown and default to +1.
|
|
105
|
+
*
|
|
106
|
+
* **Note**: This function only returns sign information, not axis
|
|
107
|
+
* permutation. For full permutation support, use
|
|
108
|
+
* {@link getOrientationMapping} and {@link applyOrientationToAffine}.
|
|
109
|
+
*
|
|
110
|
+
* @param axesOrientations - Orientation metadata from `NgffImage.axesOrientations`,
|
|
111
|
+
* or `undefined` if no orientation metadata is present
|
|
112
|
+
* @returns Sign multipliers for x, y, and z axes
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```typescript
|
|
116
|
+
* import { LPS, RAS } from "@fideus-labs/ngff-zarr"
|
|
117
|
+
*
|
|
118
|
+
* // LPS data: x and y are anti-RAS+
|
|
119
|
+
* getOrientationSigns(LPS)
|
|
120
|
+
* // => { x: -1, y: -1, z: 1 }
|
|
121
|
+
*
|
|
122
|
+
* // RAS data: all axes are RAS+
|
|
123
|
+
* getOrientationSigns(RAS)
|
|
124
|
+
* // => { x: 1, y: 1, z: 1 }
|
|
125
|
+
*
|
|
126
|
+
* // No orientation: defaults to all positive
|
|
127
|
+
* getOrientationSigns(undefined)
|
|
128
|
+
* // => { x: 1, y: 1, z: 1 }
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export function getOrientationSigns(axesOrientations) {
|
|
132
|
+
const mapping = getOrientationMapping(axesOrientations);
|
|
133
|
+
return {
|
|
134
|
+
x: mapping.x.sign,
|
|
135
|
+
y: mapping.y.sign,
|
|
136
|
+
z: mapping.z.sign,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Apply anatomical orientation to an affine matrix in place.
|
|
141
|
+
*
|
|
142
|
+
* Builds a rotation/permutation matrix from the orientation metadata
|
|
143
|
+
* and applies it to the affine's 3x3 rotation/scale submatrix. This
|
|
144
|
+
* supports both simple sign flips (e.g. LPS where axes align with
|
|
145
|
+
* physical axes but directions differ) and full axis permutations
|
|
146
|
+
* (e.g. when OME-Zarr y axis encodes S/I instead of A/P).
|
|
147
|
+
*
|
|
148
|
+
* The input affine is expected to be a diagonal scale+translation
|
|
149
|
+
* matrix in gl-matrix column-major format, as produced by
|
|
150
|
+
* `createAffineFromOMEZarr()`:
|
|
151
|
+
*
|
|
152
|
+
* ```
|
|
153
|
+
* | sx 0 0 tx |
|
|
154
|
+
* | 0 sy 0 ty |
|
|
155
|
+
* | 0 0 sz tz |
|
|
156
|
+
* | 0 0 0 1 |
|
|
157
|
+
* ```
|
|
158
|
+
*
|
|
159
|
+
* **3x3 submatrix**: Each column's scale is placed in the row
|
|
160
|
+
* corresponding to the physical RAS axis the array axis maps to,
|
|
161
|
+
* with the appropriate sign:
|
|
162
|
+
*
|
|
163
|
+
* ```
|
|
164
|
+
* Column j (array axis j):
|
|
165
|
+
* row = physicalRow for axis j
|
|
166
|
+
* affine[j*4 + row] = sign * scale_j
|
|
167
|
+
* ```
|
|
168
|
+
*
|
|
169
|
+
* **Translation column**: Sign-flipped for LPS→RAS conversion but
|
|
170
|
+
* NOT row-permuted. This is because `itkImageToNgffImage` stores
|
|
171
|
+
* the ITK LPS origin values in array-axis-label order without
|
|
172
|
+
* transforming them through the direction matrix. The label
|
|
173
|
+
* assignment (x/y/z) follows the reversed ITK axis indices, so the
|
|
174
|
+
* physical meaning of each translation value matches its original
|
|
175
|
+
* LPS axis, regardless of axis permutation.
|
|
176
|
+
*
|
|
177
|
+
* When `axesOrientations` is `undefined`, the affine is left
|
|
178
|
+
* unchanged (backward-compatible identity mapping).
|
|
179
|
+
*
|
|
180
|
+
* @param affine - 4x4 affine matrix (column-major, modified in place)
|
|
181
|
+
* @param axesOrientations - Orientation metadata from `NgffImage.axesOrientations`
|
|
182
|
+
* @returns The same affine matrix (for chaining)
|
|
183
|
+
*/
|
|
184
|
+
export function applyOrientationToAffine(affine, axesOrientations) {
|
|
185
|
+
if (!axesOrientations) {
|
|
186
|
+
return affine;
|
|
187
|
+
}
|
|
188
|
+
const mapping = getOrientationMapping(axesOrientations);
|
|
189
|
+
// Extract the current diagonal scale and translation values
|
|
190
|
+
// (the input affine is expected to be diagonal from createAffineFromOMEZarr)
|
|
191
|
+
const sx = affine[0];
|
|
192
|
+
const sy = affine[5];
|
|
193
|
+
const sz = affine[10];
|
|
194
|
+
const tx = affine[12];
|
|
195
|
+
const ty = affine[13];
|
|
196
|
+
const tz = affine[14];
|
|
197
|
+
// Clear the 3x3 submatrix (translation will be overwritten below)
|
|
198
|
+
// Column 0
|
|
199
|
+
affine[0] = 0;
|
|
200
|
+
affine[1] = 0;
|
|
201
|
+
affine[2] = 0;
|
|
202
|
+
// Column 1
|
|
203
|
+
affine[4] = 0;
|
|
204
|
+
affine[5] = 0;
|
|
205
|
+
affine[6] = 0;
|
|
206
|
+
// Column 2
|
|
207
|
+
affine[8] = 0;
|
|
208
|
+
affine[9] = 0;
|
|
209
|
+
affine[10] = 0;
|
|
210
|
+
// Place each axis' scale into the correct physical row.
|
|
211
|
+
// gl-matrix is column-major: index = col * 4 + row
|
|
212
|
+
// Column 0 (array x axis): place sx at physicalRow for x
|
|
213
|
+
affine[0 + mapping.x.physicalRow] = mapping.x.sign * sx;
|
|
214
|
+
// Column 1 (array y axis): place sy at physicalRow for y
|
|
215
|
+
affine[4 + mapping.y.physicalRow] = mapping.y.sign * sy;
|
|
216
|
+
// Column 2 (array z axis): place sz at physicalRow for z
|
|
217
|
+
affine[8 + mapping.z.physicalRow] = mapping.z.sign * sz;
|
|
218
|
+
// Translation: sign-flip for LPS→RAS but keep at original row
|
|
219
|
+
// positions (x→row 0, y→row 1, z→row 2). See JSDoc for why.
|
|
220
|
+
affine[12] = mapping.x.sign * tx;
|
|
221
|
+
affine[13] = mapping.y.sign * ty;
|
|
222
|
+
affine[14] = mapping.z.sign * tz;
|
|
223
|
+
return affine;
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=orientation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orientation.js","sourceRoot":"","sources":["../../src/utils/orientation.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fideus-labs/fidnii",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Render OME-Zarr images in NiiVue with progressive multi-resolution loading",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,18 +16,18 @@
|
|
|
16
16
|
"src"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@fideus-labs/fiff": "^0.
|
|
20
|
-
"@fideus-labs/ngff-zarr": "^0.
|
|
19
|
+
"@fideus-labs/fiff": "^0.5.2",
|
|
20
|
+
"@fideus-labs/ngff-zarr": "^0.12.0",
|
|
21
21
|
"@itk-wasm/downsample": "^1.8.1",
|
|
22
|
-
"lru-cache": "^11.1.0",
|
|
23
22
|
"@niivue/niivue": "^0.67.0",
|
|
24
23
|
"gl-matrix": "^3.4.4",
|
|
24
|
+
"lru-cache": "^11.1.0",
|
|
25
25
|
"nifti-reader-js": "^0.8.0",
|
|
26
26
|
"zarrita": "^0.6.1",
|
|
27
27
|
"zod": "^3.25.76"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@fideus-labs/fizarrita": "^1.
|
|
30
|
+
"@fideus-labs/fizarrita": "^1.4.0",
|
|
31
31
|
"@fideus-labs/worker-pool": "^1.0.0",
|
|
32
32
|
"@playwright/test": "^1.58.1",
|
|
33
33
|
"@types/node": "^20.19.30",
|
package/src/OMEZarrNVImage.ts
CHANGED
|
@@ -68,8 +68,14 @@ import {
|
|
|
68
68
|
affineToNiftiSrows,
|
|
69
69
|
calculateWorldBounds,
|
|
70
70
|
createAffineFromNgffImage,
|
|
71
|
+
createAffineFromOMEZarr,
|
|
71
72
|
} from "./utils/affine.js"
|
|
72
73
|
import { worldToPixel } from "./utils/coordinates.js"
|
|
74
|
+
import {
|
|
75
|
+
applyOrientationToAffine,
|
|
76
|
+
getOrientationMapping,
|
|
77
|
+
getOrientationSigns,
|
|
78
|
+
} from "./utils/orientation.js"
|
|
73
79
|
import {
|
|
74
80
|
boundsApproxEqual,
|
|
75
81
|
computeViewportBounds2D,
|
|
@@ -301,8 +307,14 @@ export class OMEZarrNVImage extends NVImage {
|
|
|
301
307
|
this._is2D = highResImage.dims.indexOf("z") === -1
|
|
302
308
|
this._flipY2D = options.flipY2D ?? true
|
|
303
309
|
|
|
304
|
-
// Calculate volume bounds from highest resolution for most accurate bounds
|
|
305
|
-
|
|
310
|
+
// Calculate volume bounds from highest resolution for most accurate bounds.
|
|
311
|
+
// Use the unadjusted affine (no orientation signs) because volume bounds
|
|
312
|
+
// live in OME-Zarr world space and drive internal clip-plane / viewport math.
|
|
313
|
+
// Orientation is only applied to the NIfTI affine passed to NiiVue.
|
|
314
|
+
const highResAffine = createAffineFromOMEZarr(
|
|
315
|
+
highResImage.scale,
|
|
316
|
+
highResImage.translation,
|
|
317
|
+
)
|
|
306
318
|
const highResShape = getVolumeShape(highResImage)
|
|
307
319
|
this._volumeBounds = calculateWorldBounds(highResAffine, highResShape)
|
|
308
320
|
|
|
@@ -400,13 +412,27 @@ export class OMEZarrNVImage extends NVImage {
|
|
|
400
412
|
// Placeholder pixel dimensions
|
|
401
413
|
hdr.pixDims = [1, 1, 1, 1, 0, 0, 0, 0]
|
|
402
414
|
|
|
403
|
-
// Placeholder affine (
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
415
|
+
// Placeholder affine (unit scale with orientation permutation/signs)
|
|
416
|
+
// This is replaced by real data when loadResolutionLevel completes,
|
|
417
|
+
// but having a reasonable placeholder avoids rendering glitches during
|
|
418
|
+
// the initial frame.
|
|
419
|
+
const mapping = getOrientationMapping(
|
|
420
|
+
this.multiscales.images[0]?.axesOrientations,
|
|
421
|
+
)
|
|
422
|
+
const placeholderAffine = [
|
|
423
|
+
[0, 0, 0, 0],
|
|
424
|
+
[0, 0, 0, 0],
|
|
425
|
+
[0, 0, 0, 0],
|
|
408
426
|
[0, 0, 0, 1],
|
|
409
427
|
]
|
|
428
|
+
placeholderAffine[mapping.x.physicalRow][0] = mapping.x.sign
|
|
429
|
+
let ySign = mapping.y.sign
|
|
430
|
+
if (this._flipY2D && this._is2D) {
|
|
431
|
+
ySign = (ySign * -1) as 1 | -1
|
|
432
|
+
}
|
|
433
|
+
placeholderAffine[mapping.y.physicalRow][1] = ySign
|
|
434
|
+
placeholderAffine[mapping.z.physicalRow][2] = mapping.z.sign
|
|
435
|
+
hdr.affine = placeholderAffine
|
|
410
436
|
|
|
411
437
|
hdr.sform_code = 1 // Scanner coordinates
|
|
412
438
|
|
|
@@ -698,25 +724,22 @@ export class OMEZarrNVImage extends NVImage {
|
|
|
698
724
|
1,
|
|
699
725
|
]
|
|
700
726
|
|
|
701
|
-
// Build affine
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
// Adjust translation for region offset
|
|
705
|
-
// Buffer pixel [0,0,0] corresponds to source pixel region.chunkAlignedStart
|
|
727
|
+
// Build the unadjusted affine (no orientation signs) and offset it
|
|
728
|
+
// to the loaded region. Buffer bounds use this OME-Zarr-space affine
|
|
729
|
+
// for internal clip-plane / viewport math.
|
|
706
730
|
const regionStart = region.chunkAlignedStart
|
|
731
|
+
const affine = createAffineFromOMEZarr(
|
|
732
|
+
ngffImage.scale,
|
|
733
|
+
ngffImage.translation,
|
|
734
|
+
)
|
|
707
735
|
// regionStart is [z, y, x], affine translation is [x, y, z] (indices 12, 13, 14)
|
|
708
736
|
affine[12] += regionStart[2] * sx // x offset
|
|
709
737
|
affine[13] += regionStart[1] * sy // y offset
|
|
710
738
|
affine[14] += regionStart[0] * sz // z offset
|
|
711
739
|
|
|
712
|
-
// Update current buffer bounds
|
|
713
|
-
// (bounds stay in OME-Zarr world space for clip plane math)
|
|
740
|
+
// Update current buffer bounds in OME-Zarr world space
|
|
714
741
|
this._currentBufferBounds = {
|
|
715
|
-
min: [
|
|
716
|
-
affine[12], // x offset (world coord of buffer origin)
|
|
717
|
-
affine[13], // y offset
|
|
718
|
-
affine[14], // z offset
|
|
719
|
-
],
|
|
742
|
+
min: [affine[12], affine[13], affine[14]],
|
|
720
743
|
max: [
|
|
721
744
|
affine[12] + fetchedShape[2] * sx,
|
|
722
745
|
affine[13] + fetchedShape[1] * sy,
|
|
@@ -724,11 +747,21 @@ export class OMEZarrNVImage extends NVImage {
|
|
|
724
747
|
],
|
|
725
748
|
}
|
|
726
749
|
|
|
727
|
-
//
|
|
728
|
-
//
|
|
750
|
+
// Apply orientation signs so the NIfTI affine encodes anatomical
|
|
751
|
+
// direction for NiiVue's calculateRAS()
|
|
752
|
+
applyOrientationToAffine(affine, ngffImage.axesOrientations)
|
|
753
|
+
|
|
754
|
+
// For 2D images, flip y so NiiVue's calculateRAS() accounts for
|
|
755
|
+
// top-to-bottom pixel storage order. We shift the translation so
|
|
756
|
+
// the last row maps to where the first row was, then negate the
|
|
757
|
+
// y column. This composes correctly with any orientation sign.
|
|
729
758
|
if (this._flipY2D && this._is2D) {
|
|
730
|
-
|
|
731
|
-
|
|
759
|
+
// Get the y axis orientation mapping to find where the y scale is stored
|
|
760
|
+
const mapping = getOrientationMapping(ngffImage.axesOrientations)
|
|
761
|
+
// The y scale is at affine[4 + physicalRow] (column 1, appropriate row)
|
|
762
|
+
const yScaleIndex = 4 + mapping.y.physicalRow
|
|
763
|
+
affine[13] += affine[yScaleIndex] * (fetchedShape[1] - 1)
|
|
764
|
+
affine[yScaleIndex] = -affine[yScaleIndex]
|
|
732
765
|
}
|
|
733
766
|
|
|
734
767
|
// Update affine in header
|
|
@@ -2366,17 +2399,28 @@ export class OMEZarrNVImage extends NVImage {
|
|
|
2366
2399
|
1,
|
|
2367
2400
|
]
|
|
2368
2401
|
|
|
2369
|
-
// Build affine with offset for region start
|
|
2402
|
+
// Build affine with orientation signs, then offset for region start.
|
|
2403
|
+
// Use the original unoriented scale values with orientation signs for
|
|
2404
|
+
// the translation offset calculation. This ensures correctness even when
|
|
2405
|
+
// axes are permuted (where affine diagonal may be zero).
|
|
2370
2406
|
const affine = createAffineFromNgffImage(ngffImage)
|
|
2407
|
+
|
|
2408
|
+
// Get orientation signs to apply to the offset calculation
|
|
2409
|
+
const signs = getOrientationSigns(ngffImage.axesOrientations)
|
|
2410
|
+
|
|
2371
2411
|
// Adjust translation for region offset (fetchStart is [z, y, x])
|
|
2372
|
-
affine[12] += fetchStart[2] * sx // x offset
|
|
2373
|
-
affine[13] += fetchStart[1] * sy // y offset
|
|
2374
|
-
affine[14] += fetchStart[0] * sz // z offset
|
|
2412
|
+
affine[12] += fetchStart[2] * signs.x * sx // x offset (orientation-aware)
|
|
2413
|
+
affine[13] += fetchStart[1] * signs.y * sy // y offset (orientation-aware)
|
|
2414
|
+
affine[14] += fetchStart[0] * signs.z * sz // z offset (orientation-aware)
|
|
2375
2415
|
|
|
2376
|
-
// For 2D images,
|
|
2416
|
+
// For 2D images, flip y before normalization (composes with orientation)
|
|
2377
2417
|
if (this._flipY2D && this._is2D) {
|
|
2378
|
-
|
|
2379
|
-
|
|
2418
|
+
// Get the y axis orientation mapping to find where the y scale is stored
|
|
2419
|
+
const mapping = getOrientationMapping(ngffImage.axesOrientations)
|
|
2420
|
+
// The y scale is at affine[4 + physicalRow] (column 1, appropriate row)
|
|
2421
|
+
const yScaleIndex = 4 + mapping.y.physicalRow
|
|
2422
|
+
affine[13] += affine[yScaleIndex] * (fetchedShape[1] - 1)
|
|
2423
|
+
affine[yScaleIndex] = -affine[yScaleIndex]
|
|
2380
2424
|
}
|
|
2381
2425
|
|
|
2382
2426
|
// Apply normalization to the entire affine (scale columns + translation)
|