@fideus-labs/fidnii 0.5.0 → 0.7.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.
@@ -44,15 +44,17 @@ const SPATIAL_DIM_MAP: Record<string, 0 | 1 | 2> = {
44
44
  * each zarr dimension to the correct slice:
45
45
  * - `"z"`, `"y"`, `"x"` → sliced by the corresponding PixelRegion axis
46
46
  * - `"c"` (channel) → `null` (select all components)
47
- * - `"t"` (time) → `0` (first timepoint)
47
+ * - `"t"` (time) → `timeIndex` (selects a single time point)
48
48
  *
49
49
  * @param dims - Dimension names from NgffImage (e.g. `["y", "x", "c"]`)
50
50
  * @param region - The pixel region in `[z, y, x]` order
51
+ * @param timeIndex - Time point index to select (default: 0)
51
52
  * @returns Selection array matching the zarr dim order
52
53
  */
53
54
  export function buildSelection(
54
55
  dims: string[],
55
56
  region: PixelRegion,
57
+ timeIndex: number = 0,
56
58
  ): (zarr.Slice | number | null)[] {
57
59
  return dims.map((dim) => {
58
60
  const spatialIdx = SPATIAL_DIM_MAP[dim]
@@ -60,7 +62,7 @@ export function buildSelection(
60
62
  return zarr.slice(region.start[spatialIdx], region.end[spatialIdx])
61
63
  }
62
64
  if (dim === "c") return null // select all channels
63
- if (dim === "t") return 0 // first timepoint
65
+ if (dim === "t") return timeIndex // select specified time point
64
66
  // Unknown dimension — select all to avoid data loss
65
67
  return null
66
68
  })
@@ -96,16 +98,18 @@ export class RegionCoalescer {
96
98
  }
97
99
 
98
100
  /**
99
- * Generate a unique key for a request based on image path, level index, and region.
101
+ * Generate a unique key for a request based on image path, level index,
102
+ * region, and time index.
100
103
  */
101
104
  private makeKey(
102
105
  imagePath: string,
103
106
  levelIndex: number,
104
107
  region: PixelRegion,
108
+ timeIndex: number,
105
109
  ): string {
106
110
  const start = region.start.join(",")
107
111
  const end = region.end.join(",")
108
- return `${imagePath}:${levelIndex}:${start}:${end}`
112
+ return `${imagePath}:${levelIndex}:${start}:${end}:t${timeIndex}`
109
113
  }
110
114
 
111
115
  /**
@@ -115,6 +119,8 @@ export class RegionCoalescer {
115
119
  * @param levelIndex - The resolution level index
116
120
  * @param region - The pixel region to fetch
117
121
  * @param requesterId - ID of the requester (e.g., 'zoom', 'crop-change', 'progressive-load')
122
+ * @param timeIndex - Time point index to fetch (default: 0)
123
+ * @param signal - Optional AbortSignal to cancel the fetch
118
124
  * @returns The fetched region data
119
125
  */
120
126
  async fetchRegion(
@@ -122,8 +128,10 @@ export class RegionCoalescer {
122
128
  levelIndex: number,
123
129
  region: PixelRegion,
124
130
  requesterId: string = "default",
131
+ timeIndex: number = 0,
132
+ signal?: AbortSignal,
125
133
  ): Promise<RegionFetchResult> {
126
- const key = this.makeKey(ngffImage.data.path, levelIndex, region)
134
+ const key = this.makeKey(ngffImage.data.path, levelIndex, region, timeIndex)
127
135
 
128
136
  // Check if there's already a pending request for this data
129
137
  const existing = this.pending.get(key)
@@ -156,14 +164,21 @@ export class RegionCoalescer {
156
164
  // Build a dim-aware selection that maps the [z, y, x] PixelRegion
157
165
  // to the actual zarr dimension order. Non-spatial dims are handled:
158
166
  // "c" (channel) → null (fetch all components)
159
- // "t" (time) → 0 (first timepoint, reduces dimension)
160
- const selection = buildSelection(ngffImage.dims, region)
167
+ // "t" (time) → timeIndex (selects single time point)
168
+ const selection = buildSelection(ngffImage.dims, region, timeIndex)
161
169
  // Pass the chunk cache to fizarrita's getWorker via zarrGet.
162
170
  // The `cache` option is available in @fideus-labs/fizarrita >=1.2.0.
163
- const zarrOpts = this._cache
164
- ? ({ cache: this._cache } as Record<string, unknown>)
165
- : undefined
166
- const result = await zarrGet(ngffImage.data, selection, zarrOpts)
171
+ // When an AbortSignal is provided, forward it through `opts` so
172
+ // that the underlying FetchStore passes it as `RequestInit.signal`,
173
+ // allowing in-flight HTTP requests to be cancelled.
174
+ const zarrOpts: Record<string, unknown> = {}
175
+ if (this._cache) zarrOpts.cache = this._cache
176
+ if (signal) zarrOpts.opts = { signal }
177
+ const result = await zarrGet(
178
+ ngffImage.data,
179
+ selection,
180
+ Object.keys(zarrOpts).length > 0 ? zarrOpts : undefined,
181
+ )
167
182
 
168
183
  const fetchResult: RegionFetchResult = {
169
184
  data: result.data as TypedArray,
@@ -190,6 +205,7 @@ export class RegionCoalescer {
190
205
  * @param levelIndex - The resolution level index
191
206
  * @param regions - Array of pixel regions to fetch
192
207
  * @param requesterId - ID of the requester
208
+ * @param timeIndex - Time point index to fetch (default: 0)
193
209
  * @returns Array of fetched region data
194
210
  */
195
211
  async fetchRegions(
@@ -197,10 +213,11 @@ export class RegionCoalescer {
197
213
  levelIndex: number,
198
214
  regions: PixelRegion[],
199
215
  requesterId: string = "default",
216
+ timeIndex: number = 0,
200
217
  ): Promise<RegionFetchResult[]> {
201
218
  return Promise.all(
202
219
  regions.map((region) =>
203
- this.fetchRegion(ngffImage, levelIndex, region, requesterId),
220
+ this.fetchRegion(ngffImage, levelIndex, region, requesterId, timeIndex),
204
221
  ),
205
222
  )
206
223
  }
@@ -212,8 +229,9 @@ export class RegionCoalescer {
212
229
  ngffImage: NgffImage,
213
230
  levelIndex: number,
214
231
  region: PixelRegion,
232
+ timeIndex: number = 0,
215
233
  ): boolean {
216
- const key = this.makeKey(ngffImage.data.path, levelIndex, region)
234
+ const key = this.makeKey(ngffImage.data.path, levelIndex, region, timeIndex)
217
235
  return this.pending.has(key)
218
236
  }
219
237
 
@@ -225,8 +243,9 @@ export class RegionCoalescer {
225
243
  ngffImage: NgffImage,
226
244
  levelIndex: number,
227
245
  region: PixelRegion,
246
+ timeIndex: number = 0,
228
247
  ): Set<string> | undefined {
229
- const key = this.makeKey(ngffImage.data.path, levelIndex, region)
248
+ const key = this.makeKey(ngffImage.data.path, levelIndex, region, timeIndex)
230
249
  return this.pending.get(key)?.requesters
231
250
  }
232
251
 
package/src/events.ts CHANGED
@@ -112,6 +112,24 @@ export interface OMEZarrNVImageEventMap {
112
112
  levelIndex: number
113
113
  trigger: PopulateTrigger
114
114
  }
115
+
116
+ /**
117
+ * Fired when the active time index changes.
118
+ *
119
+ * The `cached` flag indicates whether the frame was served instantly
120
+ * from the pre-fetch cache (`true`) or required a fresh zarr fetch
121
+ * (`false`).
122
+ */
123
+ timeChange: {
124
+ /** New time index (0-based) */
125
+ index: number
126
+ /** Physical time value at the new index */
127
+ timeValue: number
128
+ /** Previous time index */
129
+ previousIndex: number
130
+ /** `true` if the frame was served from the pre-fetch cache */
131
+ cached: boolean
132
+ }
115
133
  }
116
134
 
117
135
  /**
package/src/index.ts CHANGED
@@ -38,8 +38,10 @@ export type { DeflatePool, TiffStoreOptions } from "@fideus-labs/fiff"
38
38
  export { TiffStore } from "@fideus-labs/fiff"
39
39
  // Re-export Methods enum so consumers can check isLabelImage or compare method values
40
40
  export { Methods } from "@fideus-labs/ngff-zarr"
41
- // Worker pool lifecycle (re-exported from ngff-zarr)
41
+ // Worker pool lifecycle & configuration (re-exported from ngff-zarr)
42
42
  export {
43
+ config as ngffZarrConfig,
44
+ setWorkerPoolSize,
43
45
  terminateOmeroWorkerPool,
44
46
  terminateWorkerPool,
45
47
  } from "@fideus-labs/ngff-zarr/browser"
@@ -99,6 +101,7 @@ export {
99
101
  // Types
100
102
  export type {
101
103
  AttachedNiivueState,
104
+ CachedTimeFrame,
102
105
  ChannelInfo,
103
106
  ChunkAlignedRegion,
104
107
  ChunkCache,
@@ -110,6 +113,8 @@ export type {
110
113
  ResolutionSelection,
111
114
  SlabBufferState,
112
115
  SlabSliceType,
116
+ TimeAxisInfo,
117
+ TimeUnit,
113
118
  TypedArray,
114
119
  VolumeBounds,
115
120
  ZarrDtype,
package/src/types.ts CHANGED
@@ -165,6 +165,24 @@ export interface OMEZarrNVImageOptions {
165
165
  * This option has no effect on 3D volumes (images with a `"z"` axis).
166
166
  */
167
167
  flipY2D?: boolean
168
+ /**
169
+ * Initial time index to display (default: 0, or `omero.defaultT` if available).
170
+ *
171
+ * Only relevant for datasets with a `"t"` (time) dimension. Ignored
172
+ * when the dataset has no time axis.
173
+ */
174
+ timeIndex?: number
175
+ /**
176
+ * Number of adjacent time frames to pre-fetch in each direction (default: 2).
177
+ *
178
+ * When the user navigates to time index `t`, frames
179
+ * `[t - timePrefetchCount, t + timePrefetchCount]` are fetched in the
180
+ * background so that subsequent scrubbing can swap frames instantly from
181
+ * the cache. Set to `0` to disable pre-fetching.
182
+ *
183
+ * Only relevant for datasets with a `"t"` (time) dimension.
184
+ */
185
+ timePrefetchCount?: number
168
186
  }
169
187
 
170
188
  /**
@@ -278,18 +296,8 @@ export interface AttachedNiivueState {
278
296
  nv: Niivue
279
297
  /** The current slice type of this NV instance */
280
298
  currentSliceType: SLICE_TYPE
281
- /** Previous onLocationChange callback (to chain) */
282
- previousOnLocationChange?: (location: unknown) => void
283
- /** Previous onOptsChange callback (to chain) */
284
- previousOnOptsChange?: (
285
- propertyName: string,
286
- newValue: unknown,
287
- oldValue: unknown,
288
- ) => void
289
- /** Previous onMouseUp callback (to chain, for viewport-aware mode) */
290
- previousOnMouseUp?: (data: unknown) => void
291
- /** Previous onZoom3DChange callback (to chain, for viewport-aware mode) */
292
- previousOnZoom3DChange?: (zoom: number) => void
299
+ /** AbortController for niivue addEventListener listeners (sliceTypeChange, locationChange) */
300
+ eventAbortController: AbortController
293
301
  /** AbortController for viewport-aware event listeners (wheel, etc.) */
294
302
  viewportAbortController?: AbortController
295
303
  /** AbortController for the 3D zoom override wheel listener */
@@ -328,6 +336,74 @@ export const NiftiDataType = {
328
336
  export type NiftiDataTypeCode =
329
337
  (typeof NiftiDataType)[keyof typeof NiftiDataType]
330
338
 
339
+ /**
340
+ * Time units supported by OME-Zarr NGFF.
341
+ *
342
+ * Matches the UDUNITS-2 time units recognized by the OME-Zarr
343
+ * specification for the `"time"` axis type.
344
+ */
345
+ export type TimeUnit =
346
+ | "attosecond"
347
+ | "centisecond"
348
+ | "day"
349
+ | "decisecond"
350
+ | "exasecond"
351
+ | "femtosecond"
352
+ | "gigasecond"
353
+ | "hectosecond"
354
+ | "hour"
355
+ | "kilosecond"
356
+ | "megasecond"
357
+ | "microsecond"
358
+ | "millisecond"
359
+ | "minute"
360
+ | "nanosecond"
361
+ | "petasecond"
362
+ | "picosecond"
363
+ | "second"
364
+ | "terasecond"
365
+ | "yoctosecond"
366
+ | "yottasecond"
367
+ | "zeptosecond"
368
+ | "zettasecond"
369
+
370
+ /**
371
+ * Time axis metadata extracted from OME-Zarr multiscales.
372
+ *
373
+ * Present only when the dataset has a `"t"` dimension. Contains
374
+ * the number of time points, physical time step, and unit
375
+ * information needed for time navigation.
376
+ */
377
+ export interface TimeAxisInfo {
378
+ /** Number of time points (from the zarr array shape along `"t"`) */
379
+ readonly count: number
380
+ /** Index of the `"t"` dimension in `NgffImage.dims` */
381
+ readonly dimIndex: number
382
+ /** Physical time step between adjacent indices (from `scale.t`) */
383
+ readonly step: number
384
+ /** Time origin offset (from `translation.t`) */
385
+ readonly origin: number
386
+ /** Time unit (e.g., `"second"`, `"millisecond"`), or `undefined` if not specified */
387
+ readonly unit: TimeUnit | undefined
388
+ }
389
+
390
+ /**
391
+ * A pre-fetched 3D frame ready for instant buffer swap.
392
+ *
393
+ * Used by the time frame cache to avoid re-fetching when scrubbing
394
+ * through adjacent time points.
395
+ */
396
+ export interface CachedTimeFrame {
397
+ /** The typed array pixel data for this frame */
398
+ data: TypedArray
399
+ /** Spatial shape in `[z, y, x]` order */
400
+ shape: [number, number, number]
401
+ /** The resolution level this was fetched at */
402
+ levelIndex: number
403
+ /** The chunk-aligned pixel region this was fetched for */
404
+ region: ChunkAlignedRegion
405
+ }
406
+
331
407
  /**
332
408
  * Information about a channel (component) dimension in the image.
333
409
  */