@biongff/vizarr 1.1.1 → 1.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.
Files changed (114) hide show
  1. package/dist/biongff-vizarr.cjs.js +1 -0
  2. package/dist/biongff-vizarr.es.js +7 -0
  3. package/dist/blosc-CeItQ6qj.cjs +17 -0
  4. package/dist/blosc-DaK8KnI4.js +719 -0
  5. package/dist/chunk-INHXZS53-DiyuLb3Z.js +14 -0
  6. package/dist/chunk-INHXZS53-z3BpFH8p.cjs +1 -0
  7. package/dist/deflate-BeJu5ryY.js +10 -0
  8. package/dist/deflate-Ctyqttht.cjs +1 -0
  9. package/dist/index-BR2zkK5Y.cjs +3811 -0
  10. package/dist/index-CU_d8468.js +49053 -0
  11. package/dist/index.d.ts +67 -0
  12. package/dist/jpeg-BWDIgcDK.js +533 -0
  13. package/dist/jpeg-Mp_UXZAJ.cjs +1 -0
  14. package/dist/jpeg2k-1tnzEScI.js +24 -0
  15. package/dist/jpeg2k-CcF6vCxI.cjs +1 -0
  16. package/dist/lerc-BJaDi7aO.js +1039 -0
  17. package/dist/lerc-D-6RihsZ.cjs +1 -0
  18. package/dist/lz4-CssV0LoA.js +643 -0
  19. package/dist/lz4-PFaIsPAh.cjs +15 -0
  20. package/dist/lzw-DBp1lk8U.js +84 -0
  21. package/dist/lzw-De76dcEC.cjs +1 -0
  22. package/dist/packbits-B6vt-6GN.cjs +1 -0
  23. package/dist/packbits-MT4dFhEA.js +24 -0
  24. package/dist/pako.esm-Bx5X36Wo.js +1074 -0
  25. package/dist/pako.esm-DZC2QrbJ.cjs +1 -0
  26. package/dist/raw-BF-NUDI1.js +9 -0
  27. package/dist/raw-vwCA5qWn.cjs +1 -0
  28. package/dist/ref-DAXKePD4.cjs +1 -0
  29. package/dist/ref-HqVJFnCG.js +196 -0
  30. package/dist/webimage-BQlxeHEQ.cjs +1 -0
  31. package/dist/webimage-DTyyK3Ev.js +19 -0
  32. package/dist/zstd-Cqadn9HA.js +610 -0
  33. package/dist/zstd-_xUhkGOV.cjs +15 -0
  34. package/package.json +24 -19
  35. package/.gitattributes +0 -1
  36. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -32
  37. package/.github/ISSUE_TEMPLATE/documentation_update.md +0 -16
  38. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  39. package/.github/ISSUE_TEMPLATE/maintenance.md +0 -19
  40. package/.github/ISSUE_TEMPLATE/optimisation_request.md +0 -23
  41. package/.github/ISSUE_TEMPLATE/support_request.md +0 -25
  42. package/.github/PULL_REQUEST_TEMPLATE.md +0 -21
  43. package/.github/dependabot.yml +0 -13
  44. package/.github/workflows/deploy.yml +0 -56
  45. package/.github/workflows/npm-publish-dev.yml +0 -49
  46. package/.github/workflows/npm-publish.yml +0 -42
  47. package/.github/workflows/release.yml +0 -37
  48. package/.github/workflows/test.yml +0 -21
  49. package/.releaserc.json +0 -29
  50. package/CHANGELOG.md +0 -70
  51. package/CITATION.cff +0 -56
  52. package/CONTRIBUTING.md +0 -82
  53. package/README.md +0 -85
  54. package/assets/favicon.ico +0 -0
  55. package/assets/favicon.png +0 -0
  56. package/assets/logo-wide.png +0 -0
  57. package/assets/logo-wide.svg +0 -60
  58. package/assets/logo.png +0 -0
  59. package/assets/logo.svg +0 -36
  60. package/assets/robots.txt +0 -3
  61. package/assets/screenshot.png +0 -0
  62. package/biome.json +0 -34
  63. package/index.html +0 -24
  64. package/main.ts +0 -52
  65. package/patches/@vivjs__loaders@0.17.2.patch +0 -13
  66. package/python/.python-version +0 -1
  67. package/python/README.md +0 -14
  68. package/python/Untitled.ipynb +0 -40
  69. package/python/deno.json +0 -22
  70. package/python/notebooks/IDR_example.ipynb +0 -86
  71. package/python/notebooks/README.md +0 -35
  72. package/python/notebooks/create_fixture.py +0 -72
  73. package/python/notebooks/getting_started.ipynb +0 -217
  74. package/python/notebooks/mandelbrot.ipynb +0 -211
  75. package/python/notebooks/spatial_transformations.ipynb +0 -139
  76. package/python/pyproject.toml +0 -18
  77. package/python/src/vizarr/__init__.py +0 -10
  78. package/python/src/vizarr/_widget.js +0 -113
  79. package/python/src/vizarr/_widget.py +0 -67
  80. package/python/uv.lock +0 -2495
  81. package/src/ZarrPixelSource.ts +0 -182
  82. package/src/codecs/jpeg2k.ts +0 -22
  83. package/src/codecs/register.ts +0 -2
  84. package/src/components/LayerController/AcquisitionController.tsx +0 -46
  85. package/src/components/LayerController/AddChannelButton.tsx +0 -122
  86. package/src/components/LayerController/AxisOptions.tsx +0 -93
  87. package/src/components/LayerController/AxisSlider.tsx +0 -86
  88. package/src/components/LayerController/AxisSliders.tsx +0 -29
  89. package/src/components/LayerController/ChannelController.tsx +0 -84
  90. package/src/components/LayerController/ChannelOptions.tsx +0 -183
  91. package/src/components/LayerController/ColorPalette.tsx +0 -40
  92. package/src/components/LayerController/Content.tsx +0 -71
  93. package/src/components/LayerController/Header.tsx +0 -48
  94. package/src/components/LayerController/Labels.tsx +0 -73
  95. package/src/components/LayerController/LayerFitToViewportButton.tsx +0 -43
  96. package/src/components/LayerController/LayerVisibilityButton.tsx +0 -32
  97. package/src/components/LayerController/OpacitySlider.tsx +0 -25
  98. package/src/components/LayerController/index.tsx +0 -42
  99. package/src/components/Menu.tsx +0 -54
  100. package/src/components/Viewer.tsx +0 -136
  101. package/src/hooks.ts +0 -29
  102. package/src/index.tsx +0 -138
  103. package/src/io.ts +0 -276
  104. package/src/layers/grid-layer.ts +0 -214
  105. package/src/layers/label-layer.ts +0 -446
  106. package/src/layers/viv-layers.ts +0 -55
  107. package/src/lru-store.ts +0 -57
  108. package/src/ome.ts +0 -388
  109. package/src/state.ts +0 -202
  110. package/src/theme.ts +0 -80
  111. package/src/types.ts +0 -112
  112. package/src/utils.ts +0 -663
  113. package/tsconfig.json +0 -14
  114. package/vite.config.js +0 -47
package/src/utils.ts DELETED
@@ -1,663 +0,0 @@
1
- import { Matrix4 } from "math.gl";
2
- import * as zarr from "zarrita";
3
-
4
- import type * as viv from "@vivjs/types";
5
- import ZipFileStore from "@zarrita/storage/zip";
6
- import type { ZarrPixelSource } from "./ZarrPixelSource";
7
- import type { GridLayerProps } from "./layers/grid-layer";
8
- import type { LabelLayerProps } from "./layers/label-layer";
9
- import type { ImageLayerProps, MultiscaleImageLayerProps } from "./layers/viv-layers";
10
- import { lru } from "./lru-store";
11
- import type { ViewState, VizarrLayer } from "./state";
12
-
13
- export const MAX_CHANNELS = 6;
14
-
15
- export const COLORS = {
16
- cyan: "#00FFFF",
17
- yellow: "#FFFF00",
18
- magenta: "#FF00FF",
19
- red: "#FF0000",
20
- green: "#00FF00",
21
- blue: "#0000FF",
22
- white: "#FFFFFF",
23
- };
24
- export const MAGENTA_GREEN = [COLORS.magenta, COLORS.green];
25
- export const RGB = [COLORS.red, COLORS.green, COLORS.blue];
26
- export const CYMRGB = Object.values(COLORS).slice(0, MAX_CHANNELS);
27
- export const OME_VALIDATOR_URL = "https://ome.github.io/ome-ngff-validator/";
28
-
29
- async function normalizeStore(source: string | zarr.Readable): Promise<zarr.Location<zarr.Readable>> {
30
- if (typeof source === "string") {
31
- let store: zarr.Readable;
32
- let path: `/${string}` = "/";
33
- if (source.endsWith(".json")) {
34
- // import custom store implementation
35
- const [{ default: ReferenceStore }, json] = await Promise.all([
36
- import("@zarrita/storage/ref"),
37
- fetch(source).then((res) => res.json()),
38
- ]);
39
- store = ReferenceStore.fromSpec(json);
40
- } else {
41
- // try ZipFileStore first, fallback to FetchStore
42
- try {
43
- const zipStore = ZipFileStore.fromUrl(source);
44
- await zipStore.has("/"); // will throw an error for non-zipped
45
- store = zipStore;
46
- } catch {
47
- const url = new URL(source);
48
- // grab the path and then set the URL to the root
49
- path = ensureAbosolutePath(url.pathname);
50
- url.pathname = "/";
51
- store = new zarr.FetchStore(url.href);
52
- }
53
- }
54
-
55
- // Wrap remote stores in a cache
56
- return new zarr.Location(lru(store), path);
57
- }
58
-
59
- return zarr.root(source);
60
- }
61
-
62
- function ensureAbosolutePath(path: string): `/${string}` {
63
- if (path === "/") return path;
64
- // @ts-expect-error - path always starts with '/'
65
- return path.startsWith("/") ? path : `/${path}`;
66
- }
67
-
68
- export async function open(source: string | zarr.Readable) {
69
- const location = await normalizeStore(source);
70
- return zarr.open(location);
71
- }
72
-
73
- export async function getAttrsOnly<T = unknown>(
74
- location: zarr.Location<zarr.Readable>,
75
- options: { path?: string; zarrVersion: 2 | 3 },
76
- ) {
77
- const decoder = new TextDecoder();
78
- if (options.path) {
79
- location = location.resolve(options.path);
80
- }
81
- if (options.zarrVersion === 3) {
82
- const attrs = await zarr.open.v3(location).then((node) => node.attrs);
83
- return resolveAttrs(attrs) as T;
84
- }
85
- const v2AttrsLocation = location.resolve(".zattrs");
86
- const maybeBytes = await location.store.get(v2AttrsLocation.path);
87
- const attrs = maybeBytes ? JSON.parse(decoder.decode(maybeBytes)) : {};
88
- return resolveAttrs(attrs) as T;
89
- }
90
-
91
- /**
92
- * Loads the multiscales from a group and returns the datasets.
93
- *
94
- * NOTE: We avoid loading the attributes here because we don't need them.
95
- */
96
- export async function loadMultiscales(
97
- grp: zarr.Group<zarr.Readable>,
98
- multiscales: Ome.Multiscale[],
99
- ): Promise<Array<zarr.Array<zarr.DataType, zarr.Readable>>> {
100
- const { datasets } = multiscales[0] || [{ path: "0" }];
101
- return Promise.all(
102
- // TODO(Trevor): TS is not happy about { attrs: false }.
103
- // This is just missing from zarrita types, but it is ok and
104
- // avoids making unecessary requests for v2 (see: https://github.com/manzt/zarrita.js/blob/7edffbeefb0eb877df48f54c7e8def4219c69c59/packages/zarrita/CHANGELOG.md?plain=1#L214)
105
- datasets.map(({ path }) => zarr.open(grp.resolve(path), { kind: "array", attrs: false } as { kind: "array" })),
106
- );
107
- }
108
-
109
- export function hexToRGB(hex: string): [r: number, g: number, b: number] {
110
- if (hex.startsWith("#")) hex = hex.slice(1);
111
- const r = Number.parseInt(hex.slice(0, 2), 16);
112
- const g = Number.parseInt(hex.slice(2, 4), 16);
113
- const b = Number.parseInt(hex.slice(4, 6), 16);
114
- return [r, g, b];
115
- }
116
-
117
- export function range(length: number): number[] {
118
- return Array.from({ length }, (_, i) => i);
119
- }
120
-
121
- // similar to Python's rstrip
122
- export function rstrip(str: string, remove = " "): string {
123
- // if the last character is in 'remove', truncate
124
- while (str.length > 0 && remove.includes(str.charAt(str.length - 1))) {
125
- str = str.substr(0, str.length - 1);
126
- }
127
- return str;
128
- }
129
-
130
- export function join(...args: (string | undefined)[]) {
131
- return args
132
- .filter((s) => s !== undefined)
133
- .map((s) => rstrip(s, "/"))
134
- .join("/");
135
- }
136
-
137
- export function getAxisLabels(
138
- arr: zarr.Array<zarr.DataType, zarr.Readable>,
139
- axis_labels?: string[],
140
- ): [...string[], "y", "x"] {
141
- if (!axis_labels || axis_labels.length !== arr.shape.length) {
142
- // default axis_labels are e.g. ['0', '1', 'y', 'x']
143
- const nonXYaxisLabels = arr.shape.slice(0, -2).map((_, i) => `${i}`);
144
- axis_labels = nonXYaxisLabels.concat(["y", "x"]);
145
- }
146
- return axis_labels as [...string[], "y", "x"];
147
- }
148
-
149
- export function getNgffAxes(multiscales: Ome.Multiscale[]): Ome.Axis[] {
150
- // Returns axes in the latest v0.4+ format.
151
- // defaults for v0.1 & v0.2
152
- const default_axes = [
153
- { type: "time", name: "t" },
154
- { type: "channel", name: "c" },
155
- { type: "space", name: "z" },
156
- { type: "space", name: "y" },
157
- { type: "space", name: "x" },
158
- ];
159
- function getDefaultType(name: string): string {
160
- if (name === "t") return "time";
161
- if (name === "c") return "channel";
162
- return "space";
163
- }
164
- let axes = default_axes;
165
- // v0.3 & v0.4+
166
- if (multiscales[0].axes) {
167
- axes = multiscales[0].axes.map((axis) => {
168
- // axis may be string 'x' (v0.3) or object
169
- if (typeof axis === "string") {
170
- return { name: axis, type: getDefaultType(axis) };
171
- }
172
- const { name, type, unit } = axis;
173
- return { name, type: type ?? getDefaultType(name), unit };
174
- });
175
- }
176
- return axes;
177
- }
178
-
179
- export function getNgffAxisLabels(axes: Ome.Axis[]): [...string[], "y", "x"] {
180
- const axes_names = axes.map((axis) => axis.name);
181
- return axes_names as [...string[], "y", "x"];
182
- }
183
-
184
- export function getDefaultVisibilities(n: number, visibilities?: boolean[]): boolean[] {
185
- if (!visibilities) {
186
- if (n <= MAX_CHANNELS) {
187
- // Default to all on if visibilities not specified and less than 6 channels.
188
- visibilities = Array(n).fill(true);
189
- } else {
190
- // If more than MAX_CHANNELS, only make first set on by default.
191
- visibilities = [...Array(MAX_CHANNELS).fill(true), ...Array(n - MAX_CHANNELS).fill(false)];
192
- }
193
- }
194
- return visibilities;
195
- }
196
-
197
- export function getDefaultColors(n: number, visibilities: boolean[]): string[] {
198
- let colors = [];
199
- if (n === 1) {
200
- colors = [COLORS.white];
201
- } else if (n === 2) {
202
- colors = MAGENTA_GREEN;
203
- } else if (n === 3) {
204
- colors = RGB;
205
- } else if (n <= MAX_CHANNELS) {
206
- colors = CYMRGB.slice(0, n);
207
- } else {
208
- // Default color for non-visible is white
209
- colors = Array(n).fill(COLORS.white);
210
- // Get visible indices
211
- const visibleIndices = visibilities.flatMap((bool, i) => (bool ? i : []));
212
- // Set visible indices to CYMRGB colors. visibleIndices.length === MAX_CHANNELS from above.
213
- for (const [i, visibleIndex] of visibleIndices.entries()) {
214
- colors[visibleIndex] = CYMRGB[i];
215
- }
216
- }
217
- return colors;
218
- }
219
-
220
- export function isInterleaved(shape: number[]) {
221
- const lastDimSize = shape[shape.length - 1];
222
- return lastDimSize === 3 || lastDimSize === 4;
223
- }
224
-
225
- export function guessTileSize(arr: zarr.Array<zarr.DataType, zarr.Readable>) {
226
- const interleaved = isInterleaved(arr.shape);
227
- const [ySize, xSize] = arr.chunks.slice(interleaved ? -3 : -2);
228
- const size = Math.min(ySize, xSize);
229
- // Needs to be a power of 2 for deck.gl
230
- return 2 ** Math.floor(Math.log2(size));
231
- }
232
-
233
- // Scales the real image size to the target viewport.
234
- export function fitImageToViewport(options: {
235
- image: { width: number; height: number };
236
- viewport: { width: number; height: number };
237
- padding: number;
238
- matrix?: Matrix4;
239
- }): ViewState {
240
- const { image, viewport, padding, matrix = new Matrix4().identity() } = options;
241
- const corners = [
242
- [0, 0, 0],
243
- [image.width, 0, 0],
244
- [image.width, image.height, 0],
245
- [0, image.height, 0],
246
- ].map((corner) => matrix.transformAsPoint(corner));
247
-
248
- const minX = Math.min(...corners.map((p) => p[0]));
249
- const maxX = Math.max(...corners.map((p) => p[0]));
250
- const minY = Math.min(...corners.map((p) => p[1]));
251
- const maxY = Math.max(...corners.map((p) => p[1]));
252
-
253
- const availableWidth = viewport.width - 2 * padding;
254
- const availableHeight = viewport.height - 2 * padding;
255
-
256
- return {
257
- zoom: Math.log2(
258
- Math.min(
259
- availableWidth / (maxX - minX), // scaleX
260
- availableHeight / (maxY - minY), // scaleY
261
- ),
262
- ),
263
- target: [(minX + maxX) / 2, (minY + maxY) / 2],
264
- };
265
- }
266
-
267
- type Array16 = [
268
- number,
269
- number,
270
- number,
271
- number,
272
- number,
273
- number,
274
- number,
275
- number,
276
- number,
277
- number,
278
- number,
279
- number,
280
- number,
281
- number,
282
- number,
283
- number,
284
- ];
285
-
286
- function isArray16(o: unknown): o is Array16 {
287
- if (!Array.isArray(o)) return false;
288
- return o.length === 16 && o.every((i) => typeof i === "number");
289
- }
290
-
291
- export function parseMatrix(model_matrix?: string | number[]): Matrix4 {
292
- if (!model_matrix) {
293
- return Matrix4.IDENTITY;
294
- }
295
- const matrix = new Matrix4();
296
- try {
297
- const arr = typeof model_matrix === "string" ? JSON.parse(model_matrix) : model_matrix;
298
- assert(isArray16(arr), "Invalid modelMatrix size. Must be 16.");
299
- matrix.setRowMajor(...arr);
300
- } catch {
301
- const msg = `Failed to parse modelMatrix. Got ${JSON.stringify(model_matrix)}, using identity.`;
302
- console.warn(msg);
303
- }
304
- return matrix;
305
- }
306
-
307
- export async function calcDataRange(
308
- source: ZarrPixelSource,
309
- selection: Array<number>,
310
- ): Promise<[min: number, max: number]> {
311
- if (source.dtype === "Uint8") return [0, 255];
312
- const { data } = await source.getRaster({ selection });
313
- let minVal = Number.POSITIVE_INFINITY;
314
- let maxVal = Number.NEGATIVE_INFINITY;
315
- for (let i = 0; i < data.length; i++) {
316
- if (data[i] > maxVal) maxVal = data[i];
317
- if (data[i] < minVal) minVal = data[i];
318
- }
319
- if (minVal === maxVal) {
320
- minVal = 0;
321
- maxVal = 1;
322
- }
323
- return [minVal, maxVal];
324
- }
325
-
326
- export async function calcConstrastLimits(
327
- source: ZarrPixelSource,
328
- channelAxis: number,
329
- visibilities: boolean[],
330
- defaultSelection?: number[],
331
- ): Promise<([min: number, max: number] | undefined)[]> {
332
- const def = defaultSelection ?? source.shape.map(() => 0);
333
- const csize = source.shape[channelAxis];
334
-
335
- // channelAxis can be -1 if there is no 'c' dimension
336
- if (channelAxis !== -1) {
337
- assert(csize === visibilities.length, "visibilities do not match number of channels");
338
- }
339
-
340
- return Promise.all(
341
- visibilities.map(async (isVisible, i) => {
342
- if (!isVisible) return undefined; // don't compute non-visible channels
343
- const selection = [...def];
344
- if (channelAxis > -1) {
345
- selection[channelAxis] = i;
346
- }
347
- return calcDataRange(source, selection);
348
- }),
349
- );
350
- }
351
-
352
- /**
353
- * Create a promise that can be resolved or rejected externally.
354
- *
355
- * TODO: Switch to Promise.withResolvers when it's available
356
- */
357
- export function defer<T>() {
358
- let resolve: (value: T | PromiseLike<T>) => void;
359
- let reject: (reason?: unknown) => void;
360
- const promise = new Promise<T>((res, rej) => {
361
- resolve = res;
362
- reject = rej;
363
- });
364
- // @ts-expect-error - resolve and reject are OK
365
- return { promise, resolve, reject };
366
- }
367
-
368
- /**
369
- * A simple event emitter that allows for typed events.
370
- *
371
- * @example
372
- * ```ts
373
- * const emitter = typedEmitter<{ foo: string, bar: number }>();
374
- * emitter.on('foo', (data) => console.log(data));
375
- * emitter.emit('foo', 'hello');
376
- * ```
377
- *
378
- * TODO: Add support for removing listeners.
379
- */
380
- export function typedEmitter<T>() {
381
- let target = new EventTarget();
382
- return {
383
- on: <E extends keyof T & string>(event: E, cb: (data: T[E]) => void) => {
384
- target.addEventListener(event, (e) => cb((e as CustomEvent).detail));
385
- },
386
- emit: <E extends keyof T & string>(event: E, data: T[E]) => {
387
- target.dispatchEvent(new CustomEvent(event, { detail: data }));
388
- },
389
- };
390
- }
391
-
392
- /**
393
- * Extracts the OME metadata from the zarr attributes
394
- *
395
- * TODO: We should use zod to handle this
396
- */
397
- export function resolveAttrs(attrs: zarr.Attributes): zarr.Attributes {
398
- if ("ome" in attrs) {
399
- // @ts-expect-error - handles v0.5
400
- return attrs.ome;
401
- }
402
- return attrs;
403
- }
404
-
405
- /**
406
- * Error thrown when an assertion fails.
407
- */
408
- export class AssertionError extends Error {
409
- /** @param message The error message. */
410
- constructor(message: string) {
411
- super(message);
412
- this.name = "AssertionError";
413
- }
414
- }
415
-
416
- /**
417
- * Error thrown when we want to redirect.
418
- */
419
- export class RedirectError extends Error {
420
- url: string;
421
-
422
- /**
423
- * @param message The error message.
424
- * @param url The url to redirect to.
425
- */
426
- constructor(message: string, url: string) {
427
- super(message);
428
- this.name = "RedirectError";
429
- this.url = url;
430
- }
431
- }
432
-
433
- /**
434
- * Make an assertion. An error is thrown if `expr` does not have truthy value.
435
- *
436
- * @param expr The expression to test.
437
- * @param msg The message to display if the assertion fails.
438
- */
439
- export function assert(expr: unknown, msg = ""): asserts expr {
440
- if (!expr) {
441
- throw new AssertionError(msg);
442
- }
443
- }
444
-
445
- /**
446
- * Guess the zarr version of a store.
447
- */
448
- export async function guessZarrVersion(location: zarr.Location<zarr.Readable>): Promise<2 | 3> {
449
- try {
450
- await zarr.open.v3(location);
451
- return 3;
452
- } catch (err) {
453
- if (!(err instanceof zarr.NodeNotFoundError)) {
454
- // rethrow if not a NodeNotFoundError
455
- throw err;
456
- }
457
- return 2;
458
- }
459
- }
460
-
461
- export function isOmePlate(attrs: zarr.Attributes): attrs is { plate: Ome.Plate } {
462
- return "plate" in attrs;
463
- }
464
-
465
- export function isOmeWell(attrs: zarr.Attributes): attrs is { well: Ome.Well } {
466
- return "well" in attrs;
467
- }
468
-
469
- export function isOmeImageLabel(
470
- attrs: zarr.Attributes,
471
- ): attrs is { "image-label": Ome.ImageLabel; multiscales: Ome.Multiscale[] } {
472
- return "image-label" in attrs && isMultiscales(attrs);
473
- }
474
-
475
- export function isOmeMultiscales(attrs: zarr.Attributes): attrs is { omero: Ome.Omero; multiscales: Ome.Multiscale[] } {
476
- return "omero" in attrs && isMultiscales(attrs);
477
- }
478
-
479
- export function isMultiscales(attrs: zarr.Attributes): attrs is { multiscales: Ome.Multiscale[] } {
480
- return "multiscales" in attrs;
481
- }
482
-
483
- export function isBioformats2rawlayout(attrs: zarr.Attributes): attrs is { multiscales: Ome.Bioformats2rawlayout } {
484
- return "bioformats2raw.layout" in attrs;
485
- }
486
-
487
- /**
488
- * Ensures an error matches expected type(s), otherwise rethrows.
489
- *
490
- * Unmatched errors bubble up, like Python's `except`. Narrows error types for
491
- * type-safe property access.
492
- *
493
- * Usage
494
- * @example
495
- * ```ts
496
- * class DatabaseError extends Error {}
497
- * class NetworkError extends Error {}
498
- *
499
- * try {
500
- * await db.query();
501
- * } catch (error) {
502
- * rethrowUnless(error, DatabaseError, NetworkError);
503
- * error; // DatabaseError | NetworkError
504
- * }
505
- * ```
506
- *
507
- * @param error - The error to check
508
- * @param ErrorClasses - Expected error type(s)
509
- * @throws The original error if it doesn't match expected type(s)
510
- *
511
- * @copyright Trevor Manz 2025
512
- * @license MIT
513
- * @see {@link https://github.com/manzt/manzt/blob/4b04f2/utils/rethrow-unless.js}
514
- */
515
- // biome-ignore lint/suspicious/noExplicitAny: Ok to use any for generic constraint
516
- export function rethrowUnless<E extends ReadonlyArray<new (...args: any[]) => Error>>(
517
- error: unknown,
518
- ...ErrorClasses: E
519
- // biome-ignore lint/suspicious/noExplicitAny: Ok to use any for generic constraint
520
- ): asserts error is E[number] extends new (...args: any[]) => infer R ? R : never {
521
- if (!ErrorClasses.some((ErrorClass) => error instanceof ErrorClass)) {
522
- throw error;
523
- }
524
- }
525
-
526
- export function isGridLayerProps(
527
- props: GridLayerProps | ImageLayerProps | MultiscaleImageLayerProps | LabelLayerProps,
528
- ): props is GridLayerProps {
529
- return "loaders" in props && "rows" in props && "columns" in props;
530
- }
531
-
532
- export function resolveLoaderFromLayerProps(
533
- layerProps: GridLayerProps | ImageLayerProps | MultiscaleImageLayerProps | LabelLayerProps,
534
- ) {
535
- return isGridLayerProps(layerProps) ? layerProps.loaders[0].loader : layerProps.loader;
536
- }
537
-
538
- /**
539
- * Convert an array of coordinateTransformations objects to a 16-element
540
- * plain JS array using Matrix4 linear algebra transformation functions.
541
- *
542
- * Adapted from Vitessce: https://github.com/vitessce/vitessce/blob/c267ebecab1824dae68d6f2640a6c5ce7250efbb/packages/utils/spatial-utils/src/spatial.js#L403-L524
543
- *
544
- * @param coordinateTransformations List of objects matching the OME-NGFF v0.4 coordinateTransformations spec.
545
- * @param axes - Axes in OME-NGFF v0.4 format
546
- *
547
- * @returns Array of 16 numbers representing the Matrix4.
548
- */
549
- export function coordinateTransformationsToMatrix(multiscales: Array<Ome.Multiscale>) {
550
- let mat = new Matrix4().identity();
551
- const axes = getNgffAxes(multiscales);
552
- const coordinateTransformations = multiscales[0].datasets[0]?.coordinateTransformations;
553
- const xyzIndices = ["x", "y", "z"].map((name) =>
554
- axes.findIndex((axisObj) => axisObj.type === "space" && axisObj.name === name),
555
- );
556
-
557
- // Apply each transformation sequentially and in order according to the OME-NGFF v0.4 spec.
558
- // Reference: https://ngff.openmicroscopy.org/0.4/#trafo-md
559
- for (const transform of coordinateTransformations ?? []) {
560
- if (transform.type === "translation") {
561
- const { translation: axisOrderedTranslation } = transform;
562
- if (axisOrderedTranslation.length !== axes.length) {
563
- throw new Error("Length of translation array was expected to match length of axes.");
564
- }
565
- const defaultValue = 0;
566
- // Get the translation values for [x, y, z].
567
- const xyzTranslation = xyzIndices.map((axisIndex) =>
568
- axisIndex >= 0 ? axisOrderedTranslation[axisIndex] : defaultValue,
569
- );
570
- const nextMat = new Matrix4().translate(xyzTranslation);
571
- mat = mat.multiplyLeft(nextMat);
572
- }
573
- if (transform.type === "scale") {
574
- const { scale: axisOrderedScale } = transform;
575
- // Add in z dimension needed for Matrix4 scale API.
576
- if (axisOrderedScale.length !== axes.length) {
577
- throw new Error("Length of scale array was expected to match length of axes.");
578
- }
579
- const defaultValue = 1;
580
- // Get the scale values for [x, y, z].
581
- const xyzScale = xyzIndices.map((axisIndex) => (axisIndex >= 0 ? axisOrderedScale[axisIndex] : defaultValue));
582
- const nextMat = new Matrix4().scale(xyzScale);
583
- mat = mat.multiplyLeft(nextMat);
584
- }
585
- }
586
-
587
- return mat;
588
- }
589
-
590
- /**
591
- * Builds N-tuples of elements from the given N arrays with matching indices,
592
- * stopping when the smallest array's end is reached.
593
- */
594
- export function zip<T extends unknown[]>(...arrays: { [K in keyof T]: ReadonlyArray<T[K]> }): T[] {
595
- const minLength = arrays.reduce((minLength, arr) => Math.min(arr.length, minLength), Number.POSITIVE_INFINITY);
596
- const result: T[] = new Array(minLength);
597
- for (let i = 0; i < minLength; i += 1) {
598
- const arr = arrays.map((it) => it[i]);
599
- result[i] = arr as T;
600
- }
601
- return result;
602
- }
603
-
604
- export function getLayerSize({ props }: VizarrLayer) {
605
- const loader = resolveLoaderFromLayerProps(props);
606
- const [baseResolution, maxZoom] = Array.isArray(loader) ? [loader[0], loader.length] : [loader, 0];
607
- const interleaved = isInterleaved(baseResolution.shape);
608
- let [height, width] = baseResolution.shape.slice(interleaved ? -3 : -2);
609
- if (isGridLayerProps(props)) {
610
- // TODO: Don't hardcode spacer size. Probably best to inspect the deck.gl Layers rather than
611
- // the Layer Props.
612
- const spacer = 5;
613
- height = (height + spacer) * props.rows;
614
- width = (width + spacer) * props.columns;
615
- }
616
- return { height, width, maxZoom };
617
- }
618
-
619
- export function getPhysicalSizes(attrs: zarr.Attributes) {
620
- if (isMultiscales(attrs)) {
621
- const axes = getNgffAxes(attrs.multiscales);
622
- const ct = coordinateTransformationsToMatrix(attrs.multiscales);
623
- const matrixIndices = {
624
- x: 0,
625
- y: 5,
626
- z: 10,
627
- };
628
- const physicalSizes = axes
629
- .filter((a) => a.type === "space")
630
- .reduce((acc: { [key: string]: viv.PhysicalSize }, { name, unit }: Ome.Axis) => {
631
- acc[name] = { size: ct[matrixIndices[name as keyof typeof matrixIndices]], unit: unit ?? "" };
632
- return acc;
633
- }, {});
634
- // @TODO: get t size from multiscales.coordinateTransformations if axis is present
635
- return physicalSizes;
636
- }
637
- }
638
-
639
- // @TODO: remove after updating deck.gl
640
- // From deck.gl geo-layers tileset-2d utils
641
- export function transformBox(bbox: number[], modelMatrix: Matrix4): number[] {
642
- const transformedCoords = [
643
- // top-left
644
- modelMatrix.transformAsPoint([bbox[0], bbox[1]]),
645
- // top-right
646
- modelMatrix.transformAsPoint([bbox[2], bbox[1]]),
647
- // bottom-left
648
- modelMatrix.transformAsPoint([bbox[0], bbox[3]]),
649
- // bottom-right
650
- modelMatrix.transformAsPoint([bbox[2], bbox[3]]),
651
- ];
652
- const transformedBox = [
653
- // Minimum x coord
654
- Math.min(...transformedCoords.map((i) => i[0])),
655
- // Minimum y coord
656
- Math.min(...transformedCoords.map((i) => i[1])),
657
- // Max x coord
658
- Math.max(...transformedCoords.map((i) => i[0])),
659
- // Max y coord
660
- Math.max(...transformedCoords.map((i) => i[1])),
661
- ];
662
- return transformedBox;
663
- }
package/tsconfig.json DELETED
@@ -1,14 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "module": "esnext",
4
- "target": "esnext",
5
- "moduleResolution": "bundler",
6
- "noEmit": true,
7
- "strict": true,
8
- "skipLibCheck": true,
9
- "forceConsistentCasingInFileNames": true,
10
- "verbatimModuleSyntax": true,
11
- "jsx": "preserve",
12
- "types": ["vite/client"]
13
- }
14
- }