@fideus-labs/fidnii 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BufferManager.d.ts.map +1 -1
- package/dist/BufferManager.js +2 -5
- package/dist/BufferManager.js.map +1 -1
- package/dist/ClipPlanes.d.ts.map +1 -1
- package/dist/ClipPlanes.js +11 -15
- package/dist/ClipPlanes.js.map +1 -1
- package/dist/OMEZarrNVImage.d.ts +33 -3
- package/dist/OMEZarrNVImage.d.ts.map +1 -1
- package/dist/OMEZarrNVImage.js +129 -50
- package/dist/OMEZarrNVImage.js.map +1 -1
- package/dist/RegionCoalescer.d.ts.map +1 -1
- package/dist/RegionCoalescer.js +1 -1
- package/dist/RegionCoalescer.js.map +1 -1
- package/dist/ResolutionSelector.d.ts.map +1 -1
- package/dist/ResolutionSelector.js +2 -4
- package/dist/ResolutionSelector.js.map +1 -1
- package/dist/ViewportBounds.d.ts.map +1 -1
- package/dist/ViewportBounds.js +7 -5
- package/dist/ViewportBounds.js.map +1 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -16
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/affine.d.ts +1 -1
- package/dist/utils/affine.d.ts.map +1 -1
- package/dist/utils/affine.js.map +1 -1
- package/dist/utils/coordinates.d.ts +1 -1
- package/dist/utils/coordinates.d.ts.map +1 -1
- package/dist/utils/coordinates.js.map +1 -1
- package/package.json +1 -1
- package/src/BufferManager.ts +45 -45
- package/src/ClipPlanes.ts +131 -130
- package/src/OMEZarrNVImage.ts +685 -606
- package/src/RegionCoalescer.ts +48 -47
- package/src/ResolutionSelector.ts +66 -67
- package/src/ViewportBounds.ts +120 -118
- package/src/events.ts +36 -35
- package/src/index.ts +59 -69
- package/src/types.ts +95 -94
- package/src/utils/affine.ts +65 -65
- package/src/utils/coordinates.ts +70 -70
package/src/RegionCoalescer.ts
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: Copyright (c) Fideus Labs LLC
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
4
|
+
import type { NgffImage } from "@fideus-labs/ngff-zarr"
|
|
5
|
+
import { zarrGet } from "@fideus-labs/ngff-zarr/browser"
|
|
6
|
+
import * as zarr from "zarrita"
|
|
7
|
+
|
|
7
8
|
import type {
|
|
8
9
|
ChunkCache,
|
|
9
10
|
PixelRegion,
|
|
10
11
|
RegionFetchResult,
|
|
11
12
|
TypedArray,
|
|
12
|
-
} from "./types.js"
|
|
13
|
+
} from "./types.js"
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Represents a pending request that may have multiple consumers waiting for the result.
|
|
16
17
|
*/
|
|
17
18
|
interface PendingRequest {
|
|
18
19
|
/** The promise that resolves when the request completes */
|
|
19
|
-
promise: Promise<RegionFetchResult
|
|
20
|
+
promise: Promise<RegionFetchResult>
|
|
20
21
|
/** Function to resolve the promise with the result */
|
|
21
|
-
resolve: (data: RegionFetchResult) => void
|
|
22
|
+
resolve: (data: RegionFetchResult) => void
|
|
22
23
|
/** Function to reject the promise with an error */
|
|
23
|
-
reject: (error: Error) => void
|
|
24
|
+
reject: (error: Error) => void
|
|
24
25
|
/** Set of requester IDs waiting for this result */
|
|
25
|
-
requesters: Set<string
|
|
26
|
+
requesters: Set<string>
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -40,10 +41,10 @@ interface PendingRequest {
|
|
|
40
41
|
* overlapping region requests simultaneously.
|
|
41
42
|
*/
|
|
42
43
|
export class RegionCoalescer {
|
|
43
|
-
private readonly pending: Map<string, PendingRequest> = new Map()
|
|
44
|
+
private readonly pending: Map<string, PendingRequest> = new Map()
|
|
44
45
|
|
|
45
46
|
/** Optional decoded-chunk cache forwarded to fizarrita's getWorker. */
|
|
46
|
-
private readonly _cache: ChunkCache | undefined
|
|
47
|
+
private readonly _cache: ChunkCache | undefined
|
|
47
48
|
|
|
48
49
|
/**
|
|
49
50
|
* @param cache - Optional decoded-chunk cache. When provided, `zarrGet`
|
|
@@ -51,7 +52,7 @@ export class RegionCoalescer {
|
|
|
51
52
|
* or overlapping reads.
|
|
52
53
|
*/
|
|
53
54
|
constructor(cache?: ChunkCache) {
|
|
54
|
-
this._cache = cache
|
|
55
|
+
this._cache = cache
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
/**
|
|
@@ -62,9 +63,9 @@ export class RegionCoalescer {
|
|
|
62
63
|
levelIndex: number,
|
|
63
64
|
region: PixelRegion,
|
|
64
65
|
): string {
|
|
65
|
-
const start = region.start.join(",")
|
|
66
|
-
const end = region.end.join(",")
|
|
67
|
-
return `${imagePath}:${levelIndex}:${start}:${end}
|
|
66
|
+
const start = region.start.join(",")
|
|
67
|
+
const end = region.end.join(",")
|
|
68
|
+
return `${imagePath}:${levelIndex}:${start}:${end}`
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
/**
|
|
@@ -82,33 +83,33 @@ export class RegionCoalescer {
|
|
|
82
83
|
region: PixelRegion,
|
|
83
84
|
requesterId: string = "default",
|
|
84
85
|
): Promise<RegionFetchResult> {
|
|
85
|
-
const key = this.makeKey(ngffImage.data.path, levelIndex, region)
|
|
86
|
+
const key = this.makeKey(ngffImage.data.path, levelIndex, region)
|
|
86
87
|
|
|
87
88
|
// Check if there's already a pending request for this data
|
|
88
|
-
const existing = this.pending.get(key)
|
|
89
|
+
const existing = this.pending.get(key)
|
|
89
90
|
if (existing) {
|
|
90
91
|
// Add this requester to the waiters and return the existing promise
|
|
91
|
-
existing.requesters.add(requesterId)
|
|
92
|
-
return existing.promise
|
|
92
|
+
existing.requesters.add(requesterId)
|
|
93
|
+
return existing.promise
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
// Create a new pending request
|
|
96
|
-
let resolvePromise!: (data: RegionFetchResult) => void
|
|
97
|
-
let rejectPromise!: (error: Error) => void
|
|
97
|
+
let resolvePromise!: (data: RegionFetchResult) => void
|
|
98
|
+
let rejectPromise!: (error: Error) => void
|
|
98
99
|
|
|
99
100
|
const promise = new Promise<RegionFetchResult>((resolve, reject) => {
|
|
100
|
-
resolvePromise = resolve
|
|
101
|
-
rejectPromise = reject
|
|
102
|
-
})
|
|
101
|
+
resolvePromise = resolve
|
|
102
|
+
rejectPromise = reject
|
|
103
|
+
})
|
|
103
104
|
|
|
104
105
|
const pendingRequest: PendingRequest = {
|
|
105
106
|
promise,
|
|
106
107
|
resolve: resolvePromise,
|
|
107
108
|
reject: rejectPromise,
|
|
108
109
|
requesters: new Set([requesterId]),
|
|
109
|
-
}
|
|
110
|
+
}
|
|
110
111
|
|
|
111
|
-
this.pending.set(key, pendingRequest)
|
|
112
|
+
this.pending.set(key, pendingRequest)
|
|
112
113
|
|
|
113
114
|
// Fetch using fizarrita's worker-accelerated zarrGet
|
|
114
115
|
try {
|
|
@@ -116,28 +117,28 @@ export class RegionCoalescer {
|
|
|
116
117
|
zarr.slice(region.start[0], region.end[0]),
|
|
117
118
|
zarr.slice(region.start[1], region.end[1]),
|
|
118
119
|
zarr.slice(region.start[2], region.end[2]),
|
|
119
|
-
]
|
|
120
|
+
]
|
|
120
121
|
// Pass the chunk cache to fizarrita's getWorker via zarrGet.
|
|
121
122
|
// The `cache` option is available in @fideus-labs/fizarrita >=1.2.0.
|
|
122
123
|
const zarrOpts = this._cache
|
|
123
|
-
? { cache: this._cache } as Record<string, unknown>
|
|
124
|
-
: undefined
|
|
125
|
-
const result = await zarrGet(ngffImage.data, selection, zarrOpts)
|
|
124
|
+
? ({ cache: this._cache } as Record<string, unknown>)
|
|
125
|
+
: undefined
|
|
126
|
+
const result = await zarrGet(ngffImage.data, selection, zarrOpts)
|
|
126
127
|
|
|
127
128
|
const fetchResult: RegionFetchResult = {
|
|
128
129
|
data: result.data as TypedArray,
|
|
129
130
|
shape: result.shape,
|
|
130
131
|
stride: result.stride,
|
|
131
|
-
}
|
|
132
|
+
}
|
|
132
133
|
|
|
133
|
-
pendingRequest.resolve(fetchResult)
|
|
134
|
-
return fetchResult
|
|
134
|
+
pendingRequest.resolve(fetchResult)
|
|
135
|
+
return fetchResult
|
|
135
136
|
} catch (error) {
|
|
136
|
-
const err = error instanceof Error ? error : new Error(String(error))
|
|
137
|
-
pendingRequest.reject(err)
|
|
138
|
-
throw err
|
|
137
|
+
const err = error instanceof Error ? error : new Error(String(error))
|
|
138
|
+
pendingRequest.reject(err)
|
|
139
|
+
throw err
|
|
139
140
|
} finally {
|
|
140
|
-
this.pending.delete(key)
|
|
141
|
+
this.pending.delete(key)
|
|
141
142
|
}
|
|
142
143
|
}
|
|
143
144
|
|
|
@@ -159,9 +160,9 @@ export class RegionCoalescer {
|
|
|
159
160
|
): Promise<RegionFetchResult[]> {
|
|
160
161
|
return Promise.all(
|
|
161
162
|
regions.map((region) =>
|
|
162
|
-
this.fetchRegion(ngffImage, levelIndex, region, requesterId)
|
|
163
|
+
this.fetchRegion(ngffImage, levelIndex, region, requesterId),
|
|
163
164
|
),
|
|
164
|
-
)
|
|
165
|
+
)
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
/**
|
|
@@ -172,8 +173,8 @@ export class RegionCoalescer {
|
|
|
172
173
|
levelIndex: number,
|
|
173
174
|
region: PixelRegion,
|
|
174
175
|
): boolean {
|
|
175
|
-
const key = this.makeKey(ngffImage.data.path, levelIndex, region)
|
|
176
|
-
return this.pending.has(key)
|
|
176
|
+
const key = this.makeKey(ngffImage.data.path, levelIndex, region)
|
|
177
|
+
return this.pending.has(key)
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
/**
|
|
@@ -185,8 +186,8 @@ export class RegionCoalescer {
|
|
|
185
186
|
levelIndex: number,
|
|
186
187
|
region: PixelRegion,
|
|
187
188
|
): Set<string> | undefined {
|
|
188
|
-
const key = this.makeKey(ngffImage.data.path, levelIndex, region)
|
|
189
|
-
return this.pending.get(key)?.requesters
|
|
189
|
+
const key = this.makeKey(ngffImage.data.path, levelIndex, region)
|
|
190
|
+
return this.pending.get(key)?.requesters
|
|
190
191
|
}
|
|
191
192
|
|
|
192
193
|
/**
|
|
@@ -195,16 +196,16 @@ export class RegionCoalescer {
|
|
|
195
196
|
async onIdle(): Promise<void> {
|
|
196
197
|
// Wait for all in-flight requests to settle
|
|
197
198
|
const promises = Array.from(this.pending.values()).map((p) =>
|
|
198
|
-
p.promise.catch(() => {})
|
|
199
|
-
)
|
|
200
|
-
await Promise.all(promises)
|
|
199
|
+
p.promise.catch(() => {}),
|
|
200
|
+
)
|
|
201
|
+
await Promise.all(promises)
|
|
201
202
|
}
|
|
202
203
|
|
|
203
204
|
/**
|
|
204
205
|
* Get the number of pending requests (unique region requests).
|
|
205
206
|
*/
|
|
206
207
|
get pendingCount(): number {
|
|
207
|
-
return this.pending.size
|
|
208
|
+
return this.pending.size
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
/**
|
|
@@ -212,6 +213,6 @@ export class RegionCoalescer {
|
|
|
212
213
|
* Note: Does not resolve or reject pending promises.
|
|
213
214
|
*/
|
|
214
215
|
clear(): void {
|
|
215
|
-
this.pending.clear()
|
|
216
|
+
this.pending.clear()
|
|
216
217
|
}
|
|
217
218
|
}
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: Copyright (c) Fideus Labs LLC
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
-
import type { Multiscales, NgffImage } from "@fideus-labs/ngff-zarr"
|
|
4
|
+
import type { Multiscales, NgffImage } from "@fideus-labs/ngff-zarr"
|
|
5
|
+
|
|
6
|
+
import { clipPlanesToPixelRegion } from "./ClipPlanes.js"
|
|
5
7
|
import type {
|
|
6
8
|
ClipPlanes,
|
|
7
9
|
PixelRegion,
|
|
8
10
|
ResolutionSelection,
|
|
9
11
|
VolumeBounds,
|
|
10
|
-
} from "./types.js"
|
|
11
|
-
import { clipPlanesToPixelRegion } from "./ClipPlanes.js";
|
|
12
|
+
} from "./types.js"
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Orthogonal axis index in [z, y, x] order.
|
|
15
16
|
* 0 = Z (axial view), 1 = Y (coronal view), 2 = X (sagittal view)
|
|
16
17
|
*/
|
|
17
|
-
export type OrthogonalAxis = 0 | 1 | 2
|
|
18
|
+
export type OrthogonalAxis = 0 | 1 | 2
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Select the appropriate resolution level based on pixel budget and clip planes.
|
|
@@ -38,57 +39,57 @@ export function selectResolution(
|
|
|
38
39
|
volumeBounds: VolumeBounds,
|
|
39
40
|
viewportBounds?: VolumeBounds,
|
|
40
41
|
): ResolutionSelection {
|
|
41
|
-
const images = multiscales.images
|
|
42
|
+
const images = multiscales.images
|
|
42
43
|
|
|
43
44
|
// Try each resolution from highest to lowest
|
|
44
45
|
for (let i = 0; i < images.length; i++) {
|
|
45
|
-
const image = images[i]
|
|
46
|
+
const image = images[i]
|
|
46
47
|
const region = clipPlanesToPixelRegion(
|
|
47
48
|
clipPlanes,
|
|
48
49
|
volumeBounds,
|
|
49
50
|
image,
|
|
50
51
|
viewportBounds,
|
|
51
|
-
)
|
|
52
|
-
const alignedRegion = alignRegionToChunks(region, image)
|
|
52
|
+
)
|
|
53
|
+
const alignedRegion = alignRegionToChunks(region, image)
|
|
53
54
|
|
|
54
55
|
const dimensions: [number, number, number] = [
|
|
55
56
|
alignedRegion.end[0] - alignedRegion.start[0],
|
|
56
57
|
alignedRegion.end[1] - alignedRegion.start[1],
|
|
57
58
|
alignedRegion.end[2] - alignedRegion.start[2],
|
|
58
|
-
]
|
|
59
|
+
]
|
|
59
60
|
|
|
60
|
-
const pixelCount = dimensions[0] * dimensions[1] * dimensions[2]
|
|
61
|
+
const pixelCount = dimensions[0] * dimensions[1] * dimensions[2]
|
|
61
62
|
|
|
62
63
|
if (pixelCount <= maxPixels) {
|
|
63
64
|
return {
|
|
64
65
|
levelIndex: i,
|
|
65
66
|
dimensions,
|
|
66
67
|
pixelCount,
|
|
67
|
-
}
|
|
68
|
+
}
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
// Fall back to lowest resolution
|
|
72
|
-
const lowestImage = images[images.length - 1]
|
|
73
|
+
const lowestImage = images[images.length - 1]
|
|
73
74
|
const region = clipPlanesToPixelRegion(
|
|
74
75
|
clipPlanes,
|
|
75
76
|
volumeBounds,
|
|
76
77
|
lowestImage,
|
|
77
78
|
viewportBounds,
|
|
78
|
-
)
|
|
79
|
-
const alignedRegion = alignRegionToChunks(region, lowestImage)
|
|
79
|
+
)
|
|
80
|
+
const alignedRegion = alignRegionToChunks(region, lowestImage)
|
|
80
81
|
|
|
81
82
|
const dimensions: [number, number, number] = [
|
|
82
83
|
alignedRegion.end[0] - alignedRegion.start[0],
|
|
83
84
|
alignedRegion.end[1] - alignedRegion.start[1],
|
|
84
85
|
alignedRegion.end[2] - alignedRegion.start[2],
|
|
85
|
-
]
|
|
86
|
+
]
|
|
86
87
|
|
|
87
88
|
return {
|
|
88
89
|
levelIndex: images.length - 1,
|
|
89
90
|
dimensions,
|
|
90
91
|
pixelCount: dimensions[0] * dimensions[1] * dimensions[2],
|
|
91
|
-
}
|
|
92
|
+
}
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
/**
|
|
@@ -98,21 +99,21 @@ export function selectResolution(
|
|
|
98
99
|
* @returns Chunk shape as [z, y, x]
|
|
99
100
|
*/
|
|
100
101
|
export function getChunkShape(ngffImage: NgffImage): [number, number, number] {
|
|
101
|
-
const chunks = ngffImage.data.chunks
|
|
102
|
-
const dims = ngffImage.dims
|
|
102
|
+
const chunks = ngffImage.data.chunks
|
|
103
|
+
const dims = ngffImage.dims
|
|
103
104
|
|
|
104
105
|
// Find z, y, x indices in dims
|
|
105
|
-
const zIdx = dims.indexOf("z")
|
|
106
|
-
const yIdx = dims.indexOf("y")
|
|
107
|
-
const xIdx = dims.indexOf("x")
|
|
106
|
+
const zIdx = dims.indexOf("z")
|
|
107
|
+
const yIdx = dims.indexOf("y")
|
|
108
|
+
const xIdx = dims.indexOf("x")
|
|
108
109
|
|
|
109
110
|
if (zIdx === -1 || yIdx === -1 || xIdx === -1) {
|
|
110
111
|
// Fallback: assume last 3 dimensions are z, y, x
|
|
111
|
-
const n = chunks.length
|
|
112
|
-
return [chunks[n - 3] || 1, chunks[n - 2] || 1, chunks[n - 1] || 1]
|
|
112
|
+
const n = chunks.length
|
|
113
|
+
return [chunks[n - 3] || 1, chunks[n - 2] || 1, chunks[n - 1] || 1]
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
return [chunks[zIdx], chunks[yIdx], chunks[xIdx]]
|
|
116
|
+
return [chunks[zIdx], chunks[yIdx], chunks[xIdx]]
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
/**
|
|
@@ -122,21 +123,21 @@ export function getChunkShape(ngffImage: NgffImage): [number, number, number] {
|
|
|
122
123
|
* @returns Shape as [z, y, x]
|
|
123
124
|
*/
|
|
124
125
|
export function getVolumeShape(ngffImage: NgffImage): [number, number, number] {
|
|
125
|
-
const shape = ngffImage.data.shape
|
|
126
|
-
const dims = ngffImage.dims
|
|
126
|
+
const shape = ngffImage.data.shape
|
|
127
|
+
const dims = ngffImage.dims
|
|
127
128
|
|
|
128
129
|
// Find z, y, x indices in dims
|
|
129
|
-
const zIdx = dims.indexOf("z")
|
|
130
|
-
const yIdx = dims.indexOf("y")
|
|
131
|
-
const xIdx = dims.indexOf("x")
|
|
130
|
+
const zIdx = dims.indexOf("z")
|
|
131
|
+
const yIdx = dims.indexOf("y")
|
|
132
|
+
const xIdx = dims.indexOf("x")
|
|
132
133
|
|
|
133
134
|
if (zIdx === -1 || yIdx === -1 || xIdx === -1) {
|
|
134
135
|
// Fallback: assume last 3 dimensions are z, y, x
|
|
135
|
-
const n = shape.length
|
|
136
|
-
return [shape[n - 3] || 1, shape[n - 2] || 1, shape[n - 1] || 1]
|
|
136
|
+
const n = shape.length
|
|
137
|
+
return [shape[n - 3] || 1, shape[n - 2] || 1, shape[n - 1] || 1]
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
return [shape[zIdx], shape[yIdx], shape[xIdx]]
|
|
140
|
+
return [shape[zIdx], shape[yIdx], shape[xIdx]]
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
/**
|
|
@@ -151,15 +152,15 @@ export function alignRegionToChunks(
|
|
|
151
152
|
region: PixelRegion,
|
|
152
153
|
ngffImage: NgffImage,
|
|
153
154
|
): PixelRegion {
|
|
154
|
-
const chunkShape = getChunkShape(ngffImage)
|
|
155
|
-
const volumeShape = getVolumeShape(ngffImage)
|
|
155
|
+
const chunkShape = getChunkShape(ngffImage)
|
|
156
|
+
const volumeShape = getVolumeShape(ngffImage)
|
|
156
157
|
|
|
157
158
|
// Align start down to chunk boundary
|
|
158
159
|
const alignedStart: [number, number, number] = [
|
|
159
160
|
Math.floor(region.start[0] / chunkShape[0]) * chunkShape[0],
|
|
160
161
|
Math.floor(region.start[1] / chunkShape[1]) * chunkShape[1],
|
|
161
162
|
Math.floor(region.start[2] / chunkShape[2]) * chunkShape[2],
|
|
162
|
-
]
|
|
163
|
+
]
|
|
163
164
|
|
|
164
165
|
// Align end up to chunk boundary (but don't exceed volume size)
|
|
165
166
|
const alignedEnd: [number, number, number] = [
|
|
@@ -175,12 +176,12 @@ export function alignRegionToChunks(
|
|
|
175
176
|
Math.ceil(region.end[2] / chunkShape[2]) * chunkShape[2],
|
|
176
177
|
volumeShape[2],
|
|
177
178
|
),
|
|
178
|
-
]
|
|
179
|
+
]
|
|
179
180
|
|
|
180
181
|
return {
|
|
181
182
|
start: alignedStart,
|
|
182
183
|
end: alignedEnd,
|
|
183
|
-
}
|
|
184
|
+
}
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
/**
|
|
@@ -190,7 +191,7 @@ export function alignRegionToChunks(
|
|
|
190
191
|
* @returns Middle resolution level index
|
|
191
192
|
*/
|
|
192
193
|
export function getMiddleResolutionIndex(multiscales: Multiscales): number {
|
|
193
|
-
return Math.floor(multiscales.images.length / 2)
|
|
194
|
+
return Math.floor(multiscales.images.length / 2)
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
/**
|
|
@@ -206,17 +207,17 @@ export function calculateUpsampleFactor(
|
|
|
206
207
|
fromLevel: number,
|
|
207
208
|
toLevel: number,
|
|
208
209
|
): [number, number, number] {
|
|
209
|
-
const fromImage = multiscales.images[fromLevel]
|
|
210
|
-
const toImage = multiscales.images[toLevel]
|
|
210
|
+
const fromImage = multiscales.images[fromLevel]
|
|
211
|
+
const toImage = multiscales.images[toLevel]
|
|
211
212
|
|
|
212
|
-
const fromShape = getVolumeShape(fromImage)
|
|
213
|
-
const toShape = getVolumeShape(toImage)
|
|
213
|
+
const fromShape = getVolumeShape(fromImage)
|
|
214
|
+
const toShape = getVolumeShape(toImage)
|
|
214
215
|
|
|
215
216
|
return [
|
|
216
217
|
toShape[0] / fromShape[0],
|
|
217
218
|
toShape[1] / fromShape[1],
|
|
218
219
|
toShape[2] / fromShape[2],
|
|
219
|
-
]
|
|
220
|
+
]
|
|
220
221
|
}
|
|
221
222
|
|
|
222
223
|
/**
|
|
@@ -226,7 +227,7 @@ export function getFullVolumeDimensions(
|
|
|
226
227
|
multiscales: Multiscales,
|
|
227
228
|
levelIndex: number,
|
|
228
229
|
): [number, number, number] {
|
|
229
|
-
return getVolumeShape(multiscales.images[levelIndex])
|
|
230
|
+
return getVolumeShape(multiscales.images[levelIndex])
|
|
230
231
|
}
|
|
231
232
|
|
|
232
233
|
/**
|
|
@@ -257,69 +258,67 @@ export function select2DResolution(
|
|
|
257
258
|
orthogonalAxis: OrthogonalAxis,
|
|
258
259
|
viewportBounds?: VolumeBounds,
|
|
259
260
|
): ResolutionSelection {
|
|
260
|
-
const images = multiscales.images
|
|
261
|
+
const images = multiscales.images
|
|
261
262
|
|
|
262
263
|
// Try each resolution from highest to lowest
|
|
263
264
|
for (let i = 0; i < images.length; i++) {
|
|
264
|
-
const image = images[i]
|
|
265
|
+
const image = images[i]
|
|
265
266
|
const region = clipPlanesToPixelRegion(
|
|
266
267
|
clipPlanes,
|
|
267
268
|
volumeBounds,
|
|
268
269
|
image,
|
|
269
270
|
viewportBounds,
|
|
270
|
-
)
|
|
271
|
-
const alignedRegion = alignRegionToChunks(region, image)
|
|
271
|
+
)
|
|
272
|
+
const alignedRegion = alignRegionToChunks(region, image)
|
|
272
273
|
|
|
273
274
|
const dimensions: [number, number, number] = [
|
|
274
275
|
alignedRegion.end[0] - alignedRegion.start[0],
|
|
275
276
|
alignedRegion.end[1] - alignedRegion.start[1],
|
|
276
277
|
alignedRegion.end[2] - alignedRegion.start[2],
|
|
277
|
-
]
|
|
278
|
+
]
|
|
278
279
|
|
|
279
280
|
// Count the full slab volume: in-plane area × chunk depth along the
|
|
280
281
|
// orthogonal axis. The slab is always one chunk thick, so we use the
|
|
281
282
|
// chunk shape rather than the full volume extent in that axis.
|
|
282
|
-
const chunkShape = getChunkShape(image)
|
|
283
|
-
const slabDepth = chunkShape[orthogonalAxis]
|
|
284
|
-
const inPlaneAxes = ([0, 1, 2] as const).filter((a) =>
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const slabVoxelCount = dimensions[inPlaneAxes[0]] *
|
|
288
|
-
dimensions[inPlaneAxes[1]] * slabDepth;
|
|
283
|
+
const chunkShape = getChunkShape(image)
|
|
284
|
+
const slabDepth = chunkShape[orthogonalAxis]
|
|
285
|
+
const inPlaneAxes = ([0, 1, 2] as const).filter((a) => a !== orthogonalAxis)
|
|
286
|
+
const slabVoxelCount =
|
|
287
|
+
dimensions[inPlaneAxes[0]] * dimensions[inPlaneAxes[1]] * slabDepth
|
|
289
288
|
|
|
290
289
|
if (slabVoxelCount <= maxPixels) {
|
|
291
290
|
return {
|
|
292
291
|
levelIndex: i,
|
|
293
292
|
dimensions,
|
|
294
293
|
pixelCount: slabVoxelCount,
|
|
295
|
-
}
|
|
294
|
+
}
|
|
296
295
|
}
|
|
297
296
|
}
|
|
298
297
|
|
|
299
298
|
// Fall back to lowest resolution
|
|
300
|
-
const lowestImage = images[images.length - 1]
|
|
299
|
+
const lowestImage = images[images.length - 1]
|
|
301
300
|
const region = clipPlanesToPixelRegion(
|
|
302
301
|
clipPlanes,
|
|
303
302
|
volumeBounds,
|
|
304
303
|
lowestImage,
|
|
305
304
|
viewportBounds,
|
|
306
|
-
)
|
|
307
|
-
const alignedRegion = alignRegionToChunks(region, lowestImage)
|
|
305
|
+
)
|
|
306
|
+
const alignedRegion = alignRegionToChunks(region, lowestImage)
|
|
308
307
|
|
|
309
308
|
const dimensions: [number, number, number] = [
|
|
310
309
|
alignedRegion.end[0] - alignedRegion.start[0],
|
|
311
310
|
alignedRegion.end[1] - alignedRegion.start[1],
|
|
312
311
|
alignedRegion.end[2] - alignedRegion.start[2],
|
|
313
|
-
]
|
|
312
|
+
]
|
|
314
313
|
|
|
315
|
-
const chunkShape = getChunkShape(lowestImage)
|
|
316
|
-
const slabDepth = chunkShape[orthogonalAxis]
|
|
317
|
-
const inPlaneAxes = ([0, 1, 2] as const).filter((a) => a !== orthogonalAxis)
|
|
314
|
+
const chunkShape = getChunkShape(lowestImage)
|
|
315
|
+
const slabDepth = chunkShape[orthogonalAxis]
|
|
316
|
+
const inPlaneAxes = ([0, 1, 2] as const).filter((a) => a !== orthogonalAxis)
|
|
318
317
|
|
|
319
318
|
return {
|
|
320
319
|
levelIndex: images.length - 1,
|
|
321
320
|
dimensions,
|
|
322
|
-
pixelCount:
|
|
323
|
-
slabDepth,
|
|
324
|
-
}
|
|
321
|
+
pixelCount:
|
|
322
|
+
dimensions[inPlaneAxes[0]] * dimensions[inPlaneAxes[1]] * slabDepth,
|
|
323
|
+
}
|
|
325
324
|
}
|