@fideus-labs/fidnii 0.1.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.
Files changed (58) hide show
  1. package/LICENSE.txt +9 -0
  2. package/README.md +180 -0
  3. package/dist/BufferManager.d.ts +86 -0
  4. package/dist/BufferManager.d.ts.map +1 -0
  5. package/dist/BufferManager.js +146 -0
  6. package/dist/BufferManager.js.map +1 -0
  7. package/dist/ClipPlanes.d.ts +180 -0
  8. package/dist/ClipPlanes.d.ts.map +1 -0
  9. package/dist/ClipPlanes.js +513 -0
  10. package/dist/ClipPlanes.js.map +1 -0
  11. package/dist/OMEZarrNVImage.d.ts +545 -0
  12. package/dist/OMEZarrNVImage.d.ts.map +1 -0
  13. package/dist/OMEZarrNVImage.js +1799 -0
  14. package/dist/OMEZarrNVImage.js.map +1 -0
  15. package/dist/RegionCoalescer.d.ts +75 -0
  16. package/dist/RegionCoalescer.d.ts.map +1 -0
  17. package/dist/RegionCoalescer.js +151 -0
  18. package/dist/RegionCoalescer.js.map +1 -0
  19. package/dist/ResolutionSelector.d.ts +88 -0
  20. package/dist/ResolutionSelector.d.ts.map +1 -0
  21. package/dist/ResolutionSelector.js +224 -0
  22. package/dist/ResolutionSelector.js.map +1 -0
  23. package/dist/ViewportBounds.d.ts +50 -0
  24. package/dist/ViewportBounds.d.ts.map +1 -0
  25. package/dist/ViewportBounds.js +325 -0
  26. package/dist/ViewportBounds.js.map +1 -0
  27. package/dist/events.d.ts +122 -0
  28. package/dist/events.d.ts.map +1 -0
  29. package/dist/events.js +12 -0
  30. package/dist/events.js.map +1 -0
  31. package/dist/index.d.ts +48 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +59 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/types.d.ts +273 -0
  36. package/dist/types.d.ts.map +1 -0
  37. package/dist/types.js +126 -0
  38. package/dist/types.js.map +1 -0
  39. package/dist/utils/affine.d.ts +72 -0
  40. package/dist/utils/affine.d.ts.map +1 -0
  41. package/dist/utils/affine.js +173 -0
  42. package/dist/utils/affine.js.map +1 -0
  43. package/dist/utils/coordinates.d.ts +80 -0
  44. package/dist/utils/coordinates.d.ts.map +1 -0
  45. package/dist/utils/coordinates.js +207 -0
  46. package/dist/utils/coordinates.js.map +1 -0
  47. package/package.json +61 -0
  48. package/src/BufferManager.ts +176 -0
  49. package/src/ClipPlanes.ts +640 -0
  50. package/src/OMEZarrNVImage.ts +2286 -0
  51. package/src/RegionCoalescer.ts +217 -0
  52. package/src/ResolutionSelector.ts +325 -0
  53. package/src/ViewportBounds.ts +369 -0
  54. package/src/events.ts +146 -0
  55. package/src/index.ts +153 -0
  56. package/src/types.ts +429 -0
  57. package/src/utils/affine.ts +218 -0
  58. package/src/utils/coordinates.ts +271 -0
package/LICENSE.txt ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-present Fideus Labs LLC <info@fideus.io>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,180 @@
1
+ # @fideus-labs/fidnii
2
+
3
+ Render OME-Zarr images in NiiVue with progressive multi-resolution loading.
4
+
5
+ ## Features
6
+
7
+ - **Progressive loading** - Quick preview from lowest resolution, then target
8
+ resolution
9
+ - **Automatic resolution selection** - Picks optimal resolution based on pixel
10
+ budget
11
+ - **Clip planes** - Up to 6 arbitrary clip planes for cropping/visualization
12
+ - **Dynamic buffer sizing** - Matches fetched data exactly (no upsampling)
13
+ - **Chunk caching** - LRU decoded-chunk cache avoids redundant decompression
14
+ - **Request coalescing** - Efficient chunk fetching
15
+ - **Event system** - Browser-native EventTarget API for loading states
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @fideus-labs/fidnii @fideus-labs/ngff-zarr @niivue/niivue
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```typescript
26
+ import { Niivue } from "@niivue/niivue";
27
+ import { fromNgffZarr } from "@fideus-labs/ngff-zarr";
28
+ import { OMEZarrNVImage } from "@fideus-labs/fidnii";
29
+
30
+ const nv = new Niivue();
31
+ await nv.attachToCanvas(document.getElementById("canvas"));
32
+ nv.setSliceType(nv.sliceTypeRender);
33
+
34
+ const multiscales = await fromNgffZarr("/path/to/data.ome.zarr");
35
+
36
+ // Image is automatically added to NiiVue and loads progressively
37
+ await OMEZarrNVImage.create({ multiscales, niivue: nv });
38
+ ```
39
+
40
+ ## Options
41
+
42
+ | Option | Type | Default | Description |
43
+ | --------------------- | ------------- | ------------ | ----------------------------------------------- |
44
+ | `multiscales` | `Multiscales` | required | OME-Zarr multiscales data from `fromNgffZarr()` |
45
+ | `niivue` | `Niivue` | required | NiiVue instance |
46
+ | `maxPixels` | `number` | `50_000_000` | Maximum pixels to load (controls resolution) |
47
+ | `autoLoad` | `boolean` | `true` | Auto-add to NiiVue and start loading |
48
+ | `clipPlaneDebounceMs` | `number` | `300` | Debounce delay for clip plane updates |
49
+ | `maxCacheEntries` | `number` | `200` | Max decoded-chunk cache entries (0 to disable) |
50
+ | `cache` | `ChunkCache` | — | Pre-built cache (overrides `maxCacheEntries`) |
51
+
52
+ ## Events
53
+
54
+ Listen to loading events using the browser-native EventTarget API:
55
+
56
+ ```typescript
57
+ const image = await OMEZarrNVImage.create({ multiscales, niivue: nv });
58
+
59
+ image.addEventListener("loadingStart", (e) => {
60
+ console.log(`Loading level ${e.detail.levelIndex}...`);
61
+ });
62
+
63
+ image.addEventListener("populateComplete", (e) => {
64
+ console.log(`Loaded level ${e.detail.currentLevel}`);
65
+ });
66
+ ```
67
+
68
+ ### Available Events
69
+
70
+ | Event | Description |
71
+ | ------------------ | --------------------------------------------------- |
72
+ | `loadingStart` | Fired when loading starts for a resolution level |
73
+ | `loadingComplete` | Fired when loading completes for a resolution level |
74
+ | `resolutionChange` | Fired when resolution level changes |
75
+ | `populateComplete` | Fired when all loading is done |
76
+ | `clipPlanesChange` | Fired when clip planes are updated (after debounce) |
77
+
78
+ ## Advanced Usage
79
+
80
+ For manual control over when loading starts, use `autoLoad: false`:
81
+
82
+ ```typescript
83
+ const image = await OMEZarrNVImage.create({
84
+ multiscales,
85
+ niivue: nv,
86
+ autoLoad: false,
87
+ });
88
+
89
+ // Set up event listeners before loading starts
90
+ image.addEventListener("populateComplete", () => {
91
+ console.log("Loading complete!");
92
+ });
93
+
94
+ // Manually add to NiiVue and start loading
95
+ nv.addVolume(image);
96
+ await image.populateVolume();
97
+ ```
98
+
99
+ ## Clip Planes
100
+
101
+ Clip planes define visible regions of the volume. Up to 6 clip planes can be
102
+ active.
103
+
104
+ ```typescript
105
+ import { createAxisAlignedClipPlane } from "@fideus-labs/fidnii";
106
+
107
+ const image = await OMEZarrNVImage.create({ multiscales, niivue: nv });
108
+
109
+ // Wait for initial load
110
+ image.addEventListener("populateComplete", () => {
111
+ const bounds = image.getVolumeBounds();
112
+
113
+ // Clip at X = midpoint, keeping +X side visible
114
+ const midX = (bounds.min[0] + bounds.max[0]) / 2;
115
+ const clipPlane = createAxisAlignedClipPlane("x", midX, "positive", bounds);
116
+
117
+ image.setClipPlanes([clipPlane]);
118
+ }, { once: true });
119
+ ```
120
+
121
+ ## Chunk Caching
122
+
123
+ Fidnii ships with an LRU decoded-chunk cache that avoids redundant
124
+ decompression when the same Zarr chunk is read more than once. This happens
125
+ frequently with overlapping clip-plane selections, repeated `populateVolume`
126
+ calls, and progressive loading where 2D slabs and 3D volumes share chunks.
127
+
128
+ Caching is **enabled by default** with a 200-entry limit. No extra code is
129
+ needed:
130
+
131
+ ```typescript
132
+ // Default: 200-entry LRU cache created automatically
133
+ const image = await OMEZarrNVImage.create({ multiscales, niivue: nv });
134
+ ```
135
+
136
+ ### Custom cache size
137
+
138
+ ```typescript
139
+ const image = await OMEZarrNVImage.create({
140
+ multiscales,
141
+ niivue: nv,
142
+ maxCacheEntries: 500,
143
+ });
144
+ ```
145
+
146
+ ### Disabling caching
147
+
148
+ ```typescript
149
+ const image = await OMEZarrNVImage.create({
150
+ multiscales,
151
+ niivue: nv,
152
+ maxCacheEntries: 0,
153
+ });
154
+ ```
155
+
156
+ ### Bring-your-own cache
157
+
158
+ Any object that satisfies the `ChunkCache` interface (`get` / `set` with
159
+ `string` keys and `ArrayBuffer` values) can be passed directly:
160
+
161
+ ```typescript
162
+ import type { ChunkCache } from "@fideus-labs/fidnii";
163
+
164
+ const myCache: ChunkCache = {
165
+ get(key: string) { /* ... */ },
166
+ set(key: string, value: ArrayBuffer) { /* ... */ },
167
+ };
168
+
169
+ const image = await OMEZarrNVImage.create({
170
+ multiscales,
171
+ niivue: nv,
172
+ cache: myCache,
173
+ });
174
+ ```
175
+
176
+ When `cache` is provided it takes precedence over `maxCacheEntries`.
177
+
178
+ ## License
179
+
180
+ MIT
@@ -0,0 +1,86 @@
1
+ import type { TypedArray, ZarrDtype } from "./types.js";
2
+ /**
3
+ * Manages a dynamically-sized pixel buffer for volume data.
4
+ *
5
+ * The buffer is resized to match the fetched data dimensions exactly.
6
+ * Memory is reused when possible to avoid unnecessary allocations.
7
+ *
8
+ * Memory reuse strategy:
9
+ * - Reuse buffer if newSize <= currentCapacity
10
+ * - Reallocate if newSize > currentCapacity OR newSize < 25% of currentCapacity
11
+ */
12
+ export declare class BufferManager {
13
+ private buffer;
14
+ private currentDimensions;
15
+ private readonly maxPixels;
16
+ private readonly TypedArrayCtor;
17
+ private readonly bytesPerPixel;
18
+ private readonly dtype;
19
+ /**
20
+ * Create a new BufferManager.
21
+ *
22
+ * @param maxPixels - Maximum number of pixels allowed (budget)
23
+ * @param dtype - Data type for the buffer
24
+ */
25
+ constructor(maxPixels: number, dtype: ZarrDtype);
26
+ /**
27
+ * Resize the buffer to fit the given dimensions.
28
+ *
29
+ * Reuses existing buffer if large enough, otherwise allocates new buffer.
30
+ * Will also reallocate if the buffer is significantly oversized (< 25% utilization).
31
+ *
32
+ * If dimensions exceed maxPixels, a warning is logged but the buffer is still
33
+ * allocated. This handles the case where even the lowest resolution exceeds the
34
+ * pixel budget - we still want to load something rather than failing.
35
+ *
36
+ * @param dimensions - New dimensions [z, y, x]
37
+ * @returns TypedArray view over the (possibly new) buffer
38
+ */
39
+ resize(dimensions: [number, number, number]): TypedArray;
40
+ /**
41
+ * Get the underlying ArrayBuffer.
42
+ */
43
+ getBuffer(): ArrayBuffer;
44
+ /**
45
+ * Get a typed array view over the current buffer region.
46
+ *
47
+ * The view is sized to match currentDimensions, not the full buffer capacity.
48
+ */
49
+ getTypedArray(): TypedArray;
50
+ /**
51
+ * Get the current buffer dimensions [z, y, x].
52
+ */
53
+ getDimensions(): [number, number, number];
54
+ /**
55
+ * Get the total number of pixels in the current buffer region.
56
+ */
57
+ getPixelCount(): number;
58
+ /**
59
+ * Get the buffer capacity in pixels.
60
+ */
61
+ getCapacity(): number;
62
+ /**
63
+ * Get the bytes per pixel.
64
+ */
65
+ getBytesPerPixel(): number;
66
+ /**
67
+ * Get the data type.
68
+ */
69
+ getDtype(): ZarrDtype;
70
+ /**
71
+ * Get the maximum pixels budget.
72
+ */
73
+ getMaxPixels(): number;
74
+ /**
75
+ * Clear the current buffer region to zeros.
76
+ */
77
+ clear(): void;
78
+ /**
79
+ * Check if the buffer can accommodate the given dimensions without reallocation.
80
+ *
81
+ * @param dimensions - Dimensions to check [z, y, x]
82
+ * @returns True if current buffer can fit the dimensions
83
+ */
84
+ canAccommodate(dimensions: [number, number, number]): boolean;
85
+ }
86
+ //# sourceMappingURL=BufferManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BufferManager.d.ts","sourceRoot":"","sources":["../src/BufferManager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAyB,SAAS,EAAE,MAAM,YAAY,CAAC;AAG/E;;;;;;;;;GASG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,iBAAiB,CAA2B;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwB;IACvD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;IAElC;;;;;OAKG;gBACS,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS;IAW/C;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,UAAU;IA8BxD;;OAEG;IACH,SAAS,IAAI,WAAW;IAIxB;;;;OAIG;IACH,aAAa,IAAI,UAAU;IAO3B;;OAEG;IACH,aAAa,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;IAIzC;;OAEG;IACH,aAAa,IAAI,MAAM;IAQvB;;OAEG;IACH,WAAW,IAAI,MAAM;IAIrB;;OAEG;IACH,gBAAgB,IAAI,MAAM;IAI1B;;OAEG;IACH,QAAQ,IAAI,SAAS;IAIrB;;OAEG;IACH,YAAY,IAAI,MAAM;IAItB;;OAEG;IACH,KAAK,IAAI,IAAI;IAYb;;;;;OAKG;IACH,cAAc,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO;CAK9D"}
@@ -0,0 +1,146 @@
1
+ // SPDX-FileCopyrightText: Copyright (c) Fideus Labs LLC
2
+ // SPDX-License-Identifier: MIT
3
+ import { getBytesPerPixel, getTypedArrayConstructor } from "./types.js";
4
+ /**
5
+ * Manages a dynamically-sized pixel buffer for volume data.
6
+ *
7
+ * The buffer is resized to match the fetched data dimensions exactly.
8
+ * Memory is reused when possible to avoid unnecessary allocations.
9
+ *
10
+ * Memory reuse strategy:
11
+ * - Reuse buffer if newSize <= currentCapacity
12
+ * - Reallocate if newSize > currentCapacity OR newSize < 25% of currentCapacity
13
+ */
14
+ export class BufferManager {
15
+ buffer;
16
+ currentDimensions;
17
+ maxPixels;
18
+ TypedArrayCtor;
19
+ bytesPerPixel;
20
+ dtype;
21
+ /**
22
+ * Create a new BufferManager.
23
+ *
24
+ * @param maxPixels - Maximum number of pixels allowed (budget)
25
+ * @param dtype - Data type for the buffer
26
+ */
27
+ constructor(maxPixels, dtype) {
28
+ this.maxPixels = maxPixels;
29
+ this.dtype = dtype;
30
+ this.TypedArrayCtor = getTypedArrayConstructor(dtype);
31
+ this.bytesPerPixel = getBytesPerPixel(dtype);
32
+ // Initialize with empty buffer - will be allocated on first resize
33
+ this.currentDimensions = [0, 0, 0];
34
+ this.buffer = new ArrayBuffer(0);
35
+ }
36
+ /**
37
+ * Resize the buffer to fit the given dimensions.
38
+ *
39
+ * Reuses existing buffer if large enough, otherwise allocates new buffer.
40
+ * Will also reallocate if the buffer is significantly oversized (< 25% utilization).
41
+ *
42
+ * If dimensions exceed maxPixels, a warning is logged but the buffer is still
43
+ * allocated. This handles the case where even the lowest resolution exceeds the
44
+ * pixel budget - we still want to load something rather than failing.
45
+ *
46
+ * @param dimensions - New dimensions [z, y, x]
47
+ * @returns TypedArray view over the (possibly new) buffer
48
+ */
49
+ resize(dimensions) {
50
+ const requiredPixels = dimensions[0] * dimensions[1] * dimensions[2];
51
+ if (requiredPixels > this.maxPixels) {
52
+ console.warn(`[fidnii] BufferManager: Requested dimensions [${dimensions.join(", ")}] = ${requiredPixels} pixels exceeds maxPixels (${this.maxPixels}). ` +
53
+ `Proceeding anyway (likely at lowest resolution).`);
54
+ }
55
+ const currentCapacityPixels = this.buffer.byteLength / this.bytesPerPixel;
56
+ const utilizationRatio = currentCapacityPixels > 0
57
+ ? requiredPixels / currentCapacityPixels
58
+ : 0;
59
+ const needsReallocation = requiredPixels > currentCapacityPixels ||
60
+ utilizationRatio < 0.25;
61
+ if (needsReallocation) {
62
+ // Allocate new buffer
63
+ const newByteLength = requiredPixels * this.bytesPerPixel;
64
+ this.buffer = new ArrayBuffer(newByteLength);
65
+ }
66
+ this.currentDimensions = [...dimensions];
67
+ return this.getTypedArray();
68
+ }
69
+ /**
70
+ * Get the underlying ArrayBuffer.
71
+ */
72
+ getBuffer() {
73
+ return this.buffer;
74
+ }
75
+ /**
76
+ * Get a typed array view over the current buffer region.
77
+ *
78
+ * The view is sized to match currentDimensions, not the full buffer capacity.
79
+ */
80
+ getTypedArray() {
81
+ const pixelCount = this.currentDimensions[0] *
82
+ this.currentDimensions[1] *
83
+ this.currentDimensions[2];
84
+ return new this.TypedArrayCtor(this.buffer, 0, pixelCount);
85
+ }
86
+ /**
87
+ * Get the current buffer dimensions [z, y, x].
88
+ */
89
+ getDimensions() {
90
+ return [...this.currentDimensions];
91
+ }
92
+ /**
93
+ * Get the total number of pixels in the current buffer region.
94
+ */
95
+ getPixelCount() {
96
+ return (this.currentDimensions[0] *
97
+ this.currentDimensions[1] *
98
+ this.currentDimensions[2]);
99
+ }
100
+ /**
101
+ * Get the buffer capacity in pixels.
102
+ */
103
+ getCapacity() {
104
+ return this.buffer.byteLength / this.bytesPerPixel;
105
+ }
106
+ /**
107
+ * Get the bytes per pixel.
108
+ */
109
+ getBytesPerPixel() {
110
+ return this.bytesPerPixel;
111
+ }
112
+ /**
113
+ * Get the data type.
114
+ */
115
+ getDtype() {
116
+ return this.dtype;
117
+ }
118
+ /**
119
+ * Get the maximum pixels budget.
120
+ */
121
+ getMaxPixels() {
122
+ return this.maxPixels;
123
+ }
124
+ /**
125
+ * Clear the current buffer region to zeros.
126
+ */
127
+ clear() {
128
+ const pixelCount = this.getPixelCount();
129
+ if (pixelCount > 0) {
130
+ const view = new Uint8Array(this.buffer, 0, pixelCount * this.bytesPerPixel);
131
+ view.fill(0);
132
+ }
133
+ }
134
+ /**
135
+ * Check if the buffer can accommodate the given dimensions without reallocation.
136
+ *
137
+ * @param dimensions - Dimensions to check [z, y, x]
138
+ * @returns True if current buffer can fit the dimensions
139
+ */
140
+ canAccommodate(dimensions) {
141
+ const requiredPixels = dimensions[0] * dimensions[1] * dimensions[2];
142
+ const currentCapacityPixels = this.buffer.byteLength / this.bytesPerPixel;
143
+ return requiredPixels <= currentCapacityPixels;
144
+ }
145
+ }
146
+ //# sourceMappingURL=BufferManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BufferManager.js","sourceRoot":"","sources":["../src/BufferManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,180 @@
1
+ import type { Multiscales, NgffImage } from "@fideus-labs/ngff-zarr";
2
+ import type { ChunkAlignedRegion, ClipPlane, ClipPlanes, PixelRegion, VolumeBounds } from "./types.js";
3
+ /** Maximum number of clip planes supported by NiiVue */
4
+ export declare const MAX_CLIP_PLANES = 6;
5
+ /**
6
+ * Normalize a 3D vector to unit length.
7
+ *
8
+ * @param v - Vector to normalize [x, y, z]
9
+ * @returns Normalized vector [x, y, z]
10
+ * @throws Error if vector has zero length
11
+ */
12
+ export declare function normalizeVector(v: [number, number, number]): [number, number, number];
13
+ /**
14
+ * Create a clip plane from a point and normal vector.
15
+ * The normal is automatically normalized to unit length.
16
+ *
17
+ * @param point - A point on the plane [x, y, z] in world coordinates
18
+ * @param normal - Normal vector pointing toward visible region [x, y, z]
19
+ * @returns ClipPlane with normalized normal
20
+ */
21
+ export declare function createClipPlane(point: [number, number, number], normal: [number, number, number]): ClipPlane;
22
+ /**
23
+ * Create default clip planes (empty array = full volume visible).
24
+ *
25
+ * @param _multiscales - The OME-Zarr multiscales data (unused, kept for API consistency)
26
+ * @returns Empty ClipPlanes array
27
+ */
28
+ export declare function createDefaultClipPlanes(_multiscales: Multiscales): ClipPlanes;
29
+ /**
30
+ * Get volume bounds from multiscales metadata.
31
+ *
32
+ * @param multiscales - The OME-Zarr multiscales data
33
+ * @returns Volume bounds in world space
34
+ */
35
+ export declare function getVolumeBoundsFromMultiscales(multiscales: Multiscales): VolumeBounds;
36
+ /**
37
+ * Convert a normal vector to azimuth and elevation angles (for NiiVue).
38
+ *
39
+ * NiiVue convention:
40
+ * - Azimuth: 0 = posterior (+Y), 90 = right (+X), 180 = anterior (-Y), 270 = left (-X)
41
+ * - Elevation: 0 = horizontal, 90 = superior (+Z), -90 = inferior (-Z)
42
+ *
43
+ * @param normal - Unit normal vector [x, y, z]
44
+ * @returns Object with azimuth and elevation in degrees
45
+ */
46
+ export declare function normalToAzimuthElevation(normal: [number, number, number]): {
47
+ azimuth: number;
48
+ elevation: number;
49
+ };
50
+ /**
51
+ * Convert azimuth and elevation angles to a unit normal vector.
52
+ *
53
+ * @param azimuth - Azimuth angle in degrees (0 = +Y, 90 = +X)
54
+ * @param elevation - Elevation angle in degrees (0 = horizontal, 90 = +Z)
55
+ * @returns Unit normal vector [x, y, z]
56
+ */
57
+ export declare function azimuthElevationToNormal(azimuth: number, elevation: number): [number, number, number];
58
+ /**
59
+ * Calculate the NiiVue depth parameter for a clip plane.
60
+ *
61
+ * NiiVue's clip plane depth is in normalized texture coordinates where
62
+ * the volume center is at 0.5. Depth represents the signed distance from
63
+ * the center (0) to the plane, where -0.5 is at min boundary and +0.5 is
64
+ * at max boundary. Values beyond [-0.5, 0.5] place the plane outside the volume.
65
+ *
66
+ * @param plane - The clip plane
67
+ * @param volumeBounds - Volume bounds in world space
68
+ * @returns Depth value for NiiVue (typically in range [-0.5, 0.5] for planes within volume)
69
+ */
70
+ export declare function calculateNiivueDepth(plane: ClipPlane, volumeBounds: VolumeBounds): number;
71
+ /**
72
+ * Convert a single clip plane to NiiVue format [depth, azimuth, elevation].
73
+ *
74
+ * NiiVue's shader convention:
75
+ * - The "back" side of the plane (sampleSide > 0) is VISIBLE
76
+ * - The "front" side of the plane (sampleSide < 0) is CLIPPED
77
+ * - sampleSide = dot(shaderNormal, p - 0.5) + depth
78
+ * - NiiVue internally adds 180° to azimuth, which flips the normal direction
79
+ *
80
+ * Our convention:
81
+ * - Normal points toward the VISIBLE region
82
+ *
83
+ * To reconcile these conventions:
84
+ * 1. We negate the normal before computing azimuth/elevation
85
+ * 2. After NiiVue's +180° flip, the shader sees our original normal direction
86
+ * 3. We also negate the depth to match the flipped normal
87
+ *
88
+ * @param plane - The clip plane
89
+ * @param volumeBounds - Volume bounds in world space
90
+ * @returns [depth, azimuth, elevation] for NiiVue
91
+ */
92
+ export declare function clipPlaneToNiivue(plane: ClipPlane, volumeBounds: VolumeBounds): [number, number, number];
93
+ /**
94
+ * Convert clip planes to NiiVue format.
95
+ *
96
+ * @param clipPlanes - Array of clip planes
97
+ * @param volumeBounds - Volume bounds in world space
98
+ * @returns Array of [depth, azimuth, elevation] for NiiVue
99
+ */
100
+ export declare function clipPlanesToNiivue(clipPlanes: ClipPlanes, volumeBounds: VolumeBounds): number[][];
101
+ /**
102
+ * Calculate the signed distance from a point to a plane.
103
+ *
104
+ * Positive = point is on the visible side (same side as normal)
105
+ * Negative = point is on the clipped side (opposite side from normal)
106
+ *
107
+ * @param testPoint - Point to test [x, y, z]
108
+ * @param plane - The clip plane
109
+ * @returns Signed distance
110
+ */
111
+ export declare function pointToPlaneDistance(testPoint: [number, number, number], plane: ClipPlane): number;
112
+ /**
113
+ * Check if a point is inside all clip planes (on the visible side).
114
+ *
115
+ * @param worldCoord - World coordinate [x, y, z]
116
+ * @param clipPlanes - Array of clip planes
117
+ * @returns True if the point is inside all clip planes (or if there are no planes)
118
+ */
119
+ export declare function isInsideClipPlanes(worldCoord: [number, number, number], clipPlanes: ClipPlanes): boolean;
120
+ /**
121
+ * Calculate the axis-aligned bounding box that contains the clipped region.
122
+ *
123
+ * For oblique clip planes, this finds the intersection of the clip planes
124
+ * with the volume bounds and returns the AABB of that intersection.
125
+ *
126
+ * This is used for data fetching (zarr is always axis-aligned).
127
+ *
128
+ * @param clipPlanes - Array of clip planes
129
+ * @param volumeBounds - Full volume bounds in world space
130
+ * @returns Bounding box of the clipped region
131
+ */
132
+ export declare function clipPlanesToBoundingBox(clipPlanes: ClipPlanes, volumeBounds: VolumeBounds): VolumeBounds;
133
+ /**
134
+ * Convert clip planes to a pixel region for a specific NgffImage.
135
+ *
136
+ * This calculates the axis-aligned bounding box of the clipped region
137
+ * and converts it to pixel coordinates. When viewportBounds is provided,
138
+ * the result is further constrained to only include the visible viewport
139
+ * area (for viewport-aware resolution selection).
140
+ *
141
+ * @param clipPlanes - Array of clip planes
142
+ * @param volumeBounds - Full volume bounds in world space
143
+ * @param ngffImage - The NgffImage to convert to
144
+ * @param viewportBounds - Optional viewport bounds to intersect with
145
+ * @returns Pixel region [z, y, x] start and end indices
146
+ */
147
+ export declare function clipPlanesToPixelRegion(clipPlanes: ClipPlanes, volumeBounds: VolumeBounds, ngffImage: NgffImage, viewportBounds?: VolumeBounds): PixelRegion;
148
+ /**
149
+ * Align a pixel region to chunk boundaries.
150
+ *
151
+ * This expands the region to include complete chunks, which is necessary
152
+ * for efficient zarr fetching.
153
+ *
154
+ * @param region - The pixel region to align
155
+ * @param ngffImage - The NgffImage (for chunk shape)
156
+ * @returns Chunk-aligned region with clipping information
157
+ */
158
+ export declare function alignToChunks(region: PixelRegion, ngffImage: NgffImage): ChunkAlignedRegion;
159
+ /**
160
+ * Create an axis-aligned clip plane at a specific position.
161
+ *
162
+ * The normal points toward the VISIBLE region (the region to keep).
163
+ * NiiVue's shader convention is that the "back" side of the plane (where
164
+ * dot(n, p-center) + depth > 0) is visible.
165
+ *
166
+ * @param axis - The axis ('x', 'y', or 'z')
167
+ * @param position - Position along the axis in world coordinates
168
+ * @param direction - Which side to keep visible ('positive' or 'negative')
169
+ * @param volumeBounds - Volume bounds for centering the point
170
+ * @returns ClipPlane with point at center of volume projected to the plane
171
+ */
172
+ export declare function createAxisAlignedClipPlane(axis: "x" | "y" | "z", position: number, direction: "positive" | "negative", volumeBounds: VolumeBounds): ClipPlane;
173
+ /**
174
+ * Validate clip planes array.
175
+ *
176
+ * @param clipPlanes - Array of clip planes to validate
177
+ * @throws Error if validation fails
178
+ */
179
+ export declare function validateClipPlanes(clipPlanes: ClipPlanes): void;
180
+ //# sourceMappingURL=ClipPlanes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClipPlanes.d.ts","sourceRoot":"","sources":["../src/ClipPlanes.ts"],"names":[],"mappings":"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"}