zarrextra 0.1.0 → 0.2.1

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
@@ -6,6 +6,8 @@ This package provides helper functions and types for:
6
6
  - Parsing zarr store contents into a tree structure
7
7
  - Working with consolidated metadata
8
8
  - Serializing zarr tree structures
9
+ - Registering additional Zarrita codecs, including JP2K (`imagecodecs_jpeg2k`)
10
+ - Loading OME-Zarr multiscales from an existing Zarrita store for Viv-compatible viewers
9
11
  - Result type for explicit error handling
10
12
 
11
13
  ## Result Type
@@ -39,3 +41,82 @@ if (result.ok) {
39
41
  ## API
40
42
 
41
43
  See the TypeScript definitions for full API documentation.
44
+
45
+ ## Codec registration
46
+
47
+ `registerJpeg2kCodec()` registers decode support for the standard
48
+ `imagecodecs_jpeg2k` codec id from the Zarr codecs registry. The default decoder
49
+ uses the optional `@cornerstonejs/codec-openjpeg` package.
50
+
51
+ ```typescript
52
+ import { registerJpeg2kCodec } from 'zarrextra';
53
+
54
+ registerJpeg2kCodec();
55
+ ```
56
+
57
+ Applications can pass a custom decoder for alternate WASM loading or tests:
58
+
59
+ ```typescript
60
+ import OpenJPEGJS from '@cornerstonejs/codec-openjpeg/decode';
61
+ import { createOpenJpegDecoder, registerJpeg2kCodec } from 'zarrextra';
62
+
63
+ registerJpeg2kCodec({ decoder: createOpenJpegDecoder(OpenJPEGJS) });
64
+ ```
65
+
66
+ `registerExperimentalHtj2kCodec()` registers decode for `experimental.openjph_htj2k`
67
+ (new writes) and legacy `experimental.imagecodecs_htj2k` fixtures. Keep datasets
68
+ using either id clearly labelled until there is community/registry alignment.
69
+
70
+ ```typescript
71
+ import OpenJPHJS from '@cornerstonejs/codec-openjph';
72
+ import { createOpenJphDecoder, registerExperimentalHtj2kCodec } from 'zarrextra';
73
+
74
+ registerExperimentalHtj2kCodec({ decoder: createOpenJphDecoder(OpenJPHJS) });
75
+ ```
76
+
77
+ For offline encode (fixtures, recompress), use `encodeHtj2kPlane()` or
78
+ `createOpenJphEncoder()` from the same package. Python `spatialdata-codec-writer`
79
+ uses vendored OpenJPH WASM with a persistent Node worker pool; new stores use
80
+ codec id `experimental.openjph_htj2k`.
81
+
82
+ ```typescript
83
+ import { encodeHtj2kPlane } from 'zarrextra';
84
+
85
+ const plane = new Uint16Array(width * height);
86
+ // ... fill plane ...
87
+ const encoded = await encodeHtj2kPlane(plane, { width, height }, {
88
+ reversible: false,
89
+ quality: 75,
90
+ });
91
+ ```
92
+
93
+ ## Worker-backed chunk decode (browser)
94
+
95
+ JP2K and other codec work can block the main thread for a long time. For browser
96
+ apps, use the optional `zarrextra/workers` entry with
97
+ [`@fideus-labs/fizarrita`](https://www.npmjs.com/package/@fideus-labs/fizarrita)
98
+ to offload chunk decode to a Web Worker pool:
99
+
100
+ ```typescript
101
+ import { enableWorkerChunkDecode, disableWorkerChunkDecode } from 'zarrextra/workers';
102
+
103
+ enableWorkerChunkDecode();
104
+ // ... load JP2K-backed SpatialData and render tiles ...
105
+ disableWorkerChunkDecode();
106
+ ```
107
+
108
+ This uses a thin custom codec worker that registers zarrextra image codecs
109
+ (including OpenJPEG for `imagecodecs_jpeg2k` and OpenJPH for experimental HTJ2K)
110
+ into `zarrita.registry` inside the worker before fizarrita's codec handler runs. Built-in zarrita codecs (bytes, zstd,
111
+ blosc, …) are also adapted to fizarrita's worker metadata shape via
112
+ `wrapZarrRegistryForFizarritaWorker()`. Main-thread `registerJpeg2kCodec()` is not
113
+ required for that path.
114
+
115
+ | Context | Setup |
116
+ |---------|-------|
117
+ | Node / CI | `registerJpeg2kCodec()` / `registerExperimentalHtj2kCodec()` on the main thread |
118
+ | Browser | `enableWorkerChunkDecode()` from `zarrextra/workers` before loading JP2K or HTJ2K data |
119
+
120
+ Optional dependencies: `@fideus-labs/fizarrita`, `@fideus-labs/worker-pool`,
121
+ `@cornerstonejs/codec-openjpeg`, and `@cornerstonejs/codec-openjph` (bundled into
122
+ the worker script).
@@ -0,0 +1,46 @@
1
+ import * as e from "zarrita";
2
+ //#region src/chunkDecode.ts
3
+ var t = { kind: "main" };
4
+ function n() {
5
+ return t;
6
+ }
7
+ function r(e) {
8
+ t = e;
9
+ }
10
+ var i;
11
+ function a(e) {
12
+ i = e;
13
+ }
14
+ function o(e, t) {
15
+ return t ? t.aborted ? Promise.reject(t.reason ?? new DOMException("Aborted", "AbortError")) : new Promise((n, r) => {
16
+ let i = () => {
17
+ r(t.reason ?? new DOMException("Aborted", "AbortError"));
18
+ };
19
+ t.addEventListener("abort", i, { once: !0 }), e.then((e) => {
20
+ t.removeEventListener("abort", i), n(e);
21
+ }, (e) => {
22
+ t.removeEventListener("abort", i), r(e);
23
+ });
24
+ }) : e;
25
+ }
26
+ async function s(t, r, a) {
27
+ let s = n();
28
+ if (s.kind === "fizarrita") {
29
+ if (!i) throw Error("Worker chunk decode is enabled but fizarrita getWorker is not loaded. Import from zarrextra/workers instead of setting the backend directly.");
30
+ let e = await o(i(t, r, {
31
+ pool: s.pool,
32
+ workerUrl: s.options?.workerUrl,
33
+ useSharedArrayBuffer: s.options?.useSharedArrayBuffer,
34
+ cache: s.options?.cache
35
+ }), a?.signal);
36
+ if (typeof e != "object" || !e || !("data" in e)) throw Error("Expected chunk object from fizarrita getWorker().");
37
+ return e;
38
+ }
39
+ let c = await e.get(t, r, a);
40
+ if (typeof c != "object" || !c || !("data" in c)) throw Error("Expected chunk object from zarr.get().");
41
+ return c;
42
+ }
43
+ //#endregion
44
+ export { r as n, a as r, s as t };
45
+
46
+ //# sourceMappingURL=chunkDecode-Dtm8HfWS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunkDecode-Dtm8HfWS.js","names":[],"sources":["../src/chunkDecode.ts"],"sourcesContent":["import * as zarr from 'zarrita';\nimport type { WorkerPool } from '@fideus-labs/worker-pool';\nimport type { ChunkCache, GetWorkerOptions } from '@fideus-labs/fizarrita';\n\nexport type ZarrGetOptions = {\n signal?: AbortSignal;\n};\n\nexport type FizarritaGetWorkerOptions = Pick<\n GetWorkerOptions,\n 'workerUrl' | 'useSharedArrayBuffer' | 'cache'\n> & {\n pool: WorkerPool;\n};\n\nexport type ChunkDecodeBackend =\n | { kind: 'main' }\n | {\n kind: 'fizarrita';\n pool: WorkerPool;\n options?: Omit<FizarritaGetWorkerOptions, 'pool'>;\n };\n\nlet chunkDecodeBackend: ChunkDecodeBackend = { kind: 'main' };\n\nexport function getChunkDecodeBackend(): ChunkDecodeBackend {\n return chunkDecodeBackend;\n}\n\nexport function setChunkDecodeBackend(backend: ChunkDecodeBackend): void {\n chunkDecodeBackend = backend;\n}\n\ntype GetWorkerFn = (\n arr: zarr.Array<zarr.DataType>,\n selection: Array<number | zarr.Slice | null> | null,\n options: FizarritaGetWorkerOptions\n) => Promise<zarr.Chunk<zarr.DataType>>;\n\nlet getWorkerImpl: GetWorkerFn | undefined;\n\nexport function setFizarritaGetWorker(impl: GetWorkerFn): void {\n getWorkerImpl = impl;\n}\n\nfunction rejectOnAbort<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {\n if (!signal) {\n return promise;\n }\n if (signal.aborted) {\n return Promise.reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));\n }\n return new Promise((resolve, reject) => {\n const onAbort = () => {\n reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));\n };\n signal.addEventListener('abort', onAbort, { once: true });\n promise.then(\n (value) => {\n signal.removeEventListener('abort', onAbort);\n resolve(value);\n },\n (error: unknown) => {\n signal.removeEventListener('abort', onAbort);\n reject(error);\n }\n );\n });\n}\n\nexport async function getZarrChunk<D extends zarr.DataType>(\n arr: zarr.Array<D>,\n selection: Array<number | zarr.Slice | null>,\n opts?: ZarrGetOptions\n): Promise<zarr.Chunk<D>> {\n const backend = getChunkDecodeBackend();\n if (backend.kind === 'fizarrita') {\n if (!getWorkerImpl) {\n throw new Error(\n 'Worker chunk decode is enabled but fizarrita getWorker is not loaded. ' +\n 'Import from zarrextra/workers instead of setting the backend directly.'\n );\n }\n const result = await rejectOnAbort(\n getWorkerImpl(arr, selection, {\n pool: backend.pool,\n workerUrl: backend.options?.workerUrl,\n useSharedArrayBuffer: backend.options?.useSharedArrayBuffer,\n cache: backend.options?.cache,\n }),\n opts?.signal\n );\n if (typeof result !== 'object' || result === null || !('data' in result)) {\n throw new Error('Expected chunk object from fizarrita getWorker().');\n }\n return result as zarr.Chunk<D>;\n }\n\n const result = await zarr.get(arr, selection, opts);\n if (typeof result !== 'object' || result === null || !('data' in result)) {\n throw new Error('Expected chunk object from zarr.get().');\n }\n return result as zarr.Chunk<D>;\n}\n"],"mappings":";;AAuBA,IAAI,IAAyC,EAAE,MAAM,QAAQ;AAE7D,SAAgB,IAA4C;AAC1D,QAAO;;AAGT,SAAgB,EAAsB,GAAmC;AACvE,KAAqB;;AASvB,IAAI;AAEJ,SAAgB,EAAsB,GAAyB;AAC7D,KAAgB;;AAGlB,SAAS,EAAiB,GAAqB,GAAkC;AAO/E,QANK,IAGD,EAAO,UACF,QAAQ,OAAO,EAAO,UAAU,IAAI,aAAa,WAAW,aAAa,CAAC,GAE5E,IAAI,SAAS,GAAS,MAAW;EACtC,IAAM,UAAgB;AACpB,KAAO,EAAO,UAAU,IAAI,aAAa,WAAW,aAAa,CAAC;;AAGpE,EADA,EAAO,iBAAiB,SAAS,GAAS,EAAE,MAAM,IAAM,CAAC,EACzD,EAAQ,MACL,MAAU;AAET,GADA,EAAO,oBAAoB,SAAS,EAAQ,EAC5C,EAAQ,EAAM;MAEf,MAAmB;AAElB,GADA,EAAO,oBAAoB,SAAS,EAAQ,EAC5C,EAAO,EAAM;IAEhB;GACD,GApBO;;AAuBX,eAAsB,EACpB,GACA,GACA,GACwB;CACxB,IAAM,IAAU,GAAuB;AACvC,KAAI,EAAQ,SAAS,aAAa;AAChC,MAAI,CAAC,EACH,OAAU,MACR,+IAED;EAEH,IAAM,IAAS,MAAM,EACnB,EAAc,GAAK,GAAW;GAC5B,MAAM,EAAQ;GACd,WAAW,EAAQ,SAAS;GAC5B,sBAAsB,EAAQ,SAAS;GACvC,OAAO,EAAQ,SAAS;GACzB,CAAC,EACF,GAAM,OACP;AACD,MAAI,OAAO,KAAW,aAAY,KAAmB,EAAE,UAAU,GAC/D,OAAU,MAAM,oDAAoD;AAEtE,SAAO;;CAGT,IAAM,IAAS,MAAM,EAAK,IAAI,GAAK,GAAW,EAAK;AACnD,KAAI,OAAO,KAAW,aAAY,KAAmB,EAAE,UAAU,GAC/D,OAAU,MAAM,yCAAyC;AAE3D,QAAO"}
@@ -0,0 +1,23 @@
1
+ import { WorkerPool } from '@fideus-labs/worker-pool';
2
+ import { GetWorkerOptions } from '@fideus-labs/fizarrita';
3
+ import * as zarr from 'zarrita';
4
+ export type ZarrGetOptions = {
5
+ signal?: AbortSignal;
6
+ };
7
+ export type FizarritaGetWorkerOptions = Pick<GetWorkerOptions, 'workerUrl' | 'useSharedArrayBuffer' | 'cache'> & {
8
+ pool: WorkerPool;
9
+ };
10
+ export type ChunkDecodeBackend = {
11
+ kind: 'main';
12
+ } | {
13
+ kind: 'fizarrita';
14
+ pool: WorkerPool;
15
+ options?: Omit<FizarritaGetWorkerOptions, 'pool'>;
16
+ };
17
+ export declare function getChunkDecodeBackend(): ChunkDecodeBackend;
18
+ export declare function setChunkDecodeBackend(backend: ChunkDecodeBackend): void;
19
+ type GetWorkerFn = (arr: zarr.Array<zarr.DataType>, selection: Array<number | zarr.Slice | null> | null, options: FizarritaGetWorkerOptions) => Promise<zarr.Chunk<zarr.DataType>>;
20
+ export declare function setFizarritaGetWorker(impl: GetWorkerFn): void;
21
+ export declare function getZarrChunk<D extends zarr.DataType>(arr: zarr.Array<D>, selection: Array<number | zarr.Slice | null>, opts?: ZarrGetOptions): Promise<zarr.Chunk<D>>;
22
+ export {};
23
+ //# sourceMappingURL=chunkDecode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunkDecode.d.ts","sourceRoot":"","sources":["../src/chunkDecode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,SAAS,CAAC;AAChC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EAAc,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE3E,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,gBAAgB,EAChB,WAAW,GAAG,sBAAsB,GAAG,OAAO,CAC/C,GAAG;IACF,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,CAAC,EAAE,IAAI,CAAC,yBAAyB,EAAE,MAAM,CAAC,CAAC;CACnD,CAAC;AAIN,wBAAgB,qBAAqB,IAAI,kBAAkB,CAE1D;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAEvE;AAED,KAAK,WAAW,GAAG,CACjB,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,EAC9B,SAAS,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,IAAI,EACnD,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;AAIxC,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAE7D;AA2BD,wBAAsB,YAAY,CAAC,CAAC,SAAS,IAAI,CAAC,QAAQ,EACxD,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAClB,SAAS,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,EAC5C,IAAI,CAAC,EAAE,cAAc,GACpB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CA6BxB"}