omezarr-tilesource 0.4.0 → 0.6.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/README.md CHANGED
@@ -22,6 +22,14 @@ Using pnpm:
22
22
  pnpm add omezarr-tilesource
23
23
  ```
24
24
 
25
+ The package is distributed as an ES module for use with a bundler (e.g. Vite).
26
+ [zarrita](https://github.com/manzt/zarrita.js),
27
+ [@zarrita/storage](https://github.com/manzt/zarrita.js) and
28
+ [ome-zarr.js](https://github.com/BioNGFF/ome-zarr.js) are peer dependencies
29
+ (installed automatically by pnpm and npm 7 or newer) and are not bundled, so
30
+ the app and the tile source share a single copy, e.g. for passing `NgffImage`
31
+ instances loaded by the app.
32
+
25
33
  ## Usage
26
34
 
27
35
  ```javascript
@@ -30,37 +38,46 @@ import { OMEZarrTileSource } from "omezarr-tilesource";
30
38
 
31
39
  const url = ...;
32
40
 
33
- // configuration with URL (only works with zipped OME-Zarr URLs ending with .ozx)
41
+ // register the tile source and its data type converters with OpenSeadragon
42
+ // (required for inline configurations and URLs, see "Data pipeline" below)
43
+ OMEZarrTileSource.enable(OpenSeadragon);
44
+
45
+ // configuration with URL (only works with zipped OME-Zarr URLs, path ending in .ozx)
34
46
  const tileSource1 = url;
35
47
 
36
- // inline configuration with options object (requires prior enabling, see below)
37
- OMEZarrTileSource.enable(OpenSeadragon);
48
+ // inline configuration with options object
38
49
  const tileSource2 = {
39
50
  type: "ome-zarr",
40
51
  url: url,
41
- // zip: undefined, // undefined = OME-Zarr ZIP auto-detection based on .ozx suffix
42
- // c: undefined, // undefined = composite of all active channels (requires dataType "context2d")
43
- // z: undefined, // undefined = omero rdefs default (middle z-slice if missing)
52
+ // zip: undefined, // undefined = OME-Zarr ZIP auto-detection based on .ozx path suffix
44
53
  // t: undefined, // undefined = omero rdefs default (middle timepoint if missing)
45
- // dataType: undefined, // "context2d" (default, rendered tiles) or "ome-zarr" (raw single-channel chunks)
54
+ // z: undefined, // undefined = omero rdefs default (middle z-slice if missing)
55
+ // c: undefined, // channel index or array of indices; undefined = all active channels
56
+ // range: undefined, // contrast limits [min, max] or array thereof; undefined = omero windows
57
+ // color: undefined, // RGB color or array of colors; undefined = omero channel colors
58
+ // lutOrColorMap: undefined, // LUT/color map or array thereof; undefined = omero LUTs
59
+ // inverted: undefined, // boolean or array of booleans; undefined = omero inversion
46
60
  // autoBoost: undefined // boost brightness of dark tiles (default false)
47
61
  };
48
62
 
49
63
  // direct instantiation with URL (works with any OME-Zarr storage backend)
50
64
  const tileSource3 = new OMEZarrTileSource(url);
51
65
 
52
- // direct instantiation with options object (no prior enabling required)
66
+ // direct instantiation with options object
53
67
  const tileSource4 = new OMEZarrTileSource({
54
68
  url: url,
55
- // zip: undefined, // undefined = OME-Zarr ZIP auto-detection based on .ozx suffix
56
- // c: undefined, // undefined = composite of all active channels (requires dataType "context2d")
57
- // z: undefined, // undefined = omero rdefs default (middle z-slice if missing)
69
+ // zip: undefined, // undefined = OME-Zarr ZIP auto-detection based on .ozx path suffix
58
70
  // t: undefined, // undefined = omero rdefs default (middle timepoint if missing)
59
- // dataType: undefined, // "context2d" (default, rendered tiles) or "ome-zarr" (raw single-channel chunks)
71
+ // z: undefined, // undefined = omero rdefs default (middle z-slice if missing)
72
+ // c: undefined, // channel index or array of indices; undefined = all active channels
73
+ // range: undefined, // contrast limits [min, max] or array thereof; undefined = omero windows
74
+ // color: undefined, // RGB color or array of colors; undefined = omero channel colors
75
+ // lutOrColorMap: undefined, // LUT/color map or array thereof; undefined = omero LUTs
76
+ // inverted: undefined, // boolean or array of booleans; undefined = omero inversion
60
77
  // autoBoost: undefined // boost brightness of dark tiles (default false)
61
78
  });
62
79
 
63
- const viewer = OpenSeadragon(
80
+ const viewer = OpenSeadragon({
64
81
  ...
65
82
  tileSources: [
66
83
  tileSource1,
@@ -68,78 +85,188 @@ const viewer = OpenSeadragon(
68
85
  tileSource3,
69
86
  tileSource4
70
87
  ]
71
- );
88
+ });
72
89
  ```
73
90
 
91
+ ### Options
92
+
93
+ | Option | Type | Default | Description |
94
+ | --------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
95
+ | `url` | `string \| URL` | (required) | URL of the OME-Zarr image (group) or of a zipped OME-Zarr file, relative to the document base URL |
96
+ | `zip` | `boolean` | `true` if the `url` path ends with `.ozx` | Whether `url` points to a zipped OME-Zarr file |
97
+ | `t` | `number` | omero `rdefs.defaultT`, else middle plane | Timepoint index (0-based) |
98
+ | `z` | `number` | omero `rdefs.defaultZ`, else middle plane | Z-slice index (0-based) |
99
+ | `c` | `number \| number[]` | all channels marked active in omero | Channel index or indices (0-based) to render, composited in the given order. Arrays must be non-empty |
100
+ | `range` | `[number, number] \| ([number, number] \| undefined)[]` | omero channel windows (`window.start`, `window.end`), else the data type range | Contrast limits (`[min, max]`) to render the channels with. Ignored for channels rendered with a color map |
101
+ | `color` | `Color \| (Color \| undefined)[]` | colors of the rendered omero channels, else white | RGB color or colors (`[r, g, b]`, 0-255) to render the channels with. Ignored for channels rendered with a LUT or color map |
102
+ | `lutOrColorMap` | `LUTOrColorMap \| (LUTOrColorMap \| undefined)[]` | omero channel LUTs and color maps, else none | Color LUT (a color per scaled value) or color map (a color per raw value, `Map`) to render the channels with; colors may carry alpha. A color map also overrides `range` and `inverted` |
103
+ | `inverted` | `boolean \| (boolean \| undefined)[]` | omero channel inversion, else `false` | Whether to invert the channels. Ignored for channels rendered with a color map |
104
+ | `autoBoost` | `boolean` | `false` | Boost the brightness of dark tiles (ome-zarr.js `renderChunks` option) |
105
+
106
+ `range`, `color`, `lutOrColorMap` and `inverted` take a single value, which
107
+ applies to all rendered channels, or one entry per rendered channel. An
108
+ `undefined` entry falls back to the omero metadata of that channel, as does the
109
+ option as a whole. The `ranges`, `colors`, `lutsOrColorMaps` and `inverteds`
110
+ getters return the resolved per-channel arrays.
111
+
112
+ `t`, `z` and `c` are validated against the image shape when the OME-Zarr
113
+ metadata is loaded; invalid indices fail with `open-failed`. Images without
114
+ `c` fail if the omero metadata marks no channel as active. An invalid `c`,
115
+ `url` or rendering setting is rejected by the constructor, which throws
116
+ synchronously before any request is made; the length of a rendering setting
117
+ array is only checked against `c` if that is configured too.
118
+
119
+ `url` is resolved against the document base URL when the tile source is
120
+ created; the resolved absolute URL is available as `tileSource.url` (always a
121
+ string) and is used for loading, for the tile cache keys and for comparing tile
122
+ sources.
123
+
74
124
  ### Accessing OME-Zarr metadata
75
125
 
76
126
  Directly instantiated tile sources start loading the OME-Zarr metadata
77
127
  immediately. Await `whenReady()` (or use the `OMEZarrTileSource.open` shortcut)
78
- to access the `NgffImage` instance (ome-zarr.js) and the opened zarrita arrays
79
- (one per resolution level) before adding the tile source to a viewer:
128
+ to access the loaded image (`loaded.image`, an ome-zarr.js `NgffImage`) and the
129
+ opened zarrita arrays (`loaded.arrays`, one per resolution level, highest
130
+ resolution first) before adding the tile source to a viewer:
80
131
 
81
132
  ```javascript
82
133
  const tileSource = await OMEZarrTileSource.open({ url: url, c: 0 });
83
134
  // equivalent: await new OMEZarrTileSource({ url: url, c: 0 }).whenReady();
84
135
 
85
- console.log(tileSource.image.getAxesNames()); // e.g. ["t", "z", "c", "y", "x"]
86
- console.log(tileSource.image.omero?.channels); // omero channel metadata
87
- console.log(tileSource.arrays[0].shape); // full-resolution array shape
136
+ console.log(tileSource.loaded.image.getAxesNames()); // e.g. ["t", "c", "z", "y", "x"]
137
+ console.log(tileSource.loaded.image.omero?.channels); // omero channel metadata
138
+ console.log(tileSource.loaded.arrays[0].shape); // full-resolution array shape
139
+
140
+ console.log(tileSource.t, tileSource.z, tileSource.cs); // resolved indices
141
+ console.log(tileSource.channels); // omero channels of the rendered channel indices
142
+ console.log(tileSource.ranges, tileSource.colors); // resolved rendering settings
143
+ console.log(tileSource.lutsOrColorMaps, tileSource.inverteds);
144
+ console.log(tileSource.getWidth(), tileSource.getHeight()); // full resolution
145
+ console.log(tileSource.getWidth(0), tileSource.getHeight(0)); // lowest resolution
88
146
 
89
147
  viewer.addTiledImage({ tileSource: tileSource }); // no second metadata request
90
148
  ```
91
149
 
92
- The metadata is loaded once per tile source instance: OpenSeadragon reuses a
93
- tile source instance passed to it as-is (waiting for it to become ready if
94
- necessary), whereas a URL or an inline configuration object makes OpenSeadragon
95
- create (and load) a new instance. `whenReady()` rejects (and `image`/`arrays`
96
- throw) if loading fails.
150
+ `OMEZarrTileSource.open` accepts an `AbortSignal` as `signal` in its third
151
+ argument to cancel loading. The metadata is loaded once per tile source
152
+ instance: OpenSeadragon reuses a tile source instance passed to it as-is
153
+ (waiting for it to become ready if necessary), whereas a URL or an inline
154
+ configuration object makes OpenSeadragon create (and load) a new instance.
155
+ `whenReady()` rejects (and `loaded` throws) if loading or validation fails.
156
+
157
+ The `t`, `z` and `cs` getters return the resolved values (the omero defaults or
158
+ active channels if the option was not given); `cs` is always an array (the
159
+ `c` option may be a single index), and `channels` returns the omero channel
160
+ objects for `cs`. Before the tile source is ready, they return the configured
161
+ options. The `ranges`, `colors`, `lutsOrColorMaps` and `inverteds` getters
162
+ return the resolved rendering settings of the rendered channels, merging the
163
+ configured values with the omero metadata per channel: `undefined` before the
164
+ tile source is ready or if the image has no omero metadata, like `channels`.
165
+ Entries that are still `undefined` fall back at render time to the data type
166
+ range, white, no LUT and not inverted.
97
167
 
98
168
  ### Sharing OME-Zarr metadata between tile sources
99
169
 
100
- A loaded `NgffImage` can be reused by other directly instantiated tile sources
101
- for the same URL (e.g. one tile source per channel) by passing it as the second
102
- constructor argument (also supported by `OMEZarrTileSource.open`). This skips
103
- loading the OME-Zarr metadata (the `zip` option is ignored) and reuses the
104
- opened zarrita arrays, which ome-zarr.js caches on the `NgffImage` instance.
105
- The tile source never modifies the `NgffImage`, so sharing is safe:
170
+ The loaded image and arrays (`OMEZarr`) can be reused by other directly
171
+ instantiated tile sources for the same URL (e.g. one tile source per channel)
172
+ by passing them as the second constructor argument (also supported by
173
+ `OMEZarrTileSource.open`). This skips loading the OME-Zarr metadata (the `zip`
174
+ option is ignored) and reuses the opened zarrita arrays. The tile source never
175
+ modifies the loaded image, so sharing is safe:
106
176
 
107
177
  ```javascript
178
+ // load once with the tile source's loader ...
179
+ const loaded = await OMEZarrTileSource.loadOMEZarr(url);
180
+ const tileSources = loaded.image.omero.channels.map(
181
+ (_, c) => new OMEZarrTileSource({ url: url, c: c }, loaded),
182
+ );
183
+
184
+ // ... or reuse what a first tile source has loaded
108
185
  const tileSource1 = await OMEZarrTileSource.open({ url: url, c: 0 });
109
186
  const tileSource2 = new OMEZarrTileSource(
110
187
  { url: url, c: 1 },
111
- tileSource1.image,
188
+ tileSource1.loaded,
112
189
  );
113
- // or, without a first tile source: NgffImage.load(url) from ome-zarr.js
190
+ ```
191
+
192
+ `loadOMEZarr(url, zip?, { signal })` loads the metadata with ome-zarr.js and
193
+ opens the arrays of all resolution levels. An `OMEZarr` can also be assembled
194
+ from an `NgffImage` loaded by the app, as long as `arrays` lists the
195
+ opened arrays of `image.paths` in order. It is not checked against the URL of
196
+ the tile source it is passed to.
197
+
198
+ ### Loading chunks directly
199
+
200
+ `loadChunks(level, tile, { signal })` returns the (y, x) zarrita chunks of the
201
+ rendered channels at the rendered timepoint and z-slice, either for one tile
202
+ (`{ x, y }` tile coordinates, as passed to OpenSeadragon) or for the whole
203
+ level plane (`tile` `undefined`):
204
+
205
+ ```javascript
206
+ const chunks = await tileSource.loadChunks(tileSource.maxLevel, { x: 0, y: 0 });
207
+ const plane = await tileSource.loadChunks(0, undefined); // lowest resolution
208
+ ```
209
+
210
+ `OMEZarrTileSource.render(tileData)` composites such chunks into a 2D canvas
211
+ context, the same way the `ome-zarr` to `context2d` converter does:
212
+
213
+ ```javascript
214
+ import { getDataTypeRange } from "omezarr-tilesource";
215
+
216
+ const ctx = OMEZarrTileSource.render({
217
+ chunks: plane,
218
+ // the tile data holds the renderChunks arguments, one per chunk, with the
219
+ // defaults of the tile source getters applied
220
+ ranges: plane.map(
221
+ (chunk, c) => tileSource.ranges?.[c] ?? getDataTypeRange(chunk),
222
+ ),
223
+ colors: plane.map((_, c) => tileSource.colors?.[c] ?? [255, 255, 255]),
224
+ lutsOrColorMaps: plane.map((_, c) => tileSource.lutsOrColorMaps?.[c]),
225
+ inverteds: plane.map((_, c) => tileSource.inverteds?.[c] ?? false),
226
+ autoBoost: tileSource.autoBoost,
227
+ });
114
228
  ```
115
229
 
116
230
  ## Data pipeline
117
231
 
118
- By default (`dataType: "context2d"`), tiles are rendered by the tile source and
119
- passed to OpenSeadragon as 2D canvas contexts, using the rendering settings
120
- (color, color LUT/map, contrast limits, inversion) from the omero metadata. For
121
- multi-channel images without `c`, all active channels are rendered into a
122
- composite image.
123
-
124
- Contrast limits are taken from the omero channel windows (`window.start`,
125
- `window.end`). Channels without them are rendered using the data type range
126
- for integer types (e.g. `[0, 65535]` for `uint16`) and `[0, 1]` for floating
127
- point types.
128
-
129
- With `dataType: "ome-zarr"`, tiles are instead downloaded as raw single-channel
130
- zarrita chunks and passed to OpenSeadragon with the data type `ome-zarr` (see
131
- the `OMEZarrTileData` type). Multi-channel images therefore require the `c`
132
- option. A converter from `ome-zarr` to `context2d` is registered on
133
- `OpenSeadragon.converter` when the module is imported (and by
134
- `OMEZarrTileSource.enable`). It reads the rendering settings at conversion time
135
- from the omero channel referenced by the tile data (`OMEZarrTileData.channel`).
136
- The channel object is shared by all tiles of a tile source, so advanced users
137
- may modify it (e.g. from a `tile-invalidated` handler) and re-render the cached
138
- tiles using `viewer.requestInvalidate()`.
232
+ Tiles are downloaded as raw zarrita chunks, one per rendered channel, together
233
+ with the rendering settings resolved for that tile, and passed to OpenSeadragon
234
+ with the data type `ome-zarr` (see the `OMEZarrTileData` type: `chunks`,
235
+ `ranges`, `colors`, `lutsOrColorMaps`, `inverteds` and `autoBoost` — the
236
+ arguments of ome-zarr.js `renderChunks`, one entry per chunk). A converter from
237
+ `ome-zarr` to `context2d` calls `OMEZarrTileSource.render` on demand, which
238
+ composites them into a 2D canvas context. Because the raw chunks stay in the
239
+ tile cache, tiles can be re-rendered without re-downloading them.
240
+
241
+ Settings that are neither configured nor in the omero metadata are resolved
242
+ when the tile is downloaded: to the data type range for integer types (e.g.
243
+ `[0, 65535]` for `uint16`) and `[0, 1]` for floating point types, to white, to
244
+ no LUT and to not inverted — so an image without omero metadata is rendered in
245
+ white over its full data type range.
246
+
247
+ The converters are registered on `OpenSeadragon.converter` when the module is
248
+ imported (for the OpenSeadragon instance it imports, and for a global
249
+ `OpenSeadragon` if present) and by `OMEZarrTileSource.enable`. Tiles cannot be
250
+ rendered without them, so call `enable` if the app uses a different
251
+ OpenSeadragon instance than the one resolved by this module (e.g. a separately
252
+ bundled copy).
253
+
254
+ All rendering settings in `OMEZarrTileData` are plain values, resolved from the
255
+ tile source configuration and the omero metadata when the tile is downloaded.
256
+ Editing the omero channels of a loaded image therefore does not affect tiles
257
+ that are already cached, with or without `viewer.requestInvalidate()`; render
258
+ them differently by creating a tile source with the corresponding options
259
+ instead. Tile sources that configure rendering settings do not share cached
260
+ tiles with tile sources that configure different ones.
139
261
 
140
262
  ## Example
141
263
 
142
- [Example](https://tissuumaps.github.io/OMEZarrTileSource)
264
+ [https://tissuumaps.github.io/OMEZarrTileSource?url=https://livingobjects.ebi.ac.uk/idr/zarr/v0.5/idr0062A/6001240_labels.zarr](https://tissuumaps.github.io/OMEZarrTileSource?url=https%3A%2F%2Flivingobjects.ebi.ac.uk%2Fidr%2Fzarr%2Fv0.5%2Fidr0062A%2F6001240_labels.zarr)
265
+
266
+ The example page takes the URL of the OME-Zarr image from the mandatory `url`
267
+ query parameter, and the timepoint index, the z-slice index and the channel
268
+ indices from the optional `t`, `z` and `c` query parameters (repeat `c` for
269
+ multiple channels).
143
270
 
144
271
  [Source code](index.html)
145
272