@aics/vole-core 3.12.4 → 3.13.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 +21 -13
- package/es/View3d.js +21 -5
- package/es/Volume.js +1 -1
- package/es/VolumeCache.js +10 -3
- package/es/VolumeRenderSettings.js +11 -0
- package/es/loaders/JsonImageInfoLoader.js +2 -1
- package/es/loaders/OmeZarrLoader.js +30 -31
- package/es/loaders/TiffLoader.js +3 -1
- package/es/loaders/VolumeLoadError.js +1 -1
- package/es/loaders/zarr_utils/ChunkPrefetchIterator.js +7 -0
- package/es/loaders/zarr_utils/validation.js +18 -7
- package/es/loaders/zarr_utils/wrapArray.js +39 -0
- package/es/types/NaiveSurfaceNets.d.ts +1 -1
- package/es/types/RayMarchedAtlasVolume.d.ts +1 -1
- package/es/types/ThreeJsPanel.d.ts +2 -2
- package/es/types/TrackballControls.d.ts +1 -1
- package/es/types/View3d.d.ts +6 -2
- package/es/types/VolumeCache.d.ts +5 -2
- package/es/types/VolumeDrawable.d.ts +1 -1
- package/es/types/VolumeRenderImpl.d.ts +1 -1
- package/es/types/index.d.ts +1 -1
- package/es/types/loaders/zarr_utils/types.d.ts +17 -12
- package/es/types/loaders/zarr_utils/validation.d.ts +14 -2
- package/es/types/loaders/zarr_utils/wrapArray.d.ts +7 -0
- package/es/types/workers/VolumeLoaderContext.d.ts +9 -13
- package/es/types/workers/types.d.ts +25 -16
- package/es/workers/VolumeLoadWorker.js +54 -32
- package/es/workers/VolumeLoaderContext.js +52 -51
- package/es/workers/types.js +17 -7
- package/package.json +14 -14
- package/es/loaders/zarr_utils/WrappedStore.js +0 -51
- package/es/test/ChunkPrefetchIterator.test.js +0 -208
- package/es/test/RequestQueue.test.js +0 -442
- package/es/test/SubscribableRequestQueue.test.js +0 -244
- package/es/test/VolumeCache.test.js +0 -118
- package/es/test/VolumeRenderSettings.test.js +0 -71
- package/es/test/lut.test.js +0 -671
- package/es/test/num_utils.test.js +0 -140
- package/es/test/volume.test.js +0 -98
- package/es/test/zarr_utils.test.js +0 -358
- package/es/types/loaders/zarr_utils/WrappedStore.d.ts +0 -24
- package/es/types/test/ChunkPrefetchIterator.test.d.ts +0 -1
- package/es/types/test/RequestQueue.test.d.ts +0 -1
- package/es/types/test/SubscribableRequestQueue.test.d.ts +0 -1
- package/es/types/test/VolumeCache.test.d.ts +0 -1
- package/es/types/test/VolumeRenderSettings.test.d.ts +0 -1
- package/es/types/test/lut.test.d.ts +0 -1
- package/es/types/test/num_utils.test.d.ts +0 -1
- package/es/types/test/volume.test.d.ts +0 -1
- package/es/types/test/zarr_utils.test.d.ts +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as zarr from "
|
|
2
|
-
import
|
|
1
|
+
import * as zarr from "zarrita";
|
|
2
|
+
import { AsyncReadable } from "@zarrita/storage";
|
|
3
3
|
import type SubscribableRequestQueue from "../../utils/SubscribableRequestQueue.js";
|
|
4
4
|
export type TCZYX<T> = [T, T, T, T, T];
|
|
5
5
|
export type SubscriberId = ReturnType<SubscribableRequestQueue["addSubscriber"]>;
|
|
@@ -53,16 +53,16 @@ export type OMEMultiscale = {
|
|
|
53
53
|
};
|
|
54
54
|
/** https://ngff.openmicroscopy.org/latest/#omero-md */
|
|
55
55
|
export type OmeroTransitionalMetadata = {
|
|
56
|
-
id
|
|
57
|
-
name
|
|
58
|
-
version
|
|
56
|
+
id?: number;
|
|
57
|
+
name?: string;
|
|
58
|
+
version?: string;
|
|
59
59
|
channels: {
|
|
60
|
-
active
|
|
61
|
-
coefficient
|
|
60
|
+
active?: boolean;
|
|
61
|
+
coefficient?: number;
|
|
62
62
|
color: string;
|
|
63
|
-
family
|
|
64
|
-
inverted
|
|
65
|
-
label
|
|
63
|
+
family?: string;
|
|
64
|
+
inverted?: boolean;
|
|
65
|
+
label?: string;
|
|
66
66
|
window: {
|
|
67
67
|
end: number;
|
|
68
68
|
max: number;
|
|
@@ -73,9 +73,14 @@ export type OmeroTransitionalMetadata = {
|
|
|
73
73
|
};
|
|
74
74
|
export type OMEZarrMetadata = {
|
|
75
75
|
multiscales: OMEMultiscale[];
|
|
76
|
-
omero
|
|
76
|
+
omero?: OmeroTransitionalMetadata;
|
|
77
|
+
};
|
|
78
|
+
export type WrappedArrayOpts = {
|
|
79
|
+
subscriber?: SubscriberId;
|
|
80
|
+
reportChunk?: (coords: number[], subscriber: SubscriberId) => void;
|
|
81
|
+
isPrefetch?: boolean;
|
|
77
82
|
};
|
|
78
|
-
export type NumericZarrArray = zarr.Array<zarr.NumberDataType,
|
|
83
|
+
export type NumericZarrArray = zarr.Array<zarr.NumberDataType, AsyncReadable<RequestInit & WrappedArrayOpts>>;
|
|
79
84
|
/** A record with everything we need to access and use a single remote source of multiscale OME-Zarr data. */
|
|
80
85
|
export type ZarrSource = {
|
|
81
86
|
/** Representations of each scale level in this zarr. We pick one and pass it to zarrita to load data. */
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { OMEZarrMetadata } from "./types.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* If `meta` is the top-level metadata of a zarr node formatted according to the OME-Zarr spec version 0.5, returns
|
|
4
|
+
* the object formatted according to v0.4 of the spec. For our purposes this just means flattening out the `ome` key.
|
|
5
|
+
*
|
|
6
|
+
* Return type is `unknown` because this does no actual validation; use `validateOMEZarrMetadata` for that.
|
|
7
|
+
*/
|
|
8
|
+
export declare const toOMEZarrMetaV4: (meta: unknown) => unknown;
|
|
9
|
+
/** Intermediate stage of validation, before we've picked a single multiscale to validate */
|
|
10
|
+
export type MultiscaleRecord = {
|
|
11
|
+
multiscales: unknown[];
|
|
12
|
+
};
|
|
13
|
+
export declare function assertMetadataHasMultiscales(meta: unknown, name?: string): asserts meta is MultiscaleRecord;
|
|
14
|
+
/**
|
|
15
|
+
* Validates that the `OMEZarrMetadata` record `meta` has the minimal amount of data required to open a volume. Since
|
|
4
16
|
* we only ever open one multiscale, we only validate the multiscale metadata record at index `multiscaleIdx` here.
|
|
5
17
|
* `name` is used in error messages to identify the source of the metadata.
|
|
6
18
|
*/
|
|
7
|
-
export declare function validateOMEZarrMetadata(
|
|
19
|
+
export declare function validateOMEZarrMetadata(meta: MultiscaleRecord, multiscaleIdx?: number, name?: string): asserts meta is OMEZarrMetadata;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Array as ZarrArray, AsyncReadable, DataType } from "zarrita";
|
|
2
|
+
import VolumeCache from "../../VolumeCache.js";
|
|
3
|
+
import type { WrappedArrayOpts } from "./types.js";
|
|
4
|
+
import SubscribableRequestQueue from "../../utils/SubscribableRequestQueue.js";
|
|
5
|
+
type AsyncReadableExt<Opts> = AsyncReadable<Opts & WrappedArrayOpts>;
|
|
6
|
+
export default function wrapArray<T extends DataType, Opts = unknown, Store extends AsyncReadable<Opts> = AsyncReadable<Opts>>(array: ZarrArray<T, Store>, basePath: string, cache?: VolumeCache, queue?: SubscribableRequestQueue): ZarrArray<T, AsyncReadableExt<Opts>>;
|
|
7
|
+
export {};
|
|
@@ -4,9 +4,8 @@ import { CreateLoaderOptions, PrefetchDirection } from "../loaders/index.js";
|
|
|
4
4
|
import { ThreadableVolumeLoader, LoadSpec, RawChannelDataCallback, LoadedVolumeInfo } from "../loaders/IVolumeLoader.js";
|
|
5
5
|
import { RawArrayLoader } from "../loaders/RawArrayLoader.js";
|
|
6
6
|
import { TiffLoader } from "../loaders/TiffLoader.js";
|
|
7
|
-
import type {
|
|
7
|
+
import type { ChannelLoadEvent, MetadataUpdateEvent, WorkerMsgTypeGlobal, WorkerMsgTypeWithLoader, WorkerRequestPayload, WorkerResponsePayload } from "./types.js";
|
|
8
8
|
import type { ZarrLoaderFetchOptions } from "../loaders/OmeZarrLoader.js";
|
|
9
|
-
import { WorkerMsgType } from "./types.js";
|
|
10
9
|
/**
|
|
11
10
|
* A handle that holds the worker and manages requests and messages to/from it.
|
|
12
11
|
*
|
|
@@ -22,9 +21,7 @@ declare class SharedLoadWorkerHandle {
|
|
|
22
21
|
private worker;
|
|
23
22
|
private pendingRequests;
|
|
24
23
|
private workerOpen;
|
|
25
|
-
|
|
26
|
-
onChannelData: ((e: ChannelLoadEvent) => void) | undefined;
|
|
27
|
-
onUpdateMetadata: ((e: MetadataUpdateEvent) => void) | undefined;
|
|
24
|
+
onEvent: ((e: ChannelLoadEvent | MetadataUpdateEvent) => void) | undefined;
|
|
28
25
|
constructor();
|
|
29
26
|
/** Given a handle for settling a promise when a response is received from the worker, store it and return its ID */
|
|
30
27
|
private registerMessagePromise;
|
|
@@ -35,16 +32,16 @@ declare class SharedLoadWorkerHandle {
|
|
|
35
32
|
* Send a message of type `T` to the worker.
|
|
36
33
|
* Returns a `Promise` that resolves with the worker's response, or rejects with an error message.
|
|
37
34
|
*/
|
|
38
|
-
sendMessage<T extends
|
|
35
|
+
sendMessage<T extends WorkerMsgTypeGlobal>(type: T, payload: WorkerRequestPayload<T>): Promise<WorkerResponsePayload<T>>;
|
|
36
|
+
sendMessage<T extends WorkerMsgTypeWithLoader>(type: T, payload: WorkerRequestPayload<T>, loaderId: number): Promise<WorkerResponsePayload<T>>;
|
|
39
37
|
/** Receive a message from the worker. If it's an event, call a callback; otherwise, resolve/reject a promise. */
|
|
40
38
|
private receiveMessage;
|
|
41
|
-
setThrottleChannelData(throttle: boolean): void;
|
|
42
39
|
}
|
|
43
40
|
/**
|
|
44
41
|
* A context in which volume loaders can be run, which allows loading to run on a WebWorker (where it won't block
|
|
45
42
|
* rendering or UI updates) and loaders to share a single `VolumeCache` and `RequestQueue`.
|
|
46
43
|
*
|
|
47
|
-
*
|
|
44
|
+
* # To use:
|
|
48
45
|
* 1. Create a `VolumeLoaderContext` with the desired cache and queue configuration.
|
|
49
46
|
* 2. Before creating a loader, await `onOpen` to ensure the worker is ready.
|
|
50
47
|
* 3. Create a loader with `createLoader`. This accepts nearly the same arguments as `createVolumeLoader`, but without
|
|
@@ -56,14 +53,15 @@ declare class SharedLoadWorkerHandle {
|
|
|
56
53
|
*/
|
|
57
54
|
declare class VolumeLoaderContext {
|
|
58
55
|
private workerHandle;
|
|
56
|
+
private loaders;
|
|
59
57
|
private openPromise;
|
|
60
|
-
private
|
|
61
|
-
private activeLoaderId;
|
|
58
|
+
private throttleChannelData;
|
|
62
59
|
constructor(maxCacheSize?: number, maxActiveRequests?: number, maxLowPriorityRequests?: number);
|
|
63
60
|
/** Returns a `Promise` that resolves when the worker is ready. `await` it before trying to create a loader. */
|
|
64
61
|
onOpen(): Promise<void>;
|
|
65
62
|
/** Close this context, its worker, and any active loaders. */
|
|
66
63
|
close(): void;
|
|
64
|
+
private handleEvent;
|
|
67
65
|
/**
|
|
68
66
|
* Create a new loader within this context. This loader will share the context's `VolumeCache` and `RequestQueue`.
|
|
69
67
|
*
|
|
@@ -71,7 +69,6 @@ declare class VolumeLoaderContext {
|
|
|
71
69
|
*/
|
|
72
70
|
createLoader(path: string | string[], options?: Omit<CreateLoaderOptions, "cache" | "queue">): Promise<WorkerLoader | TiffLoader | RawArrayLoader>;
|
|
73
71
|
setThrottleChannelData(throttle: boolean): void;
|
|
74
|
-
getActiveLoader(): WorkerLoader | undefined;
|
|
75
72
|
}
|
|
76
73
|
/**
|
|
77
74
|
* A handle to an instance of `IVolumeLoader` (technically, a `ThreadableVolumeLoader`) running on a WebWorker.
|
|
@@ -81,12 +78,11 @@ declare class VolumeLoaderContext {
|
|
|
81
78
|
declare class WorkerLoader extends ThreadableVolumeLoader {
|
|
82
79
|
private loaderId;
|
|
83
80
|
private workerHandle;
|
|
84
|
-
private isOpen;
|
|
85
81
|
private currentLoadId;
|
|
86
82
|
private currentLoadCallback;
|
|
87
83
|
private currentMetadataUpdateCallback;
|
|
88
84
|
constructor(loaderId: number, workerHandle: SharedLoadWorkerHandle);
|
|
89
|
-
private
|
|
85
|
+
private getLoaderId;
|
|
90
86
|
/** Close and permanently invalidate this loader. */
|
|
91
87
|
close(): void;
|
|
92
88
|
/**
|
|
@@ -9,13 +9,18 @@ import type { ZarrLoaderFetchOptions } from "../loaders/OmeZarrLoader.js";
|
|
|
9
9
|
export declare const enum WorkerMsgType {
|
|
10
10
|
INIT = 0,
|
|
11
11
|
CREATE_LOADER = 1,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
CLOSE_LOADER = 2,
|
|
13
|
+
CREATE_VOLUME = 3,
|
|
14
|
+
LOAD_DIMS = 4,
|
|
15
|
+
LOAD_VOLUME_DATA = 5,
|
|
16
|
+
SET_PREFETCH_PRIORITY_DIRECTIONS = 6,
|
|
17
|
+
SYNCHRONIZE_MULTICHANNEL_LOADING = 7,
|
|
18
|
+
UPDATE_FETCH_OPTIONS = 8
|
|
18
19
|
}
|
|
20
|
+
/** The variants of `WorkerMessageType` which represent "global" actions that don't require a specific loader */
|
|
21
|
+
export type WorkerMsgTypeGlobal = WorkerMsgType.INIT | WorkerMsgType.CREATE_LOADER;
|
|
22
|
+
/** The variants of `WorkerMessageType` which represent actions on a specific loader */
|
|
23
|
+
export type WorkerMsgTypeWithLoader = Exclude<WorkerMsgType, WorkerMsgTypeGlobal>;
|
|
19
24
|
/** The kind of response a worker can return - `SUCCESS`, `ERROR`, or `EVENT`. */
|
|
20
25
|
export declare const enum WorkerResponseResult {
|
|
21
26
|
SUCCESS = 0,
|
|
@@ -29,9 +34,13 @@ export declare const enum WorkerEventType {
|
|
|
29
34
|
/** Fired when data for a channel (or batch of channels) is loaded */
|
|
30
35
|
CHANNEL_LOAD = 1
|
|
31
36
|
}
|
|
32
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* All messages to/from a worker carry a `msgId`, a `type`, and a `payload` (whose type is determined by `type`).
|
|
39
|
+
* Messages which operate on a specific loader also require a `loaderId`.
|
|
40
|
+
*/
|
|
33
41
|
type WorkerMsgBase<T extends WorkerMsgType, P> = {
|
|
34
42
|
msgId: number;
|
|
43
|
+
loaderId: T extends WorkerMsgTypeWithLoader ? number : undefined;
|
|
35
44
|
type: T;
|
|
36
45
|
payload: P;
|
|
37
46
|
};
|
|
@@ -46,12 +55,12 @@ export type WorkerRequestPayload<T extends WorkerMsgType> = {
|
|
|
46
55
|
path: string | string[];
|
|
47
56
|
options?: CreateLoaderOptions;
|
|
48
57
|
};
|
|
58
|
+
[WorkerMsgType.CLOSE_LOADER]: void;
|
|
49
59
|
[WorkerMsgType.CREATE_VOLUME]: LoadSpec;
|
|
50
60
|
[WorkerMsgType.LOAD_DIMS]: LoadSpec;
|
|
51
61
|
[WorkerMsgType.LOAD_VOLUME_DATA]: {
|
|
52
62
|
imageInfo: ImageInfo;
|
|
53
63
|
loadSpec: LoadSpec;
|
|
54
|
-
loaderId: number;
|
|
55
64
|
loadId: number;
|
|
56
65
|
};
|
|
57
66
|
[WorkerMsgType.SET_PREFETCH_PRIORITY_DIRECTIONS]: PrefetchDirection[];
|
|
@@ -61,7 +70,8 @@ export type WorkerRequestPayload<T extends WorkerMsgType> = {
|
|
|
61
70
|
/** Maps each `WorkerMsgType` to the type of the payload of responses of that type. */
|
|
62
71
|
export type WorkerResponsePayload<T extends WorkerMsgType> = {
|
|
63
72
|
[WorkerMsgType.INIT]: void;
|
|
64
|
-
[WorkerMsgType.CREATE_LOADER]:
|
|
73
|
+
[WorkerMsgType.CREATE_LOADER]: number | undefined;
|
|
74
|
+
[WorkerMsgType.CLOSE_LOADER]: void;
|
|
65
75
|
[WorkerMsgType.CREATE_VOLUME]: LoadedVolumeInfo;
|
|
66
76
|
[WorkerMsgType.LOAD_DIMS]: VolumeDims[];
|
|
67
77
|
[WorkerMsgType.LOAD_VOLUME_DATA]: void;
|
|
@@ -69,11 +79,13 @@ export type WorkerResponsePayload<T extends WorkerMsgType> = {
|
|
|
69
79
|
[WorkerMsgType.SYNCHRONIZE_MULTICHANNEL_LOADING]: void;
|
|
70
80
|
[WorkerMsgType.UPDATE_FETCH_OPTIONS]: void;
|
|
71
81
|
}[T];
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
eventType: WorkerEventType.CHANNEL_LOAD;
|
|
82
|
+
type WorkerEventBase<T extends WorkerEventType> = {
|
|
83
|
+
eventType: T;
|
|
75
84
|
loaderId: number;
|
|
76
85
|
loadId: number;
|
|
86
|
+
};
|
|
87
|
+
/** Event for when a batch of channel data loads. */
|
|
88
|
+
export type ChannelLoadEvent = WorkerEventBase<WorkerEventType.CHANNEL_LOAD> & {
|
|
77
89
|
channelIndex: number[];
|
|
78
90
|
dtype: NumberType[];
|
|
79
91
|
data: TypedArray<NumberType>[];
|
|
@@ -81,10 +93,7 @@ export type ChannelLoadEvent = {
|
|
|
81
93
|
atlasDims?: [number, number];
|
|
82
94
|
};
|
|
83
95
|
/** Event for when metadata updates. */
|
|
84
|
-
export type MetadataUpdateEvent = {
|
|
85
|
-
eventType: WorkerEventType.METADATA_UPDATE;
|
|
86
|
-
loaderId: number;
|
|
87
|
-
loadId: number;
|
|
96
|
+
export type MetadataUpdateEvent = WorkerEventBase<WorkerEventType.METADATA_UPDATE> & {
|
|
88
97
|
imageInfo?: ImageInfo;
|
|
89
98
|
loadSpec?: LoadSpec;
|
|
90
99
|
};
|
|
@@ -9,9 +9,16 @@ import { rebuildLoadSpec } from "./util.js";
|
|
|
9
9
|
let cache = undefined;
|
|
10
10
|
let queue = undefined;
|
|
11
11
|
let subscribableQueue = undefined;
|
|
12
|
-
let
|
|
12
|
+
let loaderCount = 0;
|
|
13
|
+
const loaders = new Map();
|
|
14
|
+
const getLoader = loaderId => {
|
|
15
|
+
const loader = loaders.get(loaderId);
|
|
16
|
+
if (loader === undefined) {
|
|
17
|
+
throw new VolumeLoadError(`Loader with ID ${loaderId} does not exist`);
|
|
18
|
+
}
|
|
19
|
+
return loader;
|
|
20
|
+
};
|
|
13
21
|
let initialized = false;
|
|
14
|
-
let copyOnLoad = false;
|
|
15
22
|
const messageHandlers = {
|
|
16
23
|
[WorkerMsgType.INIT]: ({
|
|
17
24
|
maxCacheSize,
|
|
@@ -30,37 +37,50 @@ const messageHandlers = {
|
|
|
30
37
|
path,
|
|
31
38
|
options
|
|
32
39
|
}) => {
|
|
33
|
-
const
|
|
34
|
-
const fileType = options?.fileType || pathToFileType(pathString);
|
|
35
|
-
copyOnLoad = fileType === VolumeFileFormat.JSON;
|
|
36
|
-
loader = await createVolumeLoader(path, {
|
|
40
|
+
const loader = await createVolumeLoader(path, {
|
|
37
41
|
...options,
|
|
38
42
|
cache,
|
|
39
43
|
queue: subscribableQueue
|
|
40
44
|
});
|
|
41
|
-
return loader !== undefined;
|
|
42
|
-
},
|
|
43
|
-
[WorkerMsgType.CREATE_VOLUME]: async loadSpec => {
|
|
44
45
|
if (loader === undefined) {
|
|
45
|
-
|
|
46
|
+
return undefined;
|
|
46
47
|
}
|
|
48
|
+
const pathString = Array.isArray(path) ? path[0] : path;
|
|
49
|
+
const fileType = options?.fileType || pathToFileType(pathString);
|
|
50
|
+
const copyOnLoad = fileType === VolumeFileFormat.JSON;
|
|
51
|
+
const loaderId = loaderCount;
|
|
52
|
+
loaderCount += 1;
|
|
53
|
+
loaders.set(loaderId, {
|
|
54
|
+
loader,
|
|
55
|
+
copyOnLoad
|
|
56
|
+
});
|
|
57
|
+
return loaderId;
|
|
58
|
+
},
|
|
59
|
+
[WorkerMsgType.CLOSE_LOADER]: (_, loaderId) => {
|
|
60
|
+
loaders.delete(loaderId);
|
|
61
|
+
return Promise.resolve();
|
|
62
|
+
},
|
|
63
|
+
[WorkerMsgType.CREATE_VOLUME]: async (loadSpec, loaderId) => {
|
|
64
|
+
const {
|
|
65
|
+
loader
|
|
66
|
+
} = getLoader(loaderId);
|
|
47
67
|
return await loader.createImageInfo(rebuildLoadSpec(loadSpec));
|
|
48
68
|
},
|
|
49
|
-
[WorkerMsgType.LOAD_DIMS]: async loadSpec => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
69
|
+
[WorkerMsgType.LOAD_DIMS]: async (loadSpec, loaderId) => {
|
|
70
|
+
const {
|
|
71
|
+
loader
|
|
72
|
+
} = getLoader(loaderId);
|
|
53
73
|
return await loader.loadDims(rebuildLoadSpec(loadSpec));
|
|
54
74
|
},
|
|
55
75
|
[WorkerMsgType.LOAD_VOLUME_DATA]: ({
|
|
56
76
|
imageInfo,
|
|
57
77
|
loadSpec,
|
|
58
|
-
loaderId,
|
|
59
78
|
loadId
|
|
60
|
-
}) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
79
|
+
}, loaderId) => {
|
|
80
|
+
const {
|
|
81
|
+
loader,
|
|
82
|
+
copyOnLoad
|
|
83
|
+
} = getLoader(loaderId);
|
|
64
84
|
return loader.loadRawChannelData(imageInfo, rebuildLoadSpec(loadSpec), (imageInfo, loadSpec) => {
|
|
65
85
|
const message = {
|
|
66
86
|
responseResult: WorkerResponseResult.EVENT,
|
|
@@ -86,16 +106,25 @@ const messageHandlers = {
|
|
|
86
106
|
self.postMessage(message, copyOnLoad ? [] : data.map(d => d.buffer));
|
|
87
107
|
});
|
|
88
108
|
},
|
|
89
|
-
[WorkerMsgType.SET_PREFETCH_PRIORITY_DIRECTIONS]: directions => {
|
|
109
|
+
[WorkerMsgType.SET_PREFETCH_PRIORITY_DIRECTIONS]: (directions, loaderId) => {
|
|
110
|
+
const {
|
|
111
|
+
loader
|
|
112
|
+
} = getLoader(loaderId);
|
|
90
113
|
// Silently does nothing if the loader isn't an `OMEZarrLoader`
|
|
91
114
|
loader?.setPrefetchPriority(directions);
|
|
92
115
|
return Promise.resolve();
|
|
93
116
|
},
|
|
94
|
-
[WorkerMsgType.SYNCHRONIZE_MULTICHANNEL_LOADING]: syncChannels => {
|
|
117
|
+
[WorkerMsgType.SYNCHRONIZE_MULTICHANNEL_LOADING]: (syncChannels, loaderId) => {
|
|
118
|
+
const {
|
|
119
|
+
loader
|
|
120
|
+
} = getLoader(loaderId);
|
|
95
121
|
loader?.syncMultichannelLoading(syncChannels);
|
|
96
122
|
return Promise.resolve();
|
|
97
123
|
},
|
|
98
|
-
[WorkerMsgType.UPDATE_FETCH_OPTIONS]: fetchOptions => {
|
|
124
|
+
[WorkerMsgType.UPDATE_FETCH_OPTIONS]: (fetchOptions, loaderId) => {
|
|
125
|
+
const {
|
|
126
|
+
loader
|
|
127
|
+
} = getLoader(loaderId);
|
|
99
128
|
loader?.updateFetchOptions(fetchOptions);
|
|
100
129
|
return Promise.resolve();
|
|
101
130
|
}
|
|
@@ -103,25 +132,18 @@ const messageHandlers = {
|
|
|
103
132
|
self.onmessage = async ({
|
|
104
133
|
data
|
|
105
134
|
}) => {
|
|
106
|
-
const {
|
|
107
|
-
msgId,
|
|
108
|
-
type,
|
|
109
|
-
payload
|
|
110
|
-
} = data;
|
|
111
135
|
let message;
|
|
112
136
|
try {
|
|
113
|
-
const response = await messageHandlers[type](payload);
|
|
137
|
+
const response = await messageHandlers[data.type](data.payload, data.loaderId);
|
|
114
138
|
message = {
|
|
139
|
+
...data,
|
|
115
140
|
responseResult: WorkerResponseResult.SUCCESS,
|
|
116
|
-
msgId,
|
|
117
|
-
type,
|
|
118
141
|
payload: response
|
|
119
142
|
};
|
|
120
143
|
} catch (e) {
|
|
121
144
|
message = {
|
|
145
|
+
...data,
|
|
122
146
|
responseResult: WorkerResponseResult.ERROR,
|
|
123
|
-
msgId,
|
|
124
|
-
type,
|
|
125
147
|
payload: serializeError(e)
|
|
126
148
|
};
|
|
127
149
|
}
|
|
@@ -21,11 +21,11 @@ const throttle = throttledQueue(1, 16);
|
|
|
21
21
|
class SharedLoadWorkerHandle {
|
|
22
22
|
pendingRequests = [];
|
|
23
23
|
workerOpen = true;
|
|
24
|
-
|
|
25
|
-
onChannelData = undefined;
|
|
26
|
-
onUpdateMetadata = undefined;
|
|
24
|
+
onEvent = undefined;
|
|
27
25
|
constructor() {
|
|
28
|
-
this.worker = new Worker(new URL("./VolumeLoadWorker", import.meta.url)
|
|
26
|
+
this.worker = new Worker(new URL("./VolumeLoadWorker", import.meta.url), {
|
|
27
|
+
type: "module"
|
|
28
|
+
});
|
|
29
29
|
this.worker.onmessage = this.receiveMessage.bind(this);
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -53,7 +53,11 @@ class SharedLoadWorkerHandle {
|
|
|
53
53
|
* Send a message of type `T` to the worker.
|
|
54
54
|
* Returns a `Promise` that resolves with the worker's response, or rejects with an error message.
|
|
55
55
|
*/
|
|
56
|
-
|
|
56
|
+
// overload 1: message is a global action and does not require a loader ID
|
|
57
|
+
|
|
58
|
+
// overload 2: message is a loader-specific action and requires a loader ID
|
|
59
|
+
|
|
60
|
+
sendMessage(type, payload, loaderId) {
|
|
57
61
|
let msgId = -1;
|
|
58
62
|
const promise = new Promise((resolve, reject) => {
|
|
59
63
|
msgId = this.registerMessagePromise({
|
|
@@ -65,7 +69,8 @@ class SharedLoadWorkerHandle {
|
|
|
65
69
|
const msg = {
|
|
66
70
|
msgId,
|
|
67
71
|
type,
|
|
68
|
-
payload
|
|
72
|
+
payload,
|
|
73
|
+
loaderId
|
|
69
74
|
};
|
|
70
75
|
this.worker.postMessage(msg);
|
|
71
76
|
return promise;
|
|
@@ -76,17 +81,7 @@ class SharedLoadWorkerHandle {
|
|
|
76
81
|
data
|
|
77
82
|
}) {
|
|
78
83
|
if (data.responseResult === WorkerResponseResult.EVENT) {
|
|
79
|
-
|
|
80
|
-
if (this.onChannelData) {
|
|
81
|
-
if (this.throttleChannelData) {
|
|
82
|
-
throttle(() => this.onChannelData ? this.onChannelData(data) : {});
|
|
83
|
-
} else {
|
|
84
|
-
this.onChannelData ? this.onChannelData(data) : {};
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
} else if (data.eventType === WorkerEventType.METADATA_UPDATE) {
|
|
88
|
-
this.onUpdateMetadata?.(data);
|
|
89
|
-
}
|
|
84
|
+
this.onEvent?.(data);
|
|
90
85
|
} else {
|
|
91
86
|
const prom = this.pendingRequests[data.msgId];
|
|
92
87
|
if (prom === undefined) {
|
|
@@ -103,16 +98,13 @@ class SharedLoadWorkerHandle {
|
|
|
103
98
|
this.pendingRequests[data.msgId] = undefined;
|
|
104
99
|
}
|
|
105
100
|
}
|
|
106
|
-
setThrottleChannelData(throttle) {
|
|
107
|
-
this.throttleChannelData = throttle;
|
|
108
|
-
}
|
|
109
101
|
}
|
|
110
102
|
|
|
111
103
|
/**
|
|
112
104
|
* A context in which volume loaders can be run, which allows loading to run on a WebWorker (where it won't block
|
|
113
105
|
* rendering or UI updates) and loaders to share a single `VolumeCache` and `RequestQueue`.
|
|
114
106
|
*
|
|
115
|
-
*
|
|
107
|
+
* # To use:
|
|
116
108
|
* 1. Create a `VolumeLoaderContext` with the desired cache and queue configuration.
|
|
117
109
|
* 2. Before creating a loader, await `onOpen` to ensure the worker is ready.
|
|
118
110
|
* 3. Create a loader with `createLoader`. This accepts nearly the same arguments as `createVolumeLoader`, but without
|
|
@@ -123,10 +115,11 @@ class SharedLoadWorkerHandle {
|
|
|
123
115
|
* running on the worker.
|
|
124
116
|
*/
|
|
125
117
|
class VolumeLoaderContext {
|
|
126
|
-
|
|
127
|
-
activeLoaderId = -1;
|
|
118
|
+
throttleChannelData = false;
|
|
128
119
|
constructor(maxCacheSize, maxActiveRequests, maxLowPriorityRequests) {
|
|
129
120
|
this.workerHandle = new SharedLoadWorkerHandle();
|
|
121
|
+
this.workerHandle.onEvent = this.handleEvent.bind(this);
|
|
122
|
+
this.loaders = new Map();
|
|
130
123
|
this.openPromise = this.workerHandle.sendMessage(WorkerMsgType.INIT, {
|
|
131
124
|
maxCacheSize,
|
|
132
125
|
maxActiveRequests,
|
|
@@ -145,7 +138,20 @@ class VolumeLoaderContext {
|
|
|
145
138
|
/** Close this context, its worker, and any active loaders. */
|
|
146
139
|
close() {
|
|
147
140
|
this.workerHandle.close();
|
|
148
|
-
|
|
141
|
+
}
|
|
142
|
+
handleEvent(e) {
|
|
143
|
+
const loader = this.loaders.get(e.loaderId);
|
|
144
|
+
if (loader) {
|
|
145
|
+
if (e.eventType === WorkerEventType.CHANNEL_LOAD) {
|
|
146
|
+
if (this.throttleChannelData) {
|
|
147
|
+
throttle(() => loader.onChannelData(e));
|
|
148
|
+
} else {
|
|
149
|
+
loader.onChannelData(e);
|
|
150
|
+
}
|
|
151
|
+
} else if (e.eventType === WorkerEventType.METADATA_UPDATE) {
|
|
152
|
+
loader.onUpdateMetadata(e);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
149
155
|
}
|
|
150
156
|
|
|
151
157
|
/**
|
|
@@ -165,23 +171,19 @@ class VolumeLoaderContext {
|
|
|
165
171
|
}
|
|
166
172
|
return new RawArrayLoader(options.rawArrayOptions.data, options.rawArrayOptions.metadata);
|
|
167
173
|
}
|
|
168
|
-
const
|
|
174
|
+
const loaderId = await this.workerHandle.sendMessage(WorkerMsgType.CREATE_LOADER, {
|
|
169
175
|
path,
|
|
170
176
|
options
|
|
171
177
|
});
|
|
172
|
-
if (
|
|
178
|
+
if (loaderId === undefined) {
|
|
173
179
|
throw new Error("Failed to create loader");
|
|
174
180
|
}
|
|
175
|
-
this.
|
|
176
|
-
this.
|
|
177
|
-
|
|
178
|
-
return this.activeLoader;
|
|
181
|
+
const loader = new WorkerLoader(loaderId, this.workerHandle);
|
|
182
|
+
this.loaders.set(loaderId, loader);
|
|
183
|
+
return loader;
|
|
179
184
|
}
|
|
180
185
|
setThrottleChannelData(throttle) {
|
|
181
|
-
this.
|
|
182
|
-
}
|
|
183
|
-
getActiveLoader() {
|
|
184
|
-
return this.activeLoader;
|
|
186
|
+
this.throttleChannelData = throttle;
|
|
185
187
|
}
|
|
186
188
|
}
|
|
187
189
|
|
|
@@ -191,7 +193,6 @@ class VolumeLoaderContext {
|
|
|
191
193
|
* Created with `VolumeLoaderContext.createLoader`. See its documentation for more.
|
|
192
194
|
*/
|
|
193
195
|
class WorkerLoader extends ThreadableVolumeLoader {
|
|
194
|
-
isOpen = true;
|
|
195
196
|
currentLoadId = -1;
|
|
196
197
|
currentLoadCallback = undefined;
|
|
197
198
|
currentMetadataUpdateCallback = undefined;
|
|
@@ -199,18 +200,21 @@ class WorkerLoader extends ThreadableVolumeLoader {
|
|
|
199
200
|
super();
|
|
200
201
|
this.loaderId = loaderId;
|
|
201
202
|
this.workerHandle = workerHandle;
|
|
202
|
-
workerHandle.onChannelData = this.onChannelData.bind(this);
|
|
203
|
-
workerHandle.onUpdateMetadata = this.onUpdateMetadata.bind(this);
|
|
204
203
|
}
|
|
205
|
-
|
|
206
|
-
if (
|
|
204
|
+
getLoaderId() {
|
|
205
|
+
if (this.loaderId === undefined || !this.workerHandle.isOpen) {
|
|
207
206
|
throw new Error("Tried to use a closed loader");
|
|
208
207
|
}
|
|
208
|
+
return this.loaderId;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
/** Close and permanently invalidate this loader. */
|
|
212
212
|
close() {
|
|
213
|
-
this.
|
|
213
|
+
if (this.loaderId === undefined) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
this.workerHandle.sendMessage(WorkerMsgType.CLOSE_LOADER, undefined, this.loaderId);
|
|
217
|
+
this.loaderId = undefined;
|
|
214
218
|
}
|
|
215
219
|
|
|
216
220
|
/**
|
|
@@ -218,40 +222,37 @@ class WorkerLoader extends ThreadableVolumeLoader {
|
|
|
218
222
|
* any chunks are prefetched in any other directions. Has no effect if this loader doesn't support prefetching.
|
|
219
223
|
*/
|
|
220
224
|
setPrefetchPriority(directions) {
|
|
221
|
-
return this.workerHandle.sendMessage(WorkerMsgType.SET_PREFETCH_PRIORITY_DIRECTIONS, directions);
|
|
225
|
+
return this.workerHandle.sendMessage(WorkerMsgType.SET_PREFETCH_PRIORITY_DIRECTIONS, directions, this.getLoaderId());
|
|
222
226
|
}
|
|
223
227
|
updateFetchOptions(fetchOptions) {
|
|
224
|
-
return this.workerHandle.sendMessage(WorkerMsgType.UPDATE_FETCH_OPTIONS, fetchOptions);
|
|
228
|
+
return this.workerHandle.sendMessage(WorkerMsgType.UPDATE_FETCH_OPTIONS, fetchOptions, this.getLoaderId());
|
|
225
229
|
}
|
|
226
230
|
syncMultichannelLoading(sync) {
|
|
227
|
-
return this.workerHandle.sendMessage(WorkerMsgType.SYNCHRONIZE_MULTICHANNEL_LOADING, sync);
|
|
231
|
+
return this.workerHandle.sendMessage(WorkerMsgType.SYNCHRONIZE_MULTICHANNEL_LOADING, sync, this.getLoaderId());
|
|
228
232
|
}
|
|
229
233
|
loadDims(loadSpec) {
|
|
230
|
-
this.
|
|
231
|
-
return this.workerHandle.sendMessage(WorkerMsgType.LOAD_DIMS, loadSpec);
|
|
234
|
+
return this.workerHandle.sendMessage(WorkerMsgType.LOAD_DIMS, loadSpec, this.getLoaderId());
|
|
232
235
|
}
|
|
233
236
|
async createImageInfo(loadSpec) {
|
|
234
|
-
this.checkIsOpen();
|
|
235
237
|
const {
|
|
236
238
|
imageInfo,
|
|
237
239
|
loadSpec: adjustedLoadSpec
|
|
238
|
-
} = await this.workerHandle.sendMessage(WorkerMsgType.CREATE_VOLUME, loadSpec);
|
|
240
|
+
} = await this.workerHandle.sendMessage(WorkerMsgType.CREATE_VOLUME, loadSpec, this.getLoaderId());
|
|
239
241
|
return {
|
|
240
242
|
imageInfo,
|
|
241
243
|
loadSpec: rebuildLoadSpec(adjustedLoadSpec)
|
|
242
244
|
};
|
|
243
245
|
}
|
|
244
246
|
loadRawChannelData(imageInfo, loadSpec, onUpdateMetadata, onData) {
|
|
245
|
-
this.checkIsOpen();
|
|
246
247
|
this.currentLoadCallback = onData;
|
|
247
248
|
this.currentMetadataUpdateCallback = onUpdateMetadata;
|
|
248
249
|
this.currentLoadId += 1;
|
|
249
|
-
|
|
250
|
+
const message = {
|
|
250
251
|
imageInfo,
|
|
251
252
|
loadSpec,
|
|
252
|
-
loaderId: this.loaderId,
|
|
253
253
|
loadId: this.currentLoadId
|
|
254
|
-
}
|
|
254
|
+
};
|
|
255
|
+
return this.workerHandle.sendMessage(WorkerMsgType.LOAD_VOLUME_DATA, message, this.getLoaderId());
|
|
255
256
|
}
|
|
256
257
|
onChannelData(e) {
|
|
257
258
|
if (e.loaderId !== this.loaderId || e.loadId !== this.currentLoadId) {
|
package/es/workers/types.js
CHANGED
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
export let WorkerMsgType = /*#__PURE__*/function (WorkerMsgType) {
|
|
3
3
|
WorkerMsgType[WorkerMsgType["INIT"] = 0] = "INIT";
|
|
4
4
|
WorkerMsgType[WorkerMsgType["CREATE_LOADER"] = 1] = "CREATE_LOADER";
|
|
5
|
-
WorkerMsgType[WorkerMsgType["
|
|
6
|
-
WorkerMsgType[WorkerMsgType["
|
|
7
|
-
WorkerMsgType[WorkerMsgType["
|
|
8
|
-
WorkerMsgType[WorkerMsgType["
|
|
9
|
-
WorkerMsgType[WorkerMsgType["
|
|
10
|
-
WorkerMsgType[WorkerMsgType["
|
|
5
|
+
WorkerMsgType[WorkerMsgType["CLOSE_LOADER"] = 2] = "CLOSE_LOADER";
|
|
6
|
+
WorkerMsgType[WorkerMsgType["CREATE_VOLUME"] = 3] = "CREATE_VOLUME";
|
|
7
|
+
WorkerMsgType[WorkerMsgType["LOAD_DIMS"] = 4] = "LOAD_DIMS";
|
|
8
|
+
WorkerMsgType[WorkerMsgType["LOAD_VOLUME_DATA"] = 5] = "LOAD_VOLUME_DATA";
|
|
9
|
+
WorkerMsgType[WorkerMsgType["SET_PREFETCH_PRIORITY_DIRECTIONS"] = 6] = "SET_PREFETCH_PRIORITY_DIRECTIONS";
|
|
10
|
+
WorkerMsgType[WorkerMsgType["SYNCHRONIZE_MULTICHANNEL_LOADING"] = 7] = "SYNCHRONIZE_MULTICHANNEL_LOADING";
|
|
11
|
+
WorkerMsgType[WorkerMsgType["UPDATE_FETCH_OPTIONS"] = 8] = "UPDATE_FETCH_OPTIONS";
|
|
11
12
|
return WorkerMsgType;
|
|
12
13
|
}({});
|
|
13
14
|
|
|
15
|
+
/** The variants of `WorkerMessageType` which represent "global" actions that don't require a specific loader */
|
|
16
|
+
|
|
17
|
+
/** The variants of `WorkerMessageType` which represent actions on a specific loader */
|
|
18
|
+
|
|
14
19
|
/** The kind of response a worker can return - `SUCCESS`, `ERROR`, or `EVENT`. */
|
|
15
20
|
export let WorkerResponseResult = /*#__PURE__*/function (WorkerResponseResult) {
|
|
16
21
|
WorkerResponseResult[WorkerResponseResult["SUCCESS"] = 0] = "SUCCESS";
|
|
@@ -21,12 +26,17 @@ export let WorkerResponseResult = /*#__PURE__*/function (WorkerResponseResult) {
|
|
|
21
26
|
|
|
22
27
|
/** The kind of events that can occur when loading */
|
|
23
28
|
export let WorkerEventType = /*#__PURE__*/function (WorkerEventType) {
|
|
29
|
+
/** Fired to update a `Volume`'s `imageInfo` and/or `loadSpec` based on loaded data (time, channels, region, etc.) */
|
|
24
30
|
WorkerEventType[WorkerEventType["METADATA_UPDATE"] = 0] = "METADATA_UPDATE";
|
|
31
|
+
/** Fired when data for a channel (or batch of channels) is loaded */
|
|
25
32
|
WorkerEventType[WorkerEventType["CHANNEL_LOAD"] = 1] = "CHANNEL_LOAD";
|
|
26
33
|
return WorkerEventType;
|
|
27
34
|
}({});
|
|
28
35
|
|
|
29
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* All messages to/from a worker carry a `msgId`, a `type`, and a `payload` (whose type is determined by `type`).
|
|
38
|
+
* Messages which operate on a specific loader also require a `loaderId`.
|
|
39
|
+
*/
|
|
30
40
|
|
|
31
41
|
/** Maps each `WorkerMsgType` to the type of the payload of requests of that type. */
|
|
32
42
|
|